Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Module.nesting within instance_eval/exec or module_eval/exec

I came up with this question when I was trying to answer this. The following is an expected behaviour:

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

But the following:

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

all return []. Why do these not work as the above?

Additional Question

Mu is too short suggested an interesting point. If that is correct, then Module.nesting would be one of the methods and variables that are dependent on the literal context like Method#source_location, __FILE__. Is this understanding correct? If so, can someone provide the inventory of these methods/variables that are dependent on the literal context? I think it would be useful for reference.

like image 566
sawa Avatar asked Jun 14 '11 00:06

sawa


1 Answers

Warning: This is a little long a rambling. A bit of a tour through the Ruby source code seems necessary as the documentation is a bit thin. Feel free to skip to the end if you don't care about how sausage is made.


The 1.9.2 Module.nesting is implemented in eval.c like this:

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;
}

I don't know the Ruby internals that well but I read the while loop like this: extract from the cref linked list all the nodes that are associated with a class-like thing but didn't come from eval. The NODE_FL_CREF_PUSHED_BY_EVAL bit is only set in here:

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

A bit more grepping and reading reveals that instance_eval does end up going through yield_under. I'll leave checking instance_exec, module_eval, and module_exec as exercises for the reader. In any case, it looks like instance_eval is explicitly excluded from the Module.nesting list; this is, however, more of a distraction than anything else, it just means that you won't see something the evals mentioned.

So now the question is "what are NODE and rb_vm_cref() all about?".

If you look in node.h you'll see a bunch of NODE constants for the various Ruby keywords and language structures:

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

so I'd guess that NODE is a node in the instruction tree. This lines up nicely with my

Module.nesting seems to be more about talking to the parser

conjecture in the comment. But we'll keep going anyway.

The rb_vm_cref function is just a wrapper for vm_get_cref which is a wrapper for vm_get_cref0. What is vm_get_cref0 all about? It is all about this:

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);
    }
}

All three arguments to the function come straight out of this control frame:

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

The iseq appears to be an instruction sequence and the lfp and dfp are frame pointers:

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

The definition of cref_stack is relevant:

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

So it looks like you're getting some sort of call or nesting stack out of rb_vm_cref.


Now back to the specifics at hand. When you do this:

module A
  p Module.nesting
end

You'll have module A in the cref linked list (which is filtered to produce the Module.nesting result array) as you haven't hit the end yet. When you say these:

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

You won't have module A in cref anymore because you've already hit the end popped module A off the stack. However, if you do this:

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

You'll see this output:

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

because the module A hasn't been closed (and popped off cref) yet.

To finish off, the Module.nesting documentation says this:

Returns the list of Modules nested at the point of call.

I think this statement combined with the review of the internals indicates that Module.nesting does in fact depend on the specific literal context in which it is called.

If anyone with more experience in the Ruby internals has anything to add I can hand this over to the SO community as a community wiki.


UPDATE: All of this applies to class_eval as well as it does to module_eval and it also applies to 1.9.3 as well as it does to 1.9.2.

like image 146
mu is too short Avatar answered Nov 15 '22 19:11

mu is too short