* 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] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36596 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
319088e9c7
commit
19ddfc2483
@ -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>
|
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,
|
* class.c, insns.def, method.h, proc.c, vm.c, vm_core.h, vm_eval.c,
|
||||||
|
6
class.c
6
class.c
@ -620,8 +620,8 @@ rb_define_module_id_under(VALUE outer, ID id)
|
|||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
VALUE
|
||||||
include_class_new(VALUE module, VALUE super)
|
rb_include_class_new(VALUE module, VALUE super)
|
||||||
{
|
{
|
||||||
VALUE klass = class_alloc(T_ICLASS, rb_cClass);
|
VALUE klass = class_alloc(T_ICLASS, rb_cClass);
|
||||||
|
|
||||||
@ -703,7 +703,7 @@ include_modules_at(VALUE klass, VALUE c, VALUE module)
|
|||||||
break;
|
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)
|
if (RMODULE_M_TBL(module) && RMODULE_M_TBL(module)->num_entries)
|
||||||
changed = 1;
|
changed = 1;
|
||||||
skip:
|
skip:
|
||||||
|
197
eval.c
197
eval.c
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
NORETURN(void rb_raise_jump(VALUE));
|
NORETURN(void rb_raise_jump(VALUE));
|
||||||
|
|
||||||
|
NODE *rb_vm_get_cref(const rb_iseq_t *, const VALUE *);
|
||||||
|
|
||||||
VALUE rb_eLocalJumpError;
|
VALUE rb_eLocalJumpError;
|
||||||
VALUE rb_eSysStackError;
|
VALUE rb_eSysStackError;
|
||||||
|
|
||||||
@ -1024,6 +1026,180 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module)
|
|||||||
return module;
|
return 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
|
void
|
||||||
rb_obj_call_init(VALUE obj, int argc, VALUE *argv)
|
rb_obj_call_init(VALUE obj, int argc, VALUE *argv)
|
||||||
{
|
{
|
||||||
@ -1134,6 +1310,23 @@ top_include(int argc, VALUE *argv, VALUE self)
|
|||||||
return rb_mod_include(argc, argv, rb_cObject);
|
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 *
|
static VALUE *
|
||||||
errinfo_place(rb_thread_t *th)
|
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, "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_features", rb_mod_prepend_features, 1);
|
||||||
rb_define_private_method(rb_cModule, "prepend", rb_mod_prepend, -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");
|
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_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_method(rb_mKernel, "extend", rb_obj_extend, -1);
|
||||||
|
|
||||||
rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */
|
rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */
|
||||||
|
6
gc.c
6
gc.c
@ -1994,6 +1994,12 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr, int lev)
|
|||||||
ptr = (VALUE)obj->as.node.u2.node;
|
ptr = (VALUE)obj->as.node.u2.node;
|
||||||
goto again;
|
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 */
|
default: /* unlisted NODE */
|
||||||
if (is_pointer_to_heap(objspace, obj->as.node.u1.node)) {
|
if (is_pointer_to_heap(objspace, obj->as.node.u1.node)) {
|
||||||
gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev);
|
gc_mark(objspace, (VALUE)obj->as.node.u1.node, lev);
|
||||||
|
@ -173,6 +173,7 @@ VALUE rb_define_class_id_under(VALUE, ID, VALUE);
|
|||||||
VALUE rb_module_new(void);
|
VALUE rb_module_new(void);
|
||||||
VALUE rb_define_module_id(ID);
|
VALUE rb_define_module_id(ID);
|
||||||
VALUE rb_define_module_id_under(VALUE, 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_included_modules(VALUE);
|
||||||
VALUE rb_mod_include_p(VALUE, VALUE);
|
VALUE rb_mod_include_p(VALUE, VALUE);
|
||||||
VALUE rb_mod_ancestors(VALUE);
|
VALUE rb_mod_ancestors(VALUE);
|
||||||
|
@ -655,6 +655,7 @@ struct RClass {
|
|||||||
#define RMODULE_CONST_TBL(m) RCLASS_CONST_TBL(m)
|
#define RMODULE_CONST_TBL(m) RCLASS_CONST_TBL(m)
|
||||||
#define RMODULE_M_TBL(m) RCLASS_M_TBL(m)
|
#define RMODULE_M_TBL(m) RCLASS_M_TBL(m)
|
||||||
#define RMODULE_SUPER(m) RCLASS_SUPER(m)
|
#define RMODULE_SUPER(m) RCLASS_SUPER(m)
|
||||||
|
#define RMODULE_IS_OVERLAYED FL_USER2
|
||||||
|
|
||||||
struct RFloat {
|
struct RFloat {
|
||||||
struct RBasic basic;
|
struct RBasic basic;
|
||||||
|
16
insns.def
16
insns.def
@ -183,8 +183,8 @@ getclassvariable
|
|||||||
()
|
()
|
||||||
(VALUE val)
|
(VALUE val)
|
||||||
{
|
{
|
||||||
NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP());
|
NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP());
|
||||||
val = rb_cvar_get(vm_get_cvar_base(cref), id);
|
val = rb_cvar_get(vm_get_cvar_base(cref, GET_CFP()), id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,8 +198,8 @@ setclassvariable
|
|||||||
(VALUE val)
|
(VALUE val)
|
||||||
()
|
()
|
||||||
{
|
{
|
||||||
NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP());
|
NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP());
|
||||||
rb_cvar_set(vm_get_cvar_base(cref), id, val);
|
rb_cvar_set(vm_get_cvar_base(cref, GET_CFP()), id, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -777,8 +777,8 @@ defined
|
|||||||
break;
|
break;
|
||||||
case DEFINED_CVAR:
|
case DEFINED_CVAR:
|
||||||
{
|
{
|
||||||
NODE *cref = vm_get_cref(GET_ISEQ(), GET_EP());
|
NODE *cref = rb_vm_get_cref(GET_ISEQ(), GET_EP());
|
||||||
klass = vm_get_cvar_base(cref);
|
klass = vm_get_cvar_base(cref, GET_CFP());
|
||||||
if (rb_cvar_defined(klass, SYM2ID(obj))) {
|
if (rb_cvar_defined(klass, SYM2ID(obj))) {
|
||||||
expr_type = "class variable";
|
expr_type = "class variable";
|
||||||
}
|
}
|
||||||
@ -970,6 +970,7 @@ defineclass
|
|||||||
klass, 0, VM_ENVVAL_BLOCK_PTR(GET_BLOCK_PTR()),
|
klass, 0, VM_ENVVAL_BLOCK_PTR(GET_BLOCK_PTR()),
|
||||||
class_iseq->iseq_encoded, GET_SP(),
|
class_iseq->iseq_encoded, GET_SP(),
|
||||||
class_iseq->local_size, 0);
|
class_iseq->local_size, 0);
|
||||||
|
rb_vm_using_modules(class_iseq->cref_stack, klass);
|
||||||
RESTORE_REGS();
|
RESTORE_REGS();
|
||||||
|
|
||||||
INC_VM_STATE_VERSION();
|
INC_VM_STATE_VERSION();
|
||||||
@ -1044,12 +1045,11 @@ invokesuper
|
|||||||
while (ip && !ip->klass) {
|
while (ip && !ip->klass) {
|
||||||
ip = ip->parent_iseq;
|
ip = ip->parent_iseq;
|
||||||
}
|
}
|
||||||
again:
|
|
||||||
me = rb_method_entry(klass, id, &klass);
|
me = rb_method_entry(klass, id, &klass);
|
||||||
if (me && me->def->type == VM_METHOD_TYPE_ISEQ &&
|
if (me && me->def->type == VM_METHOD_TYPE_ISEQ &&
|
||||||
me->def->body.iseq == ip) {
|
me->def->body.iseq == ip) {
|
||||||
klass = RCLASS_SUPER(klass);
|
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);
|
CALL_METHOD(num, blockptr, flag, id, me, recv, klass);
|
||||||
|
13
iseq.c
13
iseq.c
@ -199,17 +199,20 @@ set_relation(rb_iseq_t *iseq, const VALUE parent)
|
|||||||
/* set class nest stack */
|
/* set class nest stack */
|
||||||
if (type == ISEQ_TYPE_TOP) {
|
if (type == ISEQ_TYPE_TOP) {
|
||||||
/* toplevel is private */
|
/* 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;
|
iseq->cref_stack->nd_visi = NOEX_PRIVATE;
|
||||||
if (th->top_wrapper) {
|
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_visi = NOEX_PRIVATE;
|
||||||
cref->nd_next = iseq->cref_stack;
|
cref->nd_next = iseq->cref_stack;
|
||||||
iseq->cref_stack = cref;
|
iseq->cref_stack = cref;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type == ISEQ_TYPE_METHOD || type == ISEQ_TYPE_CLASS) {
|
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)) {
|
else if (RTEST(parent)) {
|
||||||
rb_iseq_t *piseq;
|
rb_iseq_t *piseq;
|
||||||
@ -1653,7 +1656,9 @@ rb_iseq_clone(VALUE iseqval, VALUE newcbase)
|
|||||||
iseq1->local_iseq = iseq1;
|
iseq1->local_iseq = iseq1;
|
||||||
}
|
}
|
||||||
if (newcbase) {
|
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) {
|
if (iseq0->cref_stack->nd_next) {
|
||||||
iseq1->cref_stack->nd_next = iseq0->cref_stack->nd_next;
|
iseq1->cref_stack->nd_next = iseq0->cref_stack->nd_next;
|
||||||
}
|
}
|
||||||
|
3
method.h
3
method.h
@ -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_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(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);
|
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);
|
int rb_method_entry_arity(const rb_method_entry_t *me);
|
||||||
|
8
node.h
8
node.h
@ -190,6 +190,8 @@ enum node_type {
|
|||||||
#define NODE_COLON2 NODE_COLON2
|
#define NODE_COLON2 NODE_COLON2
|
||||||
NODE_COLON3,
|
NODE_COLON3,
|
||||||
#define NODE_COLON3 NODE_COLON3
|
#define NODE_COLON3 NODE_COLON3
|
||||||
|
NODE_CREF,
|
||||||
|
#define NODE_CREF NODE_CREF
|
||||||
NODE_DOT2,
|
NODE_DOT2,
|
||||||
#define NODE_DOT2 NODE_DOT2
|
#define NODE_DOT2 NODE_DOT2
|
||||||
NODE_DOT3,
|
NODE_DOT3,
|
||||||
@ -263,9 +265,10 @@ typedef struct RNode {
|
|||||||
|
|
||||||
#define RNODE(obj) (R_CAST(RNode)(obj))
|
#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_NEWLINE (((VALUE)1)<<7)
|
||||||
#define NODE_FL_CREF_PUSHED_BY_EVAL NODE_FL_NEWLINE
|
#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_TYPESHIFT 8
|
||||||
#define NODE_TYPEMASK (((VALUE)0x7f)<<NODE_TYPESHIFT)
|
#define NODE_TYPEMASK (((VALUE)0x7f)<<NODE_TYPESHIFT)
|
||||||
@ -280,6 +283,8 @@ typedef struct RNode {
|
|||||||
#define nd_set_line(n,l) \
|
#define nd_set_line(n,l) \
|
||||||
RNODE(n)->flags=((RNODE(n)->flags&~(-1<<NODE_LSHIFT))|(((l)&NODE_LMASK)<<NODE_LSHIFT))
|
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_head u1.node
|
||||||
#define nd_alen u2.argc
|
#define nd_alen u2.argc
|
||||||
#define nd_next u3.node
|
#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_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_COLON2(c,i) NEW_NODE(NODE_COLON2,c,i,0)
|
||||||
#define NEW_COLON3(i) NEW_NODE(NODE_COLON3,0,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_DOT2(b,e) NEW_NODE(NODE_DOT2,b,e,0)
|
||||||
#define NEW_DOT3(b,e) NEW_NODE(NODE_DOT3,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)
|
#define NEW_SELF() NEW_NODE(NODE_SELF,0,0,0)
|
||||||
|
1
object.c
1
object.c
@ -2863,6 +2863,7 @@ Init_Object(void)
|
|||||||
rb_define_private_method(rb_cModule, "included", 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, "extended", rb_obj_dummy, 1);
|
||||||
rb_define_private_method(rb_cModule, "prepended", 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_added", rb_obj_dummy, 1);
|
||||||
rb_define_private_method(rb_cModule, "method_removed", 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);
|
rb_define_private_method(rb_cModule, "method_undefined", rb_obj_dummy, 1);
|
||||||
|
@ -216,10 +216,7 @@ class TestFiber < Test::Unit::TestCase
|
|||||||
|
|
||||||
def test_no_valid_cfp
|
def test_no_valid_cfp
|
||||||
bug5083 = '[ruby-dev:44208]'
|
bug5083 = '[ruby-dev:44208]'
|
||||||
error = assert_raise(RuntimeError) do
|
assert_equal([], Fiber.new(&Module.method(:nesting)).resume)
|
||||||
Fiber.new(&Module.method(:nesting)).resume
|
|
||||||
end
|
|
||||||
assert_equal("Can't call on top of Fiber or Thread", error.message, bug5083)
|
|
||||||
error = assert_raise(RuntimeError) do
|
error = assert_raise(RuntimeError) do
|
||||||
Fiber.new(&Module.method(:undef_method)).resume(:to_s)
|
Fiber.new(&Module.method(:undef_method)).resume(:to_s)
|
||||||
end
|
end
|
||||||
|
304
test/ruby/test_refinement.rb
Normal file
304
test/ruby/test_refinement.rb
Normal file
@ -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
|
||||||
|
|
@ -606,10 +606,7 @@ class TestThread < Test::Unit::TestCase
|
|||||||
def test_no_valid_cfp
|
def test_no_valid_cfp
|
||||||
skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#intialize' if defined?(WIN32OLE)
|
skip 'with win32ole, cannot run this testcase because win32ole redefines Thread#intialize' if defined?(WIN32OLE)
|
||||||
bug5083 = '[ruby-dev:44208]'
|
bug5083 = '[ruby-dev:44208]'
|
||||||
error = assert_raise(RuntimeError) do
|
assert_equal([], Thread.new(&Module.method(:nesting)).value)
|
||||||
Thread.new(&Module.method(:nesting)).join
|
|
||||||
end
|
|
||||||
assert_equal("Can't call on top of Fiber or Thread", error.message, bug5083)
|
|
||||||
error = assert_raise(RuntimeError) do
|
error = assert_raise(RuntimeError) do
|
||||||
Thread.new(:to_s, &Module.method(:undef_method)).join
|
Thread.new(:to_s, &Module.method(:undef_method)).join
|
||||||
end
|
end
|
||||||
|
4
vm.c
4
vm.c
@ -787,9 +787,9 @@ rb_vm_cref(void)
|
|||||||
rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);
|
rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);
|
||||||
|
|
||||||
if (cfp == 0) {
|
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
|
#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);
|
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
|
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 = vm_cref_push(th, under, NOEX_PUBLIC, blockptr);
|
||||||
cref->flags |= NODE_FL_CREF_PUSHED_BY_EVAL;
|
cref->flags |= NODE_FL_CREF_PUSHED_BY_EVAL;
|
||||||
|
rb_vm_using_modules(cref, under);
|
||||||
|
|
||||||
if (values == Qundef) {
|
if (values == Qundef) {
|
||||||
return vm_yield_with_cref(th, 1, &self, cref);
|
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 {
|
else {
|
||||||
SafeStringValue(src);
|
SafeStringValue(src);
|
||||||
}
|
}
|
||||||
|
rb_vm_using_modules(cref, under);
|
||||||
|
|
||||||
return eval_string_with_cref(self, src, Qnil, cref, file, line);
|
return eval_string_with_cref(self, src, Qnil, cref, file, line);
|
||||||
}
|
}
|
||||||
|
@ -1128,13 +1128,13 @@ vm_get_cref0(const rb_iseq_t *iseq, const VALUE *ep)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static NODE *
|
NODE *
|
||||||
vm_get_cref(const rb_iseq_t *iseq, const VALUE *ep)
|
rb_vm_get_cref(const rb_iseq_t *iseq, const VALUE *ep)
|
||||||
{
|
{
|
||||||
NODE *cref = vm_get_cref0(iseq, ep);
|
NODE *cref = vm_get_cref0(iseq, ep);
|
||||||
|
|
||||||
if (cref == 0) {
|
if (cref == 0) {
|
||||||
rb_bug("vm_get_cref: unreachable");
|
rb_bug("rb_vm_get_cref: unreachable");
|
||||||
}
|
}
|
||||||
return cref;
|
return cref;
|
||||||
}
|
}
|
||||||
@ -1143,7 +1143,8 @@ static NODE *
|
|||||||
vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr)
|
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);
|
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;
|
cref->nd_visi = noex;
|
||||||
|
|
||||||
if (blockptr) {
|
if (blockptr) {
|
||||||
@ -1152,6 +1153,11 @@ vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr)
|
|||||||
else if (cfp) {
|
else if (cfp) {
|
||||||
cref->nd_next = vm_get_cref0(cfp->iseq, cfp->ep);
|
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;
|
return cref;
|
||||||
}
|
}
|
||||||
@ -1159,7 +1165,7 @@ vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr)
|
|||||||
static inline VALUE
|
static inline VALUE
|
||||||
vm_get_cbase(const rb_iseq_t *iseq, const VALUE *ep)
|
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;
|
VALUE klass = Qundef;
|
||||||
|
|
||||||
while (cref) {
|
while (cref) {
|
||||||
@ -1175,7 +1181,7 @@ vm_get_cbase(const rb_iseq_t *iseq, const VALUE *ep)
|
|||||||
static inline VALUE
|
static inline VALUE
|
||||||
vm_get_const_base(const rb_iseq_t *iseq, const VALUE *ep)
|
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;
|
VALUE klass = Qundef;
|
||||||
|
|
||||||
while (cref) {
|
while (cref) {
|
||||||
@ -1200,6 +1206,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
|
static inline VALUE
|
||||||
vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq,
|
vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq,
|
||||||
VALUE orig_klass, ID id, int is_defined)
|
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) {
|
if (orig_klass == Qnil) {
|
||||||
/* in current lexical scope */
|
/* 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;
|
const NODE *cref;
|
||||||
VALUE klass = orig_klass;
|
VALUE klass = orig_klass;
|
||||||
|
|
||||||
@ -1254,7 +1274,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq,
|
|||||||
|
|
||||||
/* search self */
|
/* search self */
|
||||||
if (root_cref && !NIL_P(root_cref->nd_clss)) {
|
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 {
|
else {
|
||||||
klass = CLASS_OF(th->cfp->self);
|
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
|
static inline VALUE
|
||||||
vm_get_cvar_base(NODE *cref)
|
vm_get_cvar_base(NODE *cref, rb_control_frame_t *cfp)
|
||||||
{
|
{
|
||||||
VALUE klass;
|
VALUE klass;
|
||||||
|
|
||||||
@ -1296,7 +1316,7 @@ vm_get_cvar_base(NODE *cref)
|
|||||||
rb_warn("class variable access from toplevel");
|
rb_warn("class variable access from toplevel");
|
||||||
}
|
}
|
||||||
|
|
||||||
klass = cref->nd_clss;
|
klass = vm_get_iclass(cfp, cref->nd_clss);
|
||||||
|
|
||||||
if (NIL_P(klass)) {
|
if (NIL_P(klass)) {
|
||||||
rb_raise(rb_eTypeError, "no class variables available");
|
rb_raise(rb_eTypeError, "no class variables available");
|
||||||
@ -1805,3 +1825,43 @@ opt_eq_func(VALUE recv, VALUE obj, IC ic)
|
|||||||
return Qundef;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -160,8 +160,17 @@ enum vm_regan_acttype {
|
|||||||
/* deal with control flow 2: method/iterator */
|
/* 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 { \
|
#define COPY_CREF(c1, c2) do { \
|
||||||
NODE *__tmp_c2 = (c2); \
|
NODE *__tmp_c2 = (c2); \
|
||||||
|
COPY_CREF_OMOD(c1, __tmp_c2); \
|
||||||
(c1)->nd_clss = __tmp_c2->nd_clss; \
|
(c1)->nd_clss = __tmp_c2->nd_clss; \
|
||||||
(c1)->nd_visi = __tmp_c2->nd_visi;\
|
(c1)->nd_visi = __tmp_c2->nd_visi;\
|
||||||
(c1)->nd_next = __tmp_c2->nd_next; \
|
(c1)->nd_next = __tmp_c2->nd_next; \
|
||||||
|
83
vm_method.c
83
vm_method.c
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#define CACHE_SIZE 0x800
|
#define CACHE_SIZE 0x800
|
||||||
#define CACHE_MASK 0x7ff
|
#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
|
#define NOEX_NOREDEF 0
|
||||||
#ifndef NOEX_NOREDEF
|
#ifndef NOEX_NOREDEF
|
||||||
@ -21,6 +21,7 @@ struct cache_entry { /* method hash table. */
|
|||||||
VALUE filled_version; /* filled state version */
|
VALUE filled_version; /* filled state version */
|
||||||
ID mid; /* method's id */
|
ID mid; /* method's id */
|
||||||
VALUE klass; /* receiver's class */
|
VALUE klass; /* receiver's class */
|
||||||
|
VALUE omod; /* overlay modules */
|
||||||
rb_method_entry_t *me;
|
rb_method_entry_t *me;
|
||||||
VALUE defined_class;
|
VALUE defined_class;
|
||||||
};
|
};
|
||||||
@ -385,12 +386,20 @@ rb_get_alloc_func(VALUE klass)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static rb_method_entry_t*
|
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;
|
st_data_t body;
|
||||||
|
VALUE iclass, skipped_class = Qnil;
|
||||||
|
|
||||||
for (body = 0; klass; klass = RCLASS_SUPER(klass)) {
|
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) {
|
if (!m_tbl) {
|
||||||
m_tbl = RCLASS_M_TBL(RCLASS_ORIGIN(RBASIC(klass)->klass));
|
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() simply.
|
||||||
*/
|
*/
|
||||||
rb_method_entry_t *
|
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_ptr)
|
||||||
{
|
{
|
||||||
VALUE defined_class;
|
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) {
|
if (ruby_running) {
|
||||||
struct cache_entry *ent;
|
struct cache_entry *ent;
|
||||||
ent = cache + EXPR1(klass, id);
|
ent = cache + EXPR1(klass, omod, id);
|
||||||
ent->filled_version = GET_VM_STATE_VERSION();
|
ent->filled_version = GET_VM_STATE_VERSION();
|
||||||
ent->klass = klass;
|
ent->klass = klass;
|
||||||
|
ent->omod = omod;
|
||||||
ent->defined_class = defined_class;
|
ent->defined_class = defined_class;
|
||||||
|
|
||||||
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
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_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;
|
struct cache_entry *ent;
|
||||||
|
|
||||||
ent = cache + EXPR1(klass, id);
|
ent = cache + EXPR1(klass, omod, id);
|
||||||
if (ent->filled_version == GET_VM_STATE_VERSION() &&
|
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)
|
if (defined_class_ptr)
|
||||||
*defined_class_ptr = ent->defined_class;
|
*defined_class_ptr = ent->defined_class;
|
||||||
return ent->me;
|
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
|
static void
|
||||||
@ -550,9 +574,9 @@ rb_export_method(VALUE klass, ID name, rb_method_flag_t noex)
|
|||||||
rb_secure(4);
|
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)) {
|
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)) {
|
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
||||||
@ -640,6 +664,9 @@ void
|
|||||||
rb_undef(VALUE klass, ID id)
|
rb_undef(VALUE klass, ID id)
|
||||||
{
|
{
|
||||||
rb_method_entry_t *me;
|
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)) {
|
if (NIL_P(klass)) {
|
||||||
rb_raise(rb_eTypeError, "no class to undef method");
|
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));
|
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)) {
|
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
||||||
const char *s0 = " class";
|
const char *s0 = " class";
|
||||||
@ -676,6 +706,11 @@ rb_undef(VALUE klass, ID id)
|
|||||||
rb_id2name(id), s0, rb_class2name(c));
|
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);
|
rb_add_method(klass, id, VM_METHOD_TYPE_UNDEF, 0, NOEX_PUBLIC);
|
||||||
|
|
||||||
CALL_METHOD_HOOK(klass, undefined, id);
|
CALL_METHOD_HOOK(klass, undefined, id);
|
||||||
@ -980,11 +1015,11 @@ rb_alias(VALUE klass, ID name, ID def)
|
|||||||
}
|
}
|
||||||
|
|
||||||
again:
|
again:
|
||||||
orig_me = search_method(klass, def, 0);
|
orig_me = search_method(klass, def, Qnil, 0);
|
||||||
|
|
||||||
if (UNDEFINED_METHOD_ENTRY_P(orig_me)) {
|
if (UNDEFINED_METHOD_ENTRY_P(orig_me)) {
|
||||||
if ((!RB_TYPE_P(klass, T_MODULE)) ||
|
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))) {
|
UNDEFINED_METHOD_ENTRY_P(orig_me))) {
|
||||||
rb_print_undef(klass, def, 0);
|
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]);
|
id = rb_to_id(argv[i]);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
me = search_method(m, id, 0);
|
me = search_method(m, id, Qnil, 0);
|
||||||
if (me == 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)) {
|
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
||||||
rb_print_undef(module, id, 0);
|
rb_print_undef(module, id, 0);
|
||||||
@ -1375,6 +1410,20 @@ obj_respond_to_missing(VALUE obj, VALUE mid, VALUE priv)
|
|||||||
return Qfalse;
|
return Qfalse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
void
|
||||||
Init_eval_method(void)
|
Init_eval_method(void)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user