diff options
-rw-r--r-- | ChangeLog | 8 | ||||
-rw-r--r-- | class.c | 6 | ||||
-rw-r--r-- | eval.c | 197 | ||||
-rw-r--r-- | gc.c | 6 | ||||
-rw-r--r-- | include/ruby/intern.h | 1 | ||||
-rw-r--r-- | include/ruby/ruby.h | 1 | ||||
-rw-r--r-- | insns.def | 16 | ||||
-rw-r--r-- | iseq.c | 13 | ||||
-rw-r--r-- | method.h | 3 | ||||
-rw-r--r-- | node.h | 8 | ||||
-rw-r--r-- | object.c | 1 | ||||
-rw-r--r-- | test/ruby/test_fiber.rb | 5 | ||||
-rw-r--r-- | test/ruby/test_refinement.rb | 304 | ||||
-rw-r--r-- | test/ruby/test_thread.rb | 5 | ||||
-rw-r--r-- | vm.c | 4 | ||||
-rw-r--r-- | vm_eval.c | 4 | ||||
-rw-r--r-- | vm_insnhelper.c | 80 | ||||
-rw-r--r-- | vm_insnhelper.h | 9 | ||||
-rw-r--r-- | vm_method.c | 83 |
19 files changed, 699 insertions, 55 deletions
@@ -1,3 +1,11 @@ +Thu Aug 2 20:32:29 2012 Shugo Maeda <shugo@ruby-lang.org> + + * eval.c (rb_mod_using): new method Module#using. [experimental] + + * eval.c (rb_mod_refine): new method Module#refine. [experimental] + + * eval.c (f_using): new method Kernel#using. [experimental] + Thu Aug 2 20:08:02 2012 Shugo Maeda <shugo@ruby-lang.org> * class.c, insns.def, method.h, proc.c, vm.c, vm_core.h, vm_eval.c, @@ -620,8 +620,8 @@ rb_define_module_id_under(VALUE outer, ID id) return module; } -static VALUE -include_class_new(VALUE module, VALUE super) +VALUE +rb_include_class_new(VALUE module, VALUE super) { VALUE klass = class_alloc(T_ICLASS, rb_cClass); @@ -703,7 +703,7 @@ include_modules_at(VALUE klass, VALUE c, VALUE module) break; } } - c = RCLASS_SUPER(c) = include_class_new(module, RCLASS_SUPER(c)); + c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) changed = 1; skip: @@ -23,6 +23,8 @@ NORETURN(void rb_raise_jump(VALUE)); +NODE *rb_vm_get_cref(const rb_iseq_t *, const VALUE *); + VALUE rb_eLocalJumpError; VALUE rb_eSysStackError; @@ -1025,6 +1027,180 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module) } void +rb_overlay_module(NODE *cref, VALUE klass, VALUE module) +{ + VALUE iclass, c, superclass = klass; + + Check_Type(klass, T_CLASS); + Check_Type(module, T_MODULE); + if (NIL_P(cref->nd_omod)) { + cref->nd_omod = rb_hash_new(); + rb_funcall(cref->nd_omod, rb_intern("compare_by_identity"), 0); + } + else { + if (cref->flags & NODE_FL_CREF_OMOD_SHARED) { + cref->nd_omod = rb_hash_dup(cref->nd_omod); + cref->flags &= ~NODE_FL_CREF_OMOD_SHARED; + } + if (!NIL_P(c = rb_hash_lookup(cref->nd_omod, klass))) { + superclass = c; + while (c && TYPE(c) == T_ICLASS) { + if (RBASIC(c)->klass == module) { + /* already overlayed module */ + return; + } + c = RCLASS_SUPER(c); + } + } + } + FL_SET(module, RMODULE_IS_OVERLAYED); + c = iclass = rb_include_class_new(module, superclass); + module = RCLASS_SUPER(module); + while (module) { + FL_SET(module, RMODULE_IS_OVERLAYED); + c = RCLASS_SUPER(c) = rb_include_class_new(module, RCLASS_SUPER(c)); + module = RCLASS_SUPER(module); + } + rb_hash_aset(cref->nd_omod, klass, iclass); + rb_clear_cache_by_class(klass); +} + +static int +using_module_i(VALUE klass, VALUE module, VALUE arg) +{ + NODE *cref = (NODE *) arg; + + rb_overlay_module(cref, klass, module); + return ST_CONTINUE; +} + +void +rb_using_module(NODE *cref, VALUE module) +{ + ID id_overlayed_modules; + VALUE overlayed_modules; + + Check_Type(module, T_MODULE); + CONST_ID(id_overlayed_modules, "__overlayed_modules__"); + overlayed_modules = rb_attr_get(module, id_overlayed_modules); + if (NIL_P(overlayed_modules)) return; + rb_hash_foreach(overlayed_modules, using_module_i, (VALUE) cref); +} + +/* + * call-seq: + * using(module) -> self + * + * Import class refinements from <i>module</i> into the receiver. + */ + +static VALUE +rb_mod_using(VALUE self, VALUE module) +{ + NODE *cref = rb_vm_cref(); + ID id_using_modules; + VALUE using_modules; + + CONST_ID(id_using_modules, "__using_modules__"); + using_modules = rb_attr_get(self, id_using_modules); + if (NIL_P(using_modules)) { + using_modules = rb_hash_new(); + rb_funcall(using_modules, rb_intern("compare_by_identity"), 0); + rb_ivar_set(self, id_using_modules, using_modules); + } + rb_hash_aset(using_modules, module, Qtrue); + rb_using_module(cref, module); + rb_funcall(module, rb_intern("used"), 1, self); + return self; +} + +void rb_redefine_opt_method(VALUE, ID); + +static VALUE +refinement_module_method_added(VALUE mod, VALUE mid) +{ + ID id = SYM2ID(mid); + ID id_refined_class; + VALUE klass; + + CONST_ID(id_refined_class, "__refined_class__"); + klass = rb_ivar_get(mod, id_refined_class); + rb_redefine_opt_method(klass, id); + return Qnil; +} + +static VALUE +refinement_module_include(int argc, VALUE *argv, VALUE module) +{ + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th->cfp; + rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(th); + VALUE result = rb_mod_include(argc, argv, module); + NODE *cref; + ID id_refined_class; + VALUE klass, c; + + CONST_ID(id_refined_class, "__refined_class__"); + klass = rb_attr_get(module, id_refined_class); + while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { + if (RUBY_VM_NORMAL_ISEQ_P(cfp->iseq) && + (cref = rb_vm_get_cref(cfp->iseq, cfp->ep)) && + !NIL_P(cref->nd_omod) && + !NIL_P(c = rb_hash_lookup(cref->nd_omod, klass))) { + while (argc--) { + VALUE mod = argv[argc]; + if (rb_class_inherited_p(module, mod)) { + RCLASS_SUPER(c) = + rb_include_class_new(mod, RCLASS_SUPER(c)); + } + } + break; + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + return result; +} + +/* + * call-seq: + * refine(klass) { block } -> self + * + * Refine <i>klass</i> in the receiver. + */ + +static VALUE +rb_mod_refine(VALUE module, VALUE klass) +{ + NODE *cref = rb_vm_cref(); + VALUE mod; + ID id_overlayed_modules, id_refined_class; + VALUE overlayed_modules; + + Check_Type(klass, T_CLASS); + CONST_ID(id_overlayed_modules, "__overlayed_modules__"); + overlayed_modules = rb_attr_get(module, id_overlayed_modules); + if (NIL_P(overlayed_modules)) { + overlayed_modules = rb_hash_new(); + rb_funcall(overlayed_modules, rb_intern("compare_by_identity"), 0); + rb_ivar_set(module, id_overlayed_modules, overlayed_modules); + } + mod = rb_hash_aref(overlayed_modules, klass); + if (NIL_P(mod)) { + mod = rb_module_new(); + CONST_ID(id_refined_class, "__refined_class__"); + rb_ivar_set(mod, id_refined_class, klass); + rb_define_singleton_method(mod, "method_added", + refinement_module_method_added, 1); + rb_define_singleton_method(mod, "include", + refinement_module_include, -1); + rb_overlay_module(cref, klass, mod); + rb_hash_aset(overlayed_modules, klass, mod); + } + rb_mod_module_eval(0, NULL, mod); + return mod; +} + +void rb_obj_call_init(VALUE obj, int argc, VALUE *argv) { PASS_PASSED_BLOCK(); @@ -1134,6 +1310,23 @@ top_include(int argc, VALUE *argv, VALUE self) return rb_mod_include(argc, argv, rb_cObject); } +/* + * call-seq: + * using(module) -> self + * + * Import class refinements from <i>module</i> into the scope where + * <code>using</code> is called. + */ + +static VALUE +f_using(VALUE self, VALUE module) +{ + NODE *cref = rb_vm_cref(); + + rb_using_module(cref, module); + return self; +} + static VALUE * errinfo_place(rb_thread_t *th) { @@ -1298,6 +1491,8 @@ Init_eval(void) rb_define_private_method(rb_cModule, "include", rb_mod_include, -1); rb_define_private_method(rb_cModule, "prepend_features", rb_mod_prepend_features, 1); rb_define_private_method(rb_cModule, "prepend", rb_mod_prepend, -1); + rb_define_private_method(rb_cModule, "using", rb_mod_using, 1); + rb_define_private_method(rb_cModule, "refine", rb_mod_refine, 1); rb_undef_method(rb_cClass, "module_function"); @@ -1309,6 +1504,8 @@ Init_eval(void) rb_define_singleton_method(rb_vm_top_self(), "include", top_include, -1); + rb_define_global_function("using", f_using, 1); + rb_define_method(rb_mKernel, "extend", rb_obj_extend, -1); rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */ @@ -1994,6 +1994,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr, int lev) ptr = (VALUE)obj->as.node.u2.node; goto again; + case NODE_CREF: + gc_mark(objspace, obj->as.node.nd_omod, lev); + gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); + ptr = (VALUE)obj->as.node.u3.node; + goto again; + default: /* unlisted NODE */ if (is_pointer_to_heap(objspace, obj->as.node.u1.node)) { gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev); diff --git a/include/ruby/intern.h b/include/ruby/intern.h index bc3d0fcea2..71f5442a7a 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -173,6 +173,7 @@ VALUE rb_define_class_id_under(VALUE, ID, VALUE); VALUE rb_module_new(void); VALUE rb_define_module_id(ID); VALUE rb_define_module_id_under(VALUE, ID); +VALUE rb_include_class_new(VALUE, VALUE); VALUE rb_mod_included_modules(VALUE); VALUE rb_mod_include_p(VALUE, VALUE); VALUE rb_mod_ancestors(VALUE); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index e9474d10fa..c551791412 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -655,6 +655,7 @@ struct RClass { #define RMODULE_CONST_TBL(m) RCLASS_CONST_TBL(m) #define RMODULE_M_TBL(m) RCLASS_M_TBL(m) #define RMODULE_SUPER(m) RCLASS_SUPER(m) +#define RMODULE_IS_OVERLAYED FL_USER2 struct RFloat { struct RBasic basic; @@ -183,8 +183,8 @@ getclassvariable () (VALUE val) { - NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP()); - val = rb_cvar_get(vm_get_cvar_base(cref), id); + NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP()); + val = rb_cvar_get(vm_get_cvar_base(cref, GET_CFP()), id); } /** @@ -198,8 +198,8 @@ setclassvariable (VALUE val) () { - NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP()); - rb_cvar_set(vm_get_cvar_base(cref), id, val); + NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP()); + rb_cvar_set(vm_get_cvar_base(cref, GET_CFP()), id, val); } /** @@ -777,8 +777,8 @@ defined break; case DEFINED_CVAR: { - NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP()); - klass = vm_get_cvar_base(cref); + NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP()); + klass = vm_get_cvar_base(cref, GET_CFP()); if (rb_cvar_defined(klass, SYM2ID(obj))) { expr_type = "class variable"; } @@ -970,6 +970,7 @@ defineclass klass, 0, VM_ENVVAL_BLOCK_PTR(GET_BLOCK_PTR()), class_iseq->iseq_encoded, GET_SP(), class_iseq->local_size, 0); + rb_vm_using_modules(class_iseq->cref_stack, klass); RESTORE_REGS(); INC_VM_STATE_VERSION(); @@ -1044,12 +1045,11 @@ invokesuper while (ip && !ip->klass) { ip = ip->parent_iseq; } - again: me = rb_method_entry(klass, id, &klass); if (me && me->def->type == VM_METHOD_TYPE_ISEQ && me->def->body.iseq == ip) { klass = RCLASS_SUPER(klass); - goto again; + me = rb_method_entry_get_with_omod(Qnil, klass, id, &klass); } CALL_METHOD(num, blockptr, flag, id, me, recv, klass); @@ -199,17 +199,20 @@ set_relation(rb_iseq_t *iseq, const VALUE parent) /* set class nest stack */ if (type == ISEQ_TYPE_TOP) { /* toplevel is private */ - iseq->cref_stack = NEW_BLOCK(rb_cObject); + iseq->cref_stack = NEW_CREF(rb_cObject); + iseq->cref_stack->nd_omod = Qnil; iseq->cref_stack->nd_visi = NOEX_PRIVATE; if (th->top_wrapper) { - NODE *cref = NEW_BLOCK(th->top_wrapper); + NODE *cref = NEW_CREF(th->top_wrapper); + cref->nd_omod = Qnil; cref->nd_visi = NOEX_PRIVATE; cref->nd_next = iseq->cref_stack; iseq->cref_stack = cref; } } else if (type == ISEQ_TYPE_METHOD || type == ISEQ_TYPE_CLASS) { - iseq->cref_stack = NEW_BLOCK(0); /* place holder */ + iseq->cref_stack = NEW_CREF(0); /* place holder */ + iseq->cref_stack->nd_omod = Qnil; } else if (RTEST(parent)) { rb_iseq_t *piseq; @@ -1653,7 +1656,9 @@ rb_iseq_clone(VALUE iseqval, VALUE newcbase) iseq1->local_iseq = iseq1; } if (newcbase) { - iseq1->cref_stack = NEW_BLOCK(newcbase); + iseq1->cref_stack = NEW_CREF(newcbase); + iseq1->cref_stack->nd_omod = iseq0->cref_stack->nd_omod; + iseq1->cref_stack->nd_visi = iseq0->cref_stack->nd_visi; if (iseq0->cref_stack->nd_next) { iseq1->cref_stack->nd_next = iseq0->cref_stack->nd_next; } @@ -91,7 +91,8 @@ void rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_entry_t *rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *option, rb_method_flag_t noex); rb_method_entry_t *rb_method_entry(VALUE klass, ID id, VALUE *define_class_ptr); -rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, ID id, VALUE *define_class_ptr); +rb_method_entry_t *rb_method_entry_get_with_omod(VALUE omod, VALUE klass, ID id, VALUE *define_class_ptr); +rb_method_entry_t *rb_method_entry_get_without_cache(VALUE klass, VALUE omod, ID id, VALUE *define_class_ptr); rb_method_entry_t *rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *, rb_method_flag_t noex); int rb_method_entry_arity(const rb_method_entry_t *me); @@ -190,6 +190,8 @@ enum node_type { #define NODE_COLON2 NODE_COLON2 NODE_COLON3, #define NODE_COLON3 NODE_COLON3 + NODE_CREF, +#define NODE_CREF NODE_CREF NODE_DOT2, #define NODE_DOT2 NODE_DOT2 NODE_DOT3, @@ -263,9 +265,10 @@ typedef struct RNode { #define RNODE(obj) (R_CAST(RNode)(obj)) -/* 0..4:T_TYPES, 5:reserved, 6:reserved, 7:NODE_FL_NEWLINE */ +/* 0..4:T_TYPES, 5:reserved, 6:NODE_FL_CREF_OMOD_SHARED, 7:NODE_FL_NEWLINE */ #define NODE_FL_NEWLINE (((VALUE)1)<<7) #define NODE_FL_CREF_PUSHED_BY_EVAL NODE_FL_NEWLINE +#define NODE_FL_CREF_OMOD_SHARED (((VALUE)1)<<6) #define NODE_TYPESHIFT 8 #define NODE_TYPEMASK (((VALUE)0x7f)<<NODE_TYPESHIFT) @@ -280,6 +283,8 @@ typedef struct RNode { #define nd_set_line(n,l) \ RNODE(n)->flags=((RNODE(n)->flags&~(-1<<NODE_LSHIFT))|(((l)&NODE_LMASK)<<NODE_LSHIFT)) +#define nd_omod nd_reserved + #define nd_head u1.node #define nd_alen u2.argc #define nd_next u3.node @@ -437,6 +442,7 @@ typedef struct RNode { #define NEW_MODULE(n,b) NEW_NODE(NODE_MODULE,n,NEW_SCOPE(0,b),0) #define NEW_COLON2(c,i) NEW_NODE(NODE_COLON2,c,i,0) #define NEW_COLON3(i) NEW_NODE(NODE_COLON3,0,i,0) +#define NEW_CREF(a) NEW_NODE(NODE_CREF,a,0,0) #define NEW_DOT2(b,e) NEW_NODE(NODE_DOT2,b,e,0) #define NEW_DOT3(b,e) NEW_NODE(NODE_DOT3,b,e,0) #define NEW_SELF() NEW_NODE(NODE_SELF,0,0,0) @@ -2863,6 +2863,7 @@ Init_Object(void) rb_define_private_method(rb_cModule, "included", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "extended", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "prepended", rb_obj_dummy, 1); + rb_define_private_method(rb_cModule, "used", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_added", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_removed", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_undefined", rb_obj_dummy, 1); diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index 9bac234252..44bdbfc2f7 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -216,10 +216,7 @@ class TestFiber < Test::Unit::TestCase def test_no_valid_cfp bug5083 = '[ruby-dev:44208]' - error = assert_raise(RuntimeError) do - Fiber.new(&Module.method(:nesting)).resume - end - assert_equal("Can't call on top of Fiber or Thread", error.message, bug5083) + assert_equal([], Fiber.new(&Module.method(:nesting)).resume) error = assert_raise(RuntimeError) do Fiber.new(&Module.method(:undef_method)).resume(:to_s) end diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb new file mode 100644 index 0000000000..65789899c5 --- /dev/null +++ b/test/ruby/test_refinement.rb @@ -0,0 +1,304 @@ +require 'test/unit' + +class TestRefinement < Test::Unit::TestCase + class Foo + def x + return "Foo#x" + end + + def y + return "Foo#y" + end + + def call_x + return x + end + end + + module FooExt + refine Foo do + def x + return "FooExt#x" + end + + def y + return "FooExt#y " + super + end + + def z + return "FooExt#z" + end + end + end + + module FooExt2 + refine Foo do + def x + return "FooExt2#x" + end + + def y + return "FooExt2#y " + super + end + + def z + return "FooExt2#z" + end + end + end + + class FooSub < Foo + def x + return "FooSub#x" + end + + def y + return "FooSub#y " + super + end + end + + class FooExtClient + using FooExt + + def self.invoke_x_on(foo) + return foo.x + end + + def self.invoke_y_on(foo) + return foo.y + end + + def self.invoke_z_on(foo) + return foo.z + end + + def self.invoke_call_x_on(foo) + return foo.call_x + end + end + + class FooExtClient2 + using FooExt + using FooExt2 + + def self.invoke_y_on(foo) + return foo.y + end + end + + def test_override + foo = Foo.new + assert_equal("Foo#x", foo.x) + assert_equal("FooExt#x", FooExtClient.invoke_x_on(foo)) + assert_equal("Foo#x", foo.x) + end + + def test_super + foo = Foo.new + assert_equal("Foo#y", foo.y) + assert_equal("FooExt#y Foo#y", FooExtClient.invoke_y_on(foo)) + assert_equal("Foo#y", foo.y) + end + + def test_super_chain + foo = Foo.new + assert_equal("Foo#y", foo.y) + assert_equal("FooExt2#y FooExt#y Foo#y", FooExtClient2.invoke_y_on(foo)) + assert_equal("Foo#y", foo.y) + end + + def test_new_method + foo = Foo.new + assert_raise(NoMethodError) { foo.z } + assert_equal("FooExt#z", FooExtClient.invoke_z_on(foo)) + assert_raise(NoMethodError) { foo.z } + end + + def test_no_local_rebinding + foo = Foo.new + assert_equal("Foo#x", foo.call_x) + assert_equal("Foo#x", FooExtClient.invoke_call_x_on(foo)) + assert_equal("Foo#x", foo.call_x) + end + + def test_subclass_is_prior + sub = FooSub.new + assert_equal("FooSub#x", sub.x) + assert_equal("FooSub#x", FooExtClient.invoke_x_on(sub)) + assert_equal("FooSub#x", sub.x) + end + + def test_super_in_subclass + sub = FooSub.new + assert_equal("FooSub#y Foo#y", sub.y) + # not "FooSub#y FooExt#y Foo#y" + assert_equal("FooSub#y Foo#y", FooExtClient.invoke_y_on(sub)) + assert_equal("FooSub#y Foo#y", sub.y) + end + + def test_new_method_on_subclass + sub = FooSub.new + assert_raise(NoMethodError) { sub.z } + assert_equal("FooExt#z", FooExtClient.invoke_z_on(sub)) + assert_raise(NoMethodError) { sub.z } + end + + def test_module_eval + foo = Foo.new + assert_equal("Foo#x", foo.x) + assert_equal("FooExt#x", FooExt.module_eval { foo.x }) + assert_equal("Foo#x", foo.x) + end + + def test_instance_eval + foo = Foo.new + ext_client = FooExtClient.new + assert_equal("Foo#x", foo.x) + assert_equal("FooExt#x", ext_client.instance_eval { foo.x }) + assert_equal("Foo#x", foo.x) + end + + def test_override_builtin_method + m = Module.new { + refine Fixnum do + def /(other) quo(other) end + end + } + assert_equal(0, 1 / 2) + assert_equal(Rational(1, 2), m.module_eval { 1 / 2 }) + assert_equal(0, 1 / 2) + end + + def test_return_value_of_refine + mod = nil + result = nil + m = Module.new { + result = refine(Object) { + mod = self + } + } + assert_equal mod, result + end + + def test_refine_same_class_twice + result1 = nil + result2 = nil + result3 = nil + m = Module.new { + result1 = refine(Fixnum) { + def foo; return "foo" end + } + result2 = refine(Fixnum) { + def bar; return "bar" end + } + result3 = refine(String) { + def baz; return "baz" end + } + } + assert_equal("foo", m.module_eval { 1.foo }) + assert_equal("bar", m.module_eval { 1.bar }) + assert_equal(result1, result2) + assert_not_equal(result1, result3) + end + + def test_respond_to? + m = Module.new { + refine Fixnum do + def foo; "foo"; end + end + } + assert_equal(false, 1.respond_to?(:foo)) + assert_equal(true, m.module_eval { 1.respond_to?(:foo) }) + assert_equal(false, 1.respond_to?(:foo)) + end + + def test_builtin_method_no_local_rebinding + m = Module.new { + refine String do + def <=>(other) return 0 end + end + } + assert_equal(false, m.module_eval { "1" >= "2" }) + + m2 = Module.new { + refine Array do + def each + super do |i| + yield 2 * i + end + end + end + } + a = [1, 2, 3] + assert_equal(1, m2.module_eval { a.min }) + end + + def test_module_inclusion + m1 = Module.new { + def foo + "m1#foo" + end + + def bar + "m1#bar" + end + } + m2 = Module.new { + def bar + "m2#bar" + end + + def baz + "m2#baz" + end + } + m3 = Module.new { + def baz + "m3#baz" + end + } + include_proc = Proc.new { + include m3, m2 + } + m = Module.new { + refine String do + include m1 + module_eval(&include_proc) + + def call_foo + foo + end + + def call_bar + bar + end + + def call_baz + baz + end + end + + def self.call_foo(s) + s.foo + end + + def self.call_bar(s) + s.bar + end + + def self.call_baz(s) + s.baz + end + } + assert_equal("m1#foo", m.module_eval { "abc".foo }) + assert_equal("m2#bar", m.module_eval { "abc".bar }) + assert_equal("m3#baz", m.module_eval { "abc".baz }) + assert_equal("m1#foo", m.module_eval { "abc".call_foo }) + assert_equal("m2#bar", m.module_eval { "abc".call_bar }) + assert_equal("m3#baz", m.module_eval { "abc".call_baz }) + assert_equal("m1#foo", m.call_foo("abc")) + assert_equal("m2#bar", m.call_bar("abc")) + assert_equal("m3#baz", m.call_baz("abc")) + end +end + diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 523cad4242..c8081b46b8 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -606,10 +606,7 @@ class TestThread < Test::Unit::TestCase def test_no_valid_cfp skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#intialize' if defined?(WIN32OLE) bug5083 = '[ruby-dev:44208]' - error = assert_raise(RuntimeError) do - Thread.new(&Module.method(:nesting)).join - end - assert_equal("Can't call on top of Fiber or Thread", error.message, bug5083) + assert_equal([], Thread.new(&Module.method(:nesting)).value) error = assert_raise(RuntimeError) do Thread.new(:to_s, &Module.method(:undef_method)).join end @@ -787,9 +787,9 @@ rb_vm_cref(void) rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp); if (cfp == 0) { - rb_raise(rb_eRuntimeError, "Can't call on top of Fiber or Thread"); + return NULL; } - return vm_get_cref(cfp->iseq, cfp->ep); + return rb_vm_get_cref(cfp->iseq, cfp->ep); } #if 0 @@ -391,7 +391,7 @@ rb_search_method_entry(VALUE recv, ID mid, VALUE *defined_class_ptr) rb_id2name(mid), type, (void *)recv, flags, klass); } } - return rb_method_entry(klass, mid, defined_class_ptr); + return rb_method_entry_get_with_omod(Qnil, klass, mid, defined_class_ptr); } static inline int @@ -1310,6 +1310,7 @@ yield_under(VALUE under, VALUE self, VALUE values) } cref = vm_cref_push(th, under, NOEX_PUBLIC, blockptr); cref->flags |= NODE_FL_CREF_PUSHED_BY_EVAL; + rb_vm_using_modules(cref, under); if (values == Qundef) { return vm_yield_with_cref(th, 1, &self, cref); @@ -1331,6 +1332,7 @@ eval_under(VALUE under, VALUE self, VALUE src, const char *file, int line) else { SafeStringValue(src); } + rb_vm_using_modules(cref, under); return eval_string_with_cref(self, src, Qnil, cref, file, line); } diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 8e24a73604..b06fb55f02 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1128,13 +1128,13 @@ vm_get_cref0(const rb_iseq_t *iseq, const VALUE *ep) } } -static NODE * -vm_get_cref(const rb_iseq_t *iseq, const VALUE *ep) +NODE * +rb_vm_get_cref(const rb_iseq_t *iseq, const VALUE *ep) { NODE *cref = vm_get_cref0(iseq, ep); if (cref == 0) { - rb_bug("vm_get_cref: unreachable"); + rb_bug("rb_vm_get_cref: unreachable"); } return cref; } @@ -1143,7 +1143,8 @@ static NODE * vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr) { rb_control_frame_t *cfp = vm_get_ruby_level_caller_cfp(th, th->cfp); - NODE *cref = NEW_BLOCK(klass); + NODE *cref = NEW_CREF(klass); + cref->nd_omod = Qnil; cref->nd_visi = noex; if (blockptr) { @@ -1152,6 +1153,11 @@ vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr) else if (cfp) { cref->nd_next = vm_get_cref0(cfp->iseq, cfp->ep); } + /* TODO: why cref->nd_next is 1? */ + if (cref->nd_next && cref->nd_next != (void *) 1 && + !NIL_P(cref->nd_next->nd_omod)) { + COPY_CREF_OMOD(cref, cref->nd_next); + } return cref; } @@ -1159,7 +1165,7 @@ vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr) static inline VALUE vm_get_cbase(const rb_iseq_t *iseq, const VALUE *ep) { - NODE *cref = vm_get_cref(iseq, ep); + NODE *cref = rb_vm_get_cref(iseq, ep); VALUE klass = Qundef; while (cref) { @@ -1175,7 +1181,7 @@ vm_get_cbase(const rb_iseq_t *iseq, const VALUE *ep) static inline VALUE vm_get_const_base(const rb_iseq_t *iseq, const VALUE *ep) { - NODE *cref = vm_get_cref(iseq, ep); + NODE *cref = rb_vm_get_cref(iseq, ep); VALUE klass = Qundef; while (cref) { @@ -1201,6 +1207,20 @@ vm_check_if_namespace(VALUE klass) } static inline VALUE +vm_get_iclass(rb_control_frame_t *cfp, VALUE klass) +{ + if (TYPE(klass) == T_MODULE && + FL_TEST(klass, RMODULE_IS_OVERLAYED) && + TYPE(cfp->klass) == T_ICLASS && + RBASIC(cfp->klass)->klass == klass) { + return cfp->klass; + } + else { + return klass; + } +} + +static inline VALUE vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, VALUE orig_klass, ID id, int is_defined) { @@ -1208,7 +1228,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, if (orig_klass == Qnil) { /* in current lexical scope */ - const NODE *root_cref = vm_get_cref(iseq, th->cfp->ep); + const NODE *root_cref = rb_vm_get_cref(iseq, th->cfp->ep); const NODE *cref; VALUE klass = orig_klass; @@ -1254,7 +1274,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, /* search self */ if (root_cref && !NIL_P(root_cref->nd_clss)) { - klass = root_cref->nd_clss; + klass = vm_get_iclass(th->cfp, root_cref->nd_clss); } else { klass = CLASS_OF(th->cfp->self); @@ -1279,7 +1299,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, } static inline VALUE -vm_get_cvar_base(NODE *cref) +vm_get_cvar_base(NODE *cref, rb_control_frame_t *cfp) { VALUE klass; @@ -1296,7 +1316,7 @@ vm_get_cvar_base(NODE *cref) rb_warn("class variable access from toplevel"); } - klass = cref->nd_clss; + klass = vm_get_iclass(cfp, cref->nd_clss); if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class variables available"); @@ -1805,3 +1825,43 @@ opt_eq_func(VALUE recv, VALUE obj, IC ic) return Qundef; } +void rb_using_module(NODE *cref, VALUE module); + +static int +vm_using_module_i(VALUE module, VALUE value, VALUE arg) +{ + NODE *cref = (NODE *) arg; + + rb_using_module(cref, module); + return ST_CONTINUE; +} + +static void +rb_vm_using_modules(NODE *cref, VALUE klass) +{ + ID id_using_modules; + VALUE using_modules; + + CONST_ID(id_using_modules, "__using_modules__"); + using_modules = rb_attr_get(klass, id_using_modules); + switch (TYPE(klass)) { + case T_CLASS: + if (NIL_P(using_modules)) { + VALUE super = rb_class_real(RCLASS_SUPER(klass)); + using_modules = rb_attr_get(super, id_using_modules); + if (!NIL_P(using_modules)) { + using_modules = rb_hash_dup(using_modules); + rb_ivar_set(klass, id_using_modules, using_modules); + } + } + break; + case T_MODULE: + rb_using_module(cref, klass); + break; + } + if (!NIL_P(using_modules)) { + rb_hash_foreach(using_modules, vm_using_module_i, + (VALUE) cref); + } +} + diff --git a/vm_insnhelper.h b/vm_insnhelper.h index fdaa818e6c..d78a84c3dc 100644 --- a/vm_insnhelper.h +++ b/vm_insnhelper.h @@ -160,8 +160,17 @@ enum vm_regan_acttype { /* deal with control flow 2: method/iterator */ /**********************************************************/ +#define COPY_CREF_OMOD(c1, c2) do { \ + (c1)->nd_omod = (c2)->nd_omod; \ + if (!NIL_P((c2)->nd_omod)) { \ + (c1)->flags |= NODE_FL_CREF_OMOD_SHARED; \ + (c2)->flags |= NODE_FL_CREF_OMOD_SHARED; \ + } \ +} while (0) + #define COPY_CREF(c1, c2) do { \ NODE *__tmp_c2 = (c2); \ + COPY_CREF_OMOD(c1, __tmp_c2); \ (c1)->nd_clss = __tmp_c2->nd_clss; \ (c1)->nd_visi = __tmp_c2->nd_visi;\ (c1)->nd_next = __tmp_c2->nd_next; \ diff --git a/vm_method.c b/vm_method.c index 9acded6280..13174dd38d 100644 --- a/vm_method.c +++ b/vm_method.c @@ -4,7 +4,7 @@ #define CACHE_SIZE 0x800 #define CACHE_MASK 0x7ff -#define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) +#define EXPR1(c,o,m) ((((c)>>3)^((o)>>3)^(m))&CACHE_MASK) #define NOEX_NOREDEF 0 #ifndef NOEX_NOREDEF @@ -21,6 +21,7 @@ struct cache_entry { /* method hash table. */ VALUE filled_version; /* filled state version */ ID mid; /* method's id */ VALUE klass; /* receiver's class */ + VALUE omod; /* overlay modules */ rb_method_entry_t *me; VALUE defined_class; }; @@ -385,12 +386,20 @@ rb_get_alloc_func(VALUE klass) } static rb_method_entry_t* -search_method(VALUE klass, ID id, VALUE *defined_class_ptr) +search_method(VALUE klass, ID id, VALUE omod, VALUE *defined_class_ptr) { st_data_t body; + VALUE iclass, skipped_class = Qnil; for (body = 0; klass; klass = RCLASS_SUPER(klass)) { - st_table *m_tbl = RCLASS_M_TBL(klass); + st_table *m_tbl; + + if (!NIL_P(omod) && klass != skipped_class && + !NIL_P(iclass = rb_hash_lookup(omod, klass))) { + skipped_class = klass; + klass = iclass; + } + m_tbl = RCLASS_M_TBL(klass); if (!m_tbl) { m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(RBASIC(klass)->klass)); } @@ -409,17 +418,18 @@ search_method(VALUE klass, ID id, VALUE *defined_class_ptr) * rb_method_entry() simply. */ rb_method_entry_t * -rb_method_entry_get_without_cache(VALUE klass, ID id, +rb_method_entry_get_without_cache(VALUE klass, VALUE omod, ID id, VALUE *defined_class_ptr) { VALUE defined_class; - rb_method_entry_t *me = search_method(klass, id, &defined_class); + rb_method_entry_t *me = search_method(klass, id, omod, &defined_class); if (ruby_running) { struct cache_entry *ent; - ent = cache + EXPR1(klass, id); + ent = cache + EXPR1(klass, omod, id); ent->filled_version = GET_VM_STATE_VERSION(); ent->klass = klass; + ent->omod = omod; ent->defined_class = defined_class; if (UNDEFINED_METHOD_ENTRY_P(me)) { @@ -439,19 +449,33 @@ rb_method_entry_get_without_cache(VALUE klass, ID id, } rb_method_entry_t * -rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr) +rb_method_entry_get_with_omod(VALUE omod, VALUE klass, ID id, + VALUE *defined_class_ptr) { struct cache_entry *ent; - ent = cache + EXPR1(klass, id); + ent = cache + EXPR1(klass, omod, id); if (ent->filled_version == GET_VM_STATE_VERSION() && - ent->mid == id && ent->klass == klass) { + ent->mid == id && ent->klass == klass && ent->omod == omod) { if (defined_class_ptr) *defined_class_ptr = ent->defined_class; return ent->me; } - return rb_method_entry_get_without_cache(klass, id, defined_class_ptr); + return rb_method_entry_get_without_cache(klass, omod, id, + defined_class_ptr); +} + +rb_method_entry_t * +rb_method_entry(VALUE klass, ID id, VALUE *defined_class_ptr) +{ + NODE *cref = rb_vm_cref(); + VALUE omod = Qnil; + + if (cref && !NIL_P(cref->nd_omod)) { + omod = cref->nd_omod; + } + return rb_method_entry_get_with_omod(omod, klass, id, defined_class_ptr); } static void @@ -550,9 +574,9 @@ rb_export_method(VALUE klass, ID name, rb_method_flag_t noex) rb_secure(4); } - me = search_method(klass, name, &defined_class); + me = search_method(klass, name, Qnil, &defined_class); if (!me && RB_TYPE_P(klass, T_MODULE)) { - me = search_method(rb_cObject, name, &defined_class); + me = search_method(rb_cObject, name, Qnil, &defined_class); } if (UNDEFINED_METHOD_ENTRY_P(me)) { @@ -640,6 +664,9 @@ void rb_undef(VALUE klass, ID id) { rb_method_entry_t *me; + NODE *cref = rb_vm_cref(); + VALUE omod = Qnil; + void rb_overlay_module(NODE *cref, VALUE klass, VALUE module); if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class to undef method"); @@ -655,7 +682,10 @@ rb_undef(VALUE klass, ID id) rb_warn("undefining `%s' may cause serious problems", rb_id2name(id)); } - me = search_method(klass, id, 0); + if (cref && !NIL_P(cref->nd_omod)) { + omod = cref->nd_omod; + } + me = search_method(klass, id, omod, 0); if (UNDEFINED_METHOD_ENTRY_P(me)) { const char *s0 = " class"; @@ -676,6 +706,11 @@ rb_undef(VALUE klass, ID id) rb_id2name(id), s0, rb_class2name(c)); } + if (!RTEST(rb_class_inherited_p(klass, me->klass))) { + VALUE mod = rb_module_new(); + rb_overlay_module(cref, klass, mod); + klass = mod; + } rb_add_method(klass, id, VM_METHOD_TYPE_UNDEF, 0, NOEX_PUBLIC); CALL_METHOD_HOOK(klass, undefined, id); @@ -980,11 +1015,11 @@ rb_alias(VALUE klass, ID name, ID def) } again: - orig_me = search_method(klass, def, 0); + orig_me = search_method(klass, def, Qnil, 0); if (UNDEFINED_METHOD_ENTRY_P(orig_me)) { if ((!RB_TYPE_P(klass, T_MODULE)) || - (orig_me = search_method(rb_cObject, def, 0), + (orig_me = search_method(rb_cObject, def, Qnil, 0), UNDEFINED_METHOD_ENTRY_P(orig_me))) { rb_print_undef(klass, def, 0); } @@ -1260,9 +1295,9 @@ rb_mod_modfunc(int argc, VALUE *argv, VALUE module) id = rb_to_id(argv[i]); for (;;) { - me = search_method(m, id, 0); + me = search_method(m, id, Qnil, 0); if (me == 0) { - me = search_method(rb_cObject, id, 0); + me = search_method(rb_cObject, id, Qnil, 0); } if (UNDEFINED_METHOD_ENTRY_P(me)) { rb_print_undef(module, id, 0); @@ -1376,6 +1411,20 @@ obj_respond_to_missing(VALUE obj, VALUE mid, VALUE priv) } void +rb_redefine_opt_method(VALUE klass, ID mid) +{ + st_data_t data; + rb_method_entry_t *me = 0; + + if (!st_lookup(RCLASS_M_TBL(klass), mid, &data) || + !(me = (rb_method_entry_t *)data) || + (!me->def || me->def->type == VM_METHOD_TYPE_UNDEF)) { + return; + } + rb_vm_check_redefinition_opt_method(me, klass); +} + +void Init_eval_method(void) { #undef rb_intern |