From 8ddbbb33242f413845079b0274c28331cb4aa0f5 Mon Sep 17 00:00:00 2001 From: nobu Date: Wed, 27 Jun 2012 07:48:50 +0000 Subject: [PATCH] Module#prepend * class.c (rb_prepend_module): prepend module into another module. * eval.c (rb_mod_prepend): new method Module#prepend. [Feature #1102] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36234 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 6 +++++ class.c | 42 +++++++++++++++++++++++++++++++ eval.c | 54 ++++++++++++++++++++++++++++++++++++++++ include/ruby/ruby.h | 1 + internal.h | 2 ++ object.c | 1 + test/ruby/test_module.rb | 32 ++++++++++++++++++++++++ vm_insnhelper.c | 1 + vm_method.c | 11 ++++++-- 9 files changed, 148 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8145a70a17f852..310f6250a35723 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Wed Jun 27 16:48:48 2012 Nobuyoshi Nakada + + * class.c (rb_prepend_module): prepend module into another module. + + * eval.c (rb_mod_prepend): new method Module#prepend. [Feature #1102] + Wed Jun 27 09:15:46 2012 Nobuyoshi Nakada * io.c (is_popen_fork): check if fork and raise NotImplementedError if diff --git a/class.c b/class.c index daaf4014979548..fc3b483735aa10 100644 --- a/class.c +++ b/class.c @@ -56,6 +56,7 @@ class_alloc(VALUE flags, VALUE klass) RCLASS_CONST_TBL(obj) = 0; RCLASS_M_TBL(obj) = 0; RCLASS_SUPER(obj) = 0; + RCLASS_ORIGIN(obj) = (VALUE)obj; RCLASS_IV_INDEX_TBL(obj) = 0; return (VALUE)obj; } @@ -687,6 +688,8 @@ rb_include_module(VALUE klass, VALUE module) break; } } + if (c == klass) + c = RCLASS_ORIGIN(klass); c = RCLASS_SUPER(c) = include_class_new(module, RCLASS_SUPER(c)); if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) changed = 1; @@ -696,6 +699,45 @@ rb_include_module(VALUE klass, VALUE module) if (changed) rb_clear_cache(); } +void +rb_prepend_module(VALUE klass, VALUE module) +{ + VALUE p, c, origin; + + rb_frozen_class_p(klass); + if (!OBJ_UNTRUSTED(klass)) { + rb_secure(4); + } + + Check_Type(module, T_MODULE); + + OBJ_INFECT(klass, module); + c = RCLASS_SUPER(klass); + if (RCLASS_M_TBL(klass) == RCLASS_M_TBL(module)) + rb_raise(rb_eArgError, "cyclic include detected"); + for (p = c; p; p = RCLASS_SUPER(p)) { + if (BUILTIN_TYPE(p) == T_ICLASS) { + if (RCLASS_M_TBL(p) == RCLASS_M_TBL(module)) { + rb_raise(rb_eArgError, "already prepended module"); + } + } + } + origin = RCLASS_ORIGIN(klass); + if (origin == klass) { + origin = class_alloc(T_ICLASS, rb_cClass); + RCLASS_SUPER(origin) = RCLASS_SUPER(klass); + RCLASS_SUPER(klass) = origin; + RCLASS_ORIGIN(klass) = origin; + RCLASS_M_TBL(origin) = RCLASS_M_TBL(klass); + RCLASS_M_TBL(klass) = 0; + c = origin; + } + RCLASS_SUPER(klass) = include_class_new(module, c); + if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries) { + rb_clear_cache_by_class(klass); + } +} + /* * call-seq: * mod.included_modules -> array diff --git a/eval.c b/eval.c index 1203a20eccf6aa..37bb35a148280e 100644 --- a/eval.c +++ b/eval.c @@ -954,6 +954,58 @@ rb_mod_include(int argc, VALUE *argv, VALUE module) return module; } +/* + * call-seq: + * prepend_features(mod) -> mod + * + * When this module is prepended in another, Ruby calls + * prepend_features in this module, passing it the + * receiving module in _mod_. Ruby's default implementation is + * to overlay the constants, methods, and module variables of this module + * to _mod_ if this module has not already been added to + * _mod_ or one of its ancestors. See also Module#prepend. + */ + +static VALUE +rb_mod_prepend_features(VALUE module, VALUE prepend) +{ + switch (TYPE(prepend)) { + case T_CLASS: + case T_MODULE: + break; + default: + Check_Type(prepend, T_CLASS); + break; + } + rb_prepend_module(prepend, module); + + return module; +} + +/* + * call-seq: + * prepend(module, ...) -> self + * + * Invokes Module.prepend_features on each parameter in reverse order. + */ + +static VALUE +rb_mod_prepend(int argc, VALUE *argv, VALUE module) +{ + int i; + ID id_prepend_features, id_prepended; + + CONST_ID(id_prepend_features, "prepend_features"); + CONST_ID(id_prepended, "prepended"); + for (i = 0; i < argc; i++) + Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], id_prepend_features, 1, module); + rb_funcall(argv[argc], id_prepended, 1, module); + } + return module; +} + void rb_obj_call_init(VALUE obj, int argc, VALUE *argv) { @@ -1213,6 +1265,8 @@ Init_eval(void) rb_define_private_method(rb_cModule, "append_features", rb_mod_append_features, 1); rb_define_private_method(rb_cModule, "extend_object", rb_mod_extend_object, 1); 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_undef_method(rb_cClass, "module_function"); diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 626f283ed2b91c..23ba7535f2cacc 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1080,6 +1080,7 @@ VALUE rb_define_module_under(VALUE, const char*); void rb_include_module(VALUE,VALUE); void rb_extend_object(VALUE,VALUE); +void rb_prepend_module(VALUE,VALUE); struct rb_global_variable; diff --git a/internal.h b/internal.h index 51d71d7cabb423..77e6c2e191dc8b 100644 --- a/internal.h +++ b/internal.h @@ -27,6 +27,7 @@ struct rb_classext_struct { VALUE super; struct st_table *iv_tbl; struct st_table *const_tbl; + VALUE origin; }; #undef RCLASS_SUPER @@ -36,6 +37,7 @@ struct rb_classext_struct { #define RCLASS_CONST_TBL(c) (RCLASS_EXT(c)->const_tbl) #define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) #define RCLASS_IV_INDEX_TBL(c) (RCLASS(c)->iv_index_tbl) +#define RCLASS_ORIGIN(c) (RCLASS_EXT(c)->origin) struct vtm; /* defined by timev.h */ diff --git a/object.c b/object.c index ac8fa531043baf..6e42043a55bd90 100644 --- a/object.c +++ b/object.c @@ -2862,6 +2862,7 @@ Init_Object(void) rb_define_private_method(rb_cClass, "inherited", rb_obj_dummy, 1); 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, "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_module.rb b/test/ruby/test_module.rb index b24fa3222d0b87..decb217289099a 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -1238,4 +1238,36 @@ module C INPUT assert_in_out_err([], src, ["NameError"], []) end + + module M0 + def m1; [:M0] end + end + module M1 + def m1; [:M1, super, :M1] end + end + module M2 + def m1; [:M2, super, :M2] end + end + M3 = Module.new do + def m1; [:M3, super, :M3] end + end + module M4 + def m1; [:M4, super, :M4] end + end + class C0 + include M0 + prepend M1 + def m1; [:C0, super, :C0] end + end + class C1 < C0 + prepend M2, M3 + include M4 + def m1; [:C1, super, :C1] end + end + + def test_prepend + obj = C1.new + expected = [:M2,[:M3,[:C1,[:M4,[:M1,[:C0,[:M0],:C0],:M1],:M4],:C1],:M3],:M2] + assert_equal(expected, obj.m1) + end end diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 2e00c0e1de81d8..6544ed89fc75aa 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1425,6 +1425,7 @@ static inline VALUE vm_search_normal_superclass(VALUE klass, VALUE recv) { if (BUILTIN_TYPE(klass) == T_CLASS) { + klass = RCLASS_ORIGIN(klass); return RCLASS_SUPER(klass); } else if (BUILTIN_TYPE(klass) == T_MODULE) { diff --git a/vm_method.c b/vm_method.c index c9f0130512b370..5a7722cd672a63 100644 --- a/vm_method.c +++ b/vm_method.c @@ -169,6 +169,9 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, rb_method_definition_t *def, rb_method_flag_t noex) { rb_method_entry_t *me; +#if NOEX_NOREDEF + VALUE rklass; +#endif st_table *mtbl; st_data_t data; @@ -194,6 +197,10 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, } rb_check_frozen(klass); +#if NOEX_NOREDEF + rklass = klass; +#endif + klass = RCLASS_ORIGIN(klass); mtbl = RCLASS_M_TBL(klass); /* check re-definition */ @@ -205,7 +212,7 @@ rb_method_entry_make(VALUE klass, ID mid, rb_method_type_t type, #if NOEX_NOREDEF if (old_me->flag & NOEX_NOREDEF) { rb_raise(rb_eTypeError, "cannot redefine %"PRIsVALUE"#%"PRIsVALUE, - rb_class_name(klass), rb_id2str(mid)); + rb_class_name(rklass), rb_id2str(mid)); } #endif rb_vm_check_redefinition_opt_method(old_me, klass); @@ -384,7 +391,7 @@ search_method(VALUE klass, ID id) return 0; } - while (!st_lookup(RCLASS_M_TBL(klass), id, &body)) { + while (!RCLASS_M_TBL(klass) || !st_lookup(RCLASS_M_TBL(klass), id, &body)) { klass = RCLASS_SUPER(klass); if (!klass) { return 0;