lexically scoped, …">

John Firebaugh

Open Source, Ruby, Rubinius, RubySpec, Rails.

Making Sense of Constant Lookup in Ruby

In Ruby 1.8, constant lookup is mostly lexically scoped, even when using class_eval or instance_eval. That is, constant lookup always searches the chain of lexically enclosing classes or modules. The first lexically enclosing class or module is not necessarily the same as self.class:

Here’s the output on 1.8.7:

Here we can see that within the lexical block defining Foo#foo, which is enclosed by class Foo, X refers to Foo::X, while in the lexical blocks used for the singleton method, class_eval, and instance_eval, which are not in class Foo’s scope, X refers to ::X, the global constant.

However, in 1.9, the situation changes, and moreover, the behavior is different between 1.9.1 and 1.9.2. Here’s the result of running rvm 1.9.1,1.9.2 constants.rb:

So, in 1.9.1, constant lookup in class_eval and instance_eval proceeds from the receiver, rather than the lexically enclosing scope. Particularly for class_eval, this turned out to be a problematic change, breaking existing libraries that depended on the 1.8.7 behavior and making it hard to build DSLs that behaved in predictable ways. Eventually, it was decided to revert to the 1.8 behavior, and this was supposedly implemented:

> [Matz] would like to revert all of instance_eval, instance_exec,
> class_eval, and class_exec to the behavior of 1.8 (including class
> variables). [...]

I have just commited it to the SVN trunk.

I say “supposedly” only because as you can see, the 1.9.2 behavior still differs from 1.8.7 in the case of instance_eval. Was this an oversight, or was the revert later unreverted for instance_eval? If so, what was the rationale? I searched the mailing list and subsequent commits, but couldn’t find an explanation. If anyone can shed light on the matter, I would appreciate it.

As you can see, 1.9.2 also changed the behavior for singleton methods: the receiver’s scope is now searched before the lexically enclosing scope. This change makes sense to me, and I haven’t heard of it causing any problems.

Note that these rules apply to constant definition as well as lookup. In 1.8 and 1.9.2, a constant defined in a class_evaluated block will be defined in the enclosing lexical scope, rather than the scope of the receiver. This is one of the things that makes Foo = Class.new { … } not quite the same as class Foo; …; end:

The block passed to Class.new is effectively class_evaluated, so in this example, the constant Quux ends up defined at the top level. (And once again 1.9.1 is the exception: it defines Baz::Quux instead.) This behavior can cause problems if you are in the habit of defining test classes in RSpec describe blocks:

Here TestClass winds up in the global scope, not the scope of the RSpec example group that describe creates. If you have multiple specs that define test classes with the same name, you may get collisions unless you place each describe within a uniquely-named module or diligently remove the constants in an after block. In the above example, you’ll get the error “superclass mismatch for class TestClass”.

If you need to ensure a particular scoping is used (for example, if you need to support 1.9.1 as well as 1.8.7/1.9.2), you can always be explicit about it by prefixing constants with :: (for global lookup), self:: (for receiver scope), or the fully qualified desired scope:

Comments