зеркало из https://github.com/github/ruby.git
Make prepending a refined module after inclusion not break refinements
After the previous commit, this was still broken. The reason it was broken is that a refined module that hasn't been prepended to yet keeps the refined methods in the module's method table. When prepending, the module's method table is moved to the origin iclass, and then the refined methods are moved from the method table to a new method table in the module itself. Unfortunately, that means that if a class has included the module, prepending breaks the refinements, because when the methods are moved from the origin iclass method table to the module method table, they are removed from the method table from the iclass created when the module was included earlier. Fix this by always creating an origin class when including a module that has any refinements, even if the refinements are not currently used. I wasn't sure the best way to do that. The approach I choose was to use an object flag. The flag is set on the module when Module#refine is called, and if the flag is present when the module is included in another module or class, an origin iclass is created for the module. Fixes [Bug #13446]
This commit is contained in:
Родитель
5069c5f521
Коммит
a0579f3606
27
class.c
27
class.c
|
@ -890,6 +890,8 @@ add_refined_method_entry_i(ID key, VALUE value, void *data)
|
|||
return ID_TABLE_CONTINUE;
|
||||
}
|
||||
|
||||
static void ensure_origin(VALUE klass);
|
||||
|
||||
static int
|
||||
include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super)
|
||||
{
|
||||
|
@ -897,6 +899,10 @@ include_modules_at(const VALUE klass, VALUE c, VALUE module, int search_super)
|
|||
int method_changed = 0, constant_changed = 0;
|
||||
struct rb_id_table *const klass_m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(klass));
|
||||
|
||||
if (FL_TEST(module, RCLASS_REFINED_BY_ANY)) {
|
||||
ensure_origin(module);
|
||||
}
|
||||
|
||||
while (module) {
|
||||
int superclass_seen = FALSE;
|
||||
struct rb_id_table *tbl;
|
||||
|
@ -978,15 +984,10 @@ move_refined_method(ID key, VALUE value, void *data)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
rb_prepend_module(VALUE klass, VALUE module)
|
||||
static void
|
||||
ensure_origin(VALUE klass)
|
||||
{
|
||||
VALUE origin;
|
||||
int changed = 0;
|
||||
|
||||
ensure_includable(klass, module);
|
||||
|
||||
origin = RCLASS_ORIGIN(klass);
|
||||
VALUE origin = RCLASS_ORIGIN(klass);
|
||||
if (origin == klass) {
|
||||
origin = class_alloc(T_ICLASS, klass);
|
||||
OBJ_WB_UNPROTECT(origin); /* TODO: conservative shading. Need more survey. */
|
||||
|
@ -997,6 +998,16 @@ rb_prepend_module(VALUE klass, VALUE module)
|
|||
RCLASS_M_TBL_INIT(klass);
|
||||
rb_id_table_foreach(RCLASS_M_TBL(origin), move_refined_method, (void *)klass);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rb_prepend_module(VALUE klass, VALUE module)
|
||||
{
|
||||
VALUE origin;
|
||||
int changed = 0;
|
||||
|
||||
ensure_includable(klass, module);
|
||||
ensure_origin(klass);
|
||||
changed = include_modules_at(klass, klass, module, FALSE);
|
||||
if (changed < 0)
|
||||
rb_raise(rb_eArgError, "cyclic prepend detected");
|
||||
|
|
3
eval.c
3
eval.c
|
@ -1542,6 +1542,9 @@ rb_mod_refine(VALUE module, VALUE klass)
|
|||
}
|
||||
|
||||
ensure_class_or_module(klass);
|
||||
if (RB_TYPE_P(klass, T_MODULE)) {
|
||||
FL_SET(klass, RCLASS_REFINED_BY_ANY);
|
||||
}
|
||||
CONST_ID(id_refinements, "__refinements__");
|
||||
refinements = rb_attr_get(module, id_refinements);
|
||||
if (NIL_P(refinements)) {
|
||||
|
|
|
@ -1081,6 +1081,7 @@ int rb_singleton_class_internal_p(VALUE sklass);
|
|||
|
||||
#define RCLASS_CLONED FL_USER6
|
||||
#define RICLASS_IS_ORIGIN FL_USER5
|
||||
#define RCLASS_REFINED_BY_ANY FL_USER7
|
||||
|
||||
static inline void
|
||||
RCLASS_SET_ORIGIN(VALUE klass, VALUE origin)
|
||||
|
|
|
@ -2350,6 +2350,38 @@ class TestRefinement < Test::Unit::TestCase
|
|||
assert_equal("refine_method", Bug16242::X.new.hoge)
|
||||
end
|
||||
|
||||
module Bug13446
|
||||
module Enumerable
|
||||
def sum(*args)
|
||||
i = 0
|
||||
args.each { |arg| i += a }
|
||||
i
|
||||
end
|
||||
end
|
||||
|
||||
using Module.new {
|
||||
refine Enumerable do
|
||||
alias :orig_sum :sum
|
||||
end
|
||||
}
|
||||
|
||||
module Enumerable
|
||||
def sum(*args)
|
||||
orig_sum(*args)
|
||||
end
|
||||
end
|
||||
|
||||
class GenericEnumerable
|
||||
include Enumerable
|
||||
end
|
||||
|
||||
Enumerable.prepend(Module.new)
|
||||
end
|
||||
|
||||
def test_prepend_refined_module
|
||||
assert_equal(0, Bug13446::GenericEnumerable.new.sum)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def eval_using(mod, s)
|
||||
|
|
Загрузка…
Ссылка в новой задаче