diff --git a/ChangeLog b/ChangeLog index b80c4a5e48..a9e90e0d52 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Wed Jun 1 01:16:02 2011 Nobuyoshi Nakada + + * class.c (rb_mix_module): implement Module#mix. + Wed Jun 1 01:15:12 2011 Nobuyoshi Nakada * io.c (io_encoding_set): should honor already set ecflags since it diff --git a/class.c b/class.c index 8479bace3f..2a2b3ef131 100644 --- a/class.c +++ b/class.c @@ -120,27 +120,28 @@ rb_class_new(VALUE super) return rb_class_boot(super); } -struct clone_method_data { - st_table *tbl; - VALUE klass; -}; - VALUE rb_iseq_clone(VALUE iseqval, VALUE newcbase); -static int -clone_method(ID mid, const rb_method_entry_t *me, struct clone_method_data *data) +static void +rb_mod_clone_method(VALUE klass, ID mid, const rb_method_entry_t *me) { VALUE newiseqval; if (me->def && me->def->type == VM_METHOD_TYPE_ISEQ) { rb_iseq_t *iseq; - newiseqval = rb_iseq_clone(me->def->body.iseq->self, data->klass); + newiseqval = rb_iseq_clone(me->def->body.iseq->self, klass); GetISeqPtr(newiseqval, iseq); - rb_add_method(data->klass, mid, VM_METHOD_TYPE_ISEQ, iseq, me->flag); + rb_add_method(klass, mid, VM_METHOD_TYPE_ISEQ, iseq, me->flag); RB_GC_GUARD(newiseqval); } else { - rb_method_entry_set(data->klass, mid, me, me->flag); + rb_method_entry_set(klass, mid, me, me->flag); } +} + +static int +clone_method_i(st_data_t key, st_data_t value, st_data_t data) +{ + rb_mod_clone_method((VALUE)data, (ID)key, (const rb_method_entry_t *)value); return ST_CONTINUE; } @@ -183,15 +184,11 @@ rb_mod_init_copy(VALUE clone, VALUE orig) st_foreach(RCLASS_CONST_TBL(orig), clone_const, (st_data_t)RCLASS_CONST_TBL(clone)); } if (RCLASS_M_TBL(orig)) { - struct clone_method_data data; - if (RCLASS_M_TBL(clone)) { rb_free_m_table(RCLASS_M_TBL(clone)); } - data.tbl = RCLASS_M_TBL(clone) = st_init_numtable(); - data.klass = clone; - st_foreach(RCLASS_M_TBL(orig), clone_method, - (st_data_t)&data); + RCLASS_M_TBL(clone) = st_init_numtable(); + st_foreach(RCLASS_M_TBL(orig), clone_method_i, (st_data_t)clone); } return clone; @@ -221,12 +218,11 @@ rb_singleton_class_clone(VALUE obj) if (!FL_TEST(klass, FL_SINGLETON)) return klass; else { - struct clone_method_data data; /* copy singleton(unnamed) class */ VALUE clone = class_alloc((RBASIC(klass)->flags & ~(FL_MARK)), 0); if (BUILTIN_TYPE(obj) == T_CLASS) { - RBASIC(clone)->klass = (VALUE)clone; + RBASIC(clone)->klass = clone; } else { RBASIC(clone)->klass = rb_singleton_class_clone(klass); @@ -241,13 +237,10 @@ rb_singleton_class_clone(VALUE obj) st_foreach(RCLASS_CONST_TBL(klass), clone_const, (st_data_t)RCLASS_CONST_TBL(clone)); } RCLASS_M_TBL(clone) = st_init_numtable(); - data.tbl = RCLASS_M_TBL(clone); - data.klass = (VALUE)clone; - st_foreach(RCLASS_M_TBL(klass), clone_method, - (st_data_t)&data); - rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone); + st_foreach(RCLASS_M_TBL(klass), clone_method_i, (st_data_t)clone); + rb_singleton_class_attached(RBASIC(clone)->klass, clone); FL_SET(clone, FL_SINGLETON); - return (VALUE)clone; + return clone; } } @@ -697,6 +690,134 @@ rb_include_module(VALUE klass, VALUE module) if (changed) rb_clear_cache(); } +struct mixing_arg { + st_table *mtbl; + ID id; + st_table *aliasing; + VALUE klass; +}; + +static int +check_mix_const_i(st_data_t key, st_data_t value, st_data_t arg) +{ + struct mixing_arg *argp = (struct mixing_arg *)arg; + ID id = (ID)key; + st_table *aliasing = argp->aliasing; + st_data_t alias; + + if (!rb_is_const_id(id)) return ST_CONTINUE; + if (aliasing && st_lookup(aliasing, ID2SYM(id), &alias)) { + id = rb_to_id(alias); + } + if (st_lookup(argp->mtbl, id, NULL)) { + argp->id = id; + return ST_STOP; + } + return ST_CONTINUE; +} + +static int +do_mix_const_i(st_data_t key, st_data_t value, st_data_t arg) +{ + struct mixing_arg *argp = (struct mixing_arg *)arg; + ID id = (ID)key; + st_table *aliasing = argp->aliasing; + st_data_t old, alias; + + if (!rb_is_const_id(id)) return ST_CONTINUE; + if (aliasing && st_lookup(aliasing, ID2SYM(id), &alias)) { + id = rb_to_id(alias); + } + if (st_lookup(argp->mtbl, id, &old)) { + argp->id = id; + return ST_STOP; + } + st_insert(argp->mtbl, id, value); + return ST_CONTINUE; +} + +static int +check_mix_method_i(st_data_t key, st_data_t value, st_data_t arg) +{ + struct mixing_arg *argp = (struct mixing_arg *)arg; + ID id = (ID)key; + st_table *aliasing = argp->aliasing; + st_data_t alias; + + if (aliasing && st_lookup(aliasing, ID2SYM(id), &alias)) { + id = rb_to_id(alias); + } + if (st_lookup(argp->mtbl, id, NULL)) { + argp->id = id; + return ST_STOP; + } + return ST_CONTINUE; +} + +static int +do_mix_method_i(st_data_t key, st_data_t value, st_data_t arg) +{ + struct mixing_arg *argp = (struct mixing_arg *)arg; + ID id = (ID)key; + st_table *aliasing = argp->aliasing; + st_data_t old, alias; + + if (aliasing && st_lookup(aliasing, ID2SYM(id), &alias)) { + id = rb_to_id(alias); + } + if (st_lookup(argp->mtbl, id, &old)) { + argp->id = id; + return ST_STOP; + } + rb_mod_clone_method(argp->klass, id, (rb_method_entry_t *)value); + return ST_CONTINUE; +} + +void +rb_mix_module(VALUE klass, VALUE module, st_table *constants, st_table *methods) +{ + st_table *mtbl_from; + struct mixing_arg methodarg, constarg; + + rb_frozen_class_p(klass); + if (!OBJ_UNTRUSTED(klass)) { + rb_secure(4); + } + + if (TYPE(module) != T_MODULE) { + Check_Type(module, T_MODULE); + } + + OBJ_INFECT(klass, module); + + mtbl_from = RMODULE_M_TBL(module); + methodarg.mtbl = RMODULE_M_TBL(klass); + methodarg.id = 0; + methodarg.aliasing = methods; + methodarg.klass = klass; + constarg.mtbl = RMODULE_IV_TBL(klass); + constarg.id = 0; + constarg.aliasing = constants; + + st_foreach(mtbl_from, check_mix_method_i, (st_data_t)&methodarg); + if (methodarg.id) { + rb_raise(rb_eArgError, "method would conflict - %s", rb_id2name(methodarg.id)); + } + st_foreach(mtbl_from, check_mix_const_i, (st_data_t)&constarg); + if (constarg.id) { + rb_raise(rb_eArgError, "constant would conflict - %s", rb_id2name(constarg.id)); + } + st_foreach(mtbl_from, do_mix_method_i, (st_data_t)&methodarg); + if (methodarg.id) { + rb_raise(rb_eArgError, "method would conflict - %s", rb_id2name(methodarg.id)); + } + st_foreach(mtbl_from, do_mix_const_i, (st_data_t)&constarg); + if (constarg.id) { + rb_raise(rb_eArgError, "constant would conflict - %s", rb_id2name(constarg.id)); + } + rb_vm_inc_const_missing_count(); +} + /* * call-seq: * mod.included_modules -> array diff --git a/eval.c b/eval.c index d5f2c4cc1d..5831ca251d 100644 --- a/eval.c +++ b/eval.c @@ -880,6 +880,69 @@ rb_mod_include(int argc, VALUE *argv, VALUE module) return module; } +/* + * call-seq: + * mix(module, ...) -> module + * + * Mix +Module+> into self. + */ + +static VALUE +rb_mod_mix_into(int argc, VALUE *argv, VALUE klass) +{ + VALUE module, tmp, constants = Qnil, methods = Qnil; + st_table *const_tbl = 0, *method_tbl = 0; + int i = 0; + + if (argc < 1 || argc > 3) { + wrong_args: + rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); + } + module = argv[i++]; + + switch (TYPE(module)) { + case T_CLASS: + case T_MODULE: + break; + default: + Check_Type(module, T_CLASS); + break; + } + if (i < argc) { + constants = argv[i++]; + if (!NIL_P(tmp = rb_check_array_type(constants))) { + constants = tmp; + } + else if (!NIL_P(methods = rb_check_hash_type(constants))) { + constants = Qnil; + } + else { + Check_Type(constants, T_HASH); + } + } + if (i < argc && NIL_P(methods)) { + methods = argv[i++]; + if (NIL_P(tmp = rb_check_hash_type(methods))) { + Check_Type(methods, T_HASH); + } + methods = tmp; + } + if (i < argc) goto wrong_args; + if (!NIL_P(constants)) { + VALUE hash = rb_hash_new(); + for (i = 0; i < RARRAY_LEN(constants); ++i) { + rb_hash_update_by(hash, RARRAY_PTR(constants)[i], NULL); + } + const_tbl = RHASH_TBL(RB_GC_GUARD(constants) = hash); + } + if (!NIL_P(methods)) { + method_tbl = RHASH_TBL(RB_GC_GUARD(methods)); + } + + rb_mix_module(klass, module, const_tbl, method_tbl); + return module; +} + void rb_obj_call_init(VALUE obj, int argc, VALUE *argv) { @@ -1144,6 +1207,7 @@ 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, "mix", rb_mod_mix_into, -1); rb_undef_method(rb_cClass, "module_function"); diff --git a/method.h b/method.h index 61555b079f..339f6e452c 100644 --- a/method.h +++ b/method.h @@ -100,5 +100,6 @@ void rb_mark_method_entry(const rb_method_entry_t *me); void rb_free_method_entry(rb_method_entry_t *me); void rb_sweep_method_entry(void *vm); void rb_free_m_table(st_table *tbl); +void rb_mix_module(VALUE klass, VALUE module, struct st_table *constants, struct st_table *methods); #endif /* METHOD_H */ diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 0dcfeccef9..ec29530f8e 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -1068,4 +1068,33 @@ class TestModule < Test::Unit::TestCase INPUT assert_in_out_err([], src, ["NameError"], []) end + + def test_mix + american = Module.new do + attr_accessor :address + end + japanese = Module.new do + attr_accessor :address + end + + japanese_american = Class.new + assert_nothing_raised(ArgumentError) { + japanese_american.class_eval {mix american} + } + assert_raise(ArgumentError) { + japanese_american.class_eval {mix japanese} + } + + japanese_american = Class.new + assert_nothing_raised(ArgumentError) { + japanese_american.class_eval { + mix american, :address => :us_address, :address= => :us_address= + } + } + assert_nothing_raised(ArgumentError) { + japanese_american.class_eval { + mix japanese, :address => :jp_address, :address= => :jp_address= + } + } + end end