Skip to content

Commit

Permalink
Make Module#include and Module#prepend behave like Ruby3.0.
Browse files Browse the repository at this point in the history
Module#include and Module#prepend now affect classes and modules
that have already included or prepended the receiver, mirroring the
behavior if the arguments were included in the receiver before
the other modules and classes included or prepended the receiver.

```ruby
class C; end
module M1; end
module M2; end
C.include M1
M1.include M2
p C.ancestors #=> [C, M1, M2, Object, Kernel, BasicObject]
```
  • Loading branch information
matz committed Dec 4, 2020
1 parent 4cd3493 commit 3972df5
Showing 1 changed file with 57 additions and 6 deletions.
63 changes: 57 additions & 6 deletions src/class.c
Expand Up @@ -1406,7 +1406,6 @@ include_module_at(mrb_state *mrb, struct RClass *c, struct RClass *ins_pos, stru
m->flags |= MRB_FL_CLASS_IS_INHERITED;
ins_pos->super = ic;
mrb_field_write_barrier(mrb, (struct RBasic*)ins_pos, (struct RBasic*)ic);
mrb_mc_clear_by_class(mrb, ins_pos);
ins_pos = ic;
skip:
m = m->super;
Expand All @@ -1415,24 +1414,71 @@ include_module_at(mrb_state *mrb, struct RClass *c, struct RClass *ins_pos, stru
return 0;
}

static int
fix_include_module(mrb_state *mrb, struct RBasic *obj, void *data)
{
struct RClass **m = (struct RClass**)data;

if (obj->tt == MRB_TT_ICLASS && obj->c == m[0] && (obj->flags & MRB_FL_CLASS_IS_ORIGIN) == 0) {
struct RClass *ic = (struct RClass*)obj;
include_module_at(mrb, ic, ic, m[1], 1);
}
return MRB_EACH_OBJ_OK;
}

MRB_API void
mrb_include_module(mrb_state *mrb, struct RClass *c, struct RClass *m)
{
mrb_check_frozen(mrb, c);
if (include_module_at(mrb, c, find_origin(c), m, 1) < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "cyclic include detected");
}
if (c->tt == MRB_TT_MODULE && (c->flags & MRB_FL_CLASS_IS_INHERITED)) {
struct RClass *data[2];
data[0] = c;
data[1] = m;
mrb_objspace_each_objects(mrb, fix_include_module, data);
}
}

static int
fix_prepend_module(mrb_state *mrb, struct RBasic *obj, void *data)
{
struct RClass **m = (struct RClass**)data;
struct RClass *c = (struct RClass*)obj;

if (c->tt == MRB_TT_CLASS || c->tt == MRB_TT_MODULE) {
struct RClass *p = c->super;
while (p) {
if (c == m[0]) break;
if (p->tt == MRB_TT_CLASS) break;
if (p->c == m[0]) {
include_module_at(mrb, c, c, m[1], 0);
break;
}
c = p;
p = p->super;
}
}
return MRB_EACH_OBJ_OK;
}

MRB_API void
mrb_prepend_module(mrb_state *mrb, struct RClass *c, struct RClass *m)
{
struct RClass *origin;
int changed = 0;

mrb_check_frozen(mrb, c);
if (!(c->flags & MRB_FL_CLASS_IS_PREPENDED)) {
origin = (struct RClass*)mrb_obj_alloc(mrb, MRB_TT_ICLASS, c);
struct RClass *c0;

if (c->tt == MRB_TT_ICLASS) {
c0 = c->c;
}
else {
c0 = c;
}
origin = (struct RClass*)mrb_obj_alloc(mrb, MRB_TT_ICLASS, c0);
origin->flags |= MRB_FL_CLASS_IS_ORIGIN | MRB_FL_CLASS_IS_INHERITED;
origin->super = c->super;
c->super = origin;
Expand All @@ -1441,10 +1487,16 @@ mrb_prepend_module(mrb_state *mrb, struct RClass *c, struct RClass *m)
mrb_field_write_barrier(mrb, (struct RBasic*)c, (struct RBasic*)origin);
c->flags |= MRB_FL_CLASS_IS_PREPENDED;
}
changed = include_module_at(mrb, c, c, m, 0);
if (changed < 0) {
if (include_module_at(mrb, c, c, m, 0) < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "cyclic prepend detected");
}
if (c->tt == MRB_TT_MODULE &&
(c->flags & (MRB_FL_CLASS_IS_INHERITED|MRB_FL_CLASS_IS_PREPENDED))) {
struct RClass *data[2];
data[0] = c;
data[1] = m;
mrb_objspace_each_objects(mrb, fix_prepend_module, data);
}
}

static mrb_value
Expand Down Expand Up @@ -1651,7 +1703,6 @@ mrb_mc_clear_by_class(mrb_state *mrb, struct RClass *c)

if (c->flags & MRB_FL_CLASS_IS_INHERITED) {
mc_clear(mrb);
c->flags &= ~MRB_FL_CLASS_IS_INHERITED;
return;
}
for (i=0; i<MRB_METHOD_CACHE_SIZE; i++) {
Expand Down

0 comments on commit 3972df5

Please sign in to comment.