Модуль.вложенность в экземпляр eval / exec или модуль eval / exec

Я придумал этот вопрос, когда я пытался ответить этой. Ниже приводится ожидаемое поведение:

module A
  p Module.nesting
end
# => [A]

но следующее:

A.instance_eval{p Module.nesting}
A.instance_exec{p Module.nesting}
A.module_eval{p Module.nesting}
A.module_exec{p Module.nesting}

все вернуть []. Почему они не работают как прежде?

Дополнительный Вопрос

му слишком коротко предложил интересный момент. Если это так, то Module.nesting будет одним из методов и переменных, которые зависят от контекста литерала как Method#source_location, __FILE__. Правильно ли это понимание? Если да, может ли кто-нибудь предоставить инвентарь этих методов/переменных, которые зависят от буквального контекста? Я думаю, это было бы полезно для справки.

1 ответов


предупреждение: это немного длинный бред. Немного тур по исходному коду Ruby кажется необходимым, поскольку документация немного тонкая. Не стесняйтесь пропустить до конца, если вам все равно, как делается колбаса.


в 1.9.2 Module.nesting реализуется в eval.c такой:

static VALUE
rb_mod_nesting(void)
{
    VALUE ary = rb_ary_new();
    const NODE *cref = rb_vm_cref();

    while (cref && cref->nd_next) {
        VALUE klass = cref->nd_clss;
        if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) &&
            !NIL_P(klass)) {
            rb_ary_push(ary, klass);
        }
        cref = cref->nd_next;
    }
    return ary;
}

я не знаю рубиновые внутренности так хорошо, но я читаю while цикл, как это: извлечение из cref связанный список всех узлов это связано с классовой вещью, но не произошло от eval. The NODE_FL_CREF_PUSHED_BY_EVAL бит установлен только здесь:

/* block eval under the class/module context */
static VALUE
yield_under(VALUE under, VALUE self, VALUE values)

немного больше grepping и чтение показывает, что instance_eval в конечном итоге идет через yield_under. Я оставлю проверку instance_exec, module_eval и module_exec в качестве упражнения для читателя. В любом случае, это выглядит как instance_eval исключена из Module.nesting список; это, однако, больше отвлекает, чем что-либо еще, это просто означает, что вы не увидите что-то эвалс упомянул.

Итак, теперь вопрос: "Что такое NODE и rb_vm_cref() все о?".

если вы посмотрите в node.h вы увидите кучу констант узлов для различных ключевых слов Ruby и языковых структур:

  • NODE_BLOCK
  • NODE_BREAK
  • NODE_CLASS
  • NODE_MODULE
  • NODE_DSYM
  • ...

так что я бы предположил, что NODE является узлом дерево инструкций. Это хорошо сочетается с моим

Module.nesting кажется, больше о разговоре с парсером

догадки в комментарии. Но мы все равно продолжим.

на rb_vm_cref функция - это просто оболочка для vm_get_cref который является оберткой для vm_get_cref0. Что такое vm_get_cref0 все о? Это все об этом:

static NODE *
vm_get_cref0(const rb_iseq_t *iseq, const VALUE *lfp, const VALUE *dfp)
{
    while (1) {
        if (lfp == dfp) {
            return iseq->cref_stack;
        }
        else if (dfp[-1] != Qnil) {
            return (NODE *)dfp[-1];
        }
        dfp = GET_PREV_DFP(dfp);
    }
}

все три аргумента функции выходят прямо из этого элемента управления кадр:

rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

на iseq кажется, последовательность инструкций и lfp и dfp указатели кадра:

VALUE *lfp;                 // cfp[6], local frame pointer
VALUE *dfp;                 // cfp[7], dynamic frame pointer

определение cref_stack актуальна:

/* klass/module nest information stack (cref) */
NODE *cref_stack;

таким образом, похоже, вы получаете какой-то вызов или вложенный стек из rb_vm_cref.


теперь вернемся к конкретике. Когда вы это сделаете:

module A
  p Module.nesting
end

вы module A на cref связанный список (что фильтруется для получения Module.nesting массив результатов), поскольку вы не попали в end еще. Когда вы говорите это:

A.instance_eval { puts Module.nesting }
A.instance_exec { puts Module.nesting }
A.module_eval   { puts Module.nesting }
A.module_exec   { puts Module.nesting }

у вас не будет module A на cref больше, потому что вы уже попали в end выскочил module A из стека. Однако, если вы это сделаете:

module A
  instance_eval { puts Module.nesting.inspect }
  instance_exec { puts Module.nesting.inspect }
  module_eval   { puts Module.nesting.inspect }
  module_exec   { puts Module.nesting.inspect }
end

вы увидите этот выход:

[A]
[A]
[A]
[A]

потому что module A не был закрыт (и выталкивается cref) пока нет.

добить,Module.nesting документация говорит:

возвращает список модулей, вложенных в точке вызова.

я думаю, что это заявление в сочетании с обзором внутренних органов указывает на то, что Module.nesting фактически зависит от конкретного буквального контекста, в котором он вызывается.

если у кого-то с большим опытом работы в Ruby internals есть что добавить, я могу передать это сообществу SO как сообществу вики.


обновление: все это относится и к class_eval так же, как и module_eval и это также относится к 1.9.3, а также к 1.9.2.