Class vs Module in constant lookup

Constant lookup behaves differently if there is a class versus a
module in the name:

 XYZ = 10

 module M
 end

 class C
 end

 p defined? M::XYZ  # => nil
 p defined? C::XYZ  # => "constant"

Is this difference intended? If so, what is the rationale?


Peter McLain
[email protected]

On 3/3/10, Peter McLain [email protected] wrote:

Constant lookup behaves differently if there is a class versus a
module in the name:

Extending your example a little to actually look up the constants
instead of just checking if they’re defined?:

XYZ = 10

module M
end

class C
end

p defined? M::XYZ # => nil
p defined? C::XYZ # => “constant”

C::XYZ
#(irb):13: warning: toplevel constant XYZ referenced by C::XYZ
#=> 10

M::XYZ

raises NameError

Note the warning for C::XYZ. You really shouldn’t look up constants
this way, so I think a little inconsistency like this is tolerable.

On Mar 3, 2010, at 12:15 PM, Caleb C. wrote:

module M
#=> 10

M::XYZ

raises NameError

Note the warning for C::XYZ. You really shouldn’t look up constants
this way, so I think a little inconsistency like this is tolerable.

Well, the code isn’t mine, but I’d still like to understand if
there is a rationale for the difference in behavior, or if this is
just another MRI implementation detail that’s leaked out. Currently,
MagLev returns “constant” for both classes and modules, but that
breaks third party code. E.g,:

 return unless defined? RDoc::VERSION # RDoc 1 does not have VERSION
 # code that blows up if RDoc 1 being used...

One could argue that the proper test, which works for both classes and
modules in all ruby implementations, is:

 return unless RDoc.const_defined? :VERSION

But I’d still like to understand if the difference in behavior is
intended or an implementation quirk, and if it is intended, what the
rationale is.


Peter McLain
[email protected]

On Wed, Mar 3, 2010 at 8:01 PM, Peter McLain [email protected]
wrote:

  p defined? M::XYZ  # => nil
  p defined? C::XYZ  # => “constant”

Is this difference intended? If so, what is the rationale?

I believe C::XYZ works because C is a subclass of Object.

On 3/3/10, Peter McLain [email protected] wrote:

modules in all ruby implementations, is:

 return unless RDoc.const_defined? :VERSION

But I’d still like to understand if the difference in behavior is
intended or an implementation quirk, and if it is intended, what the
rationale is.

Ok, I see your problem, and I sympathize.

Collisions of the toplevel constant VERSION with VERSIONs defined
inside classes are a repeated snafu. (It’s bitten me.) This is
probably why MRI 1.9 no longer defines VERSION, but RUBY_VERSION
instead.

I’d suspect this is unintended, but for an authoritative answer you’ll
have to get matz or another core maintainer to chime in. You might ask
on ruby-core; I suspect those guys don’t pay a lot of attention to
this list nowadays.

On Mar 3, 2010, at 2:45 PM, Xavier N. wrote:

class C
end

p defined? M::XYZ # => nil
p defined? C::XYZ # => “constant”

Is this difference intended? If so, what is the rationale?

I believe C::XYZ works because C is a subclass of Object.

I think that will end up being part of the explanation, but I’m not
yet convinced that it should be part of the explanation.

p M.ancestors # => [M]
p C.ancestors # => [C, Object, Kernel]

The explanation for constant lookup in the Flanagan/Matz book, Section
7.9, starts off:

“When a constant is referenced without any qualifying namespace…”
and then goes on to explain constant lookup.

Under those rules, I would expect both M and C to find the constant,
and indeed, using unqualified names, they both do find it:

XYZ = 10

module M
p XYZ # => 10
p defined? XYZ # => “constant”
end

class C
p XYZ # => 10
p defined? XYZ # => “constant”
end

But the original example was a fully qualified path: M::XYZ and
C:XYZ. I didn’t find any discussion of scoped constants. The draft
ruby spec, however, leads me to believe that the MRI behavior is a bug.

Section 11.4.3.2 of the spec discusses Scoped constant references.
Under my reading of that section, and our test case, then (following
the logic in that section):

(a) the primary-expression is M,
(b) M is a module
(c 1) XYZ is the constant-identifier
(c 2) XYZ is not one of the constants defined in M
(c 3 i) There are no included modules in M (skip)
(c 3 ii) N/A
(c 3 iii) Goto step e of section 11.4.3.1

11.4.3.1
(e) M is not a class
(e 1) Search Object for a binding for XYZ

So we should find it in Object and MRI is incorrect to return nil.

Did I miss something?


Peter McLain
[email protected]

On Thu, Mar 4, 2010 at 1:39 AM, Peter McLain [email protected]
wrote:

The explanation for constant lookup in the Flanagan/Matz book, Section 7.9,
starts off:

 “When a constant is referenced without any qualifying namespace…” and
then goes on to explain constant lookup.

Yes I also checked the book and albeit it is clear that the algorithm
applies to unqualified names, it is not that much clear what exactly
applies to qualified names.

On Thu, Mar 4, 2010 at 1:39 AM, Peter McLain [email protected]
wrote:

 (c 3 iii) Goto step e of section 11.4.3.1

11.4.3.1
 (e) M is not a class
 (e 1) Search Object for a binding for XYZ

So we should find it in Object and MRI is incorrect to return nil.

Or else the spec needs rewording.

Looking for Object for non-scoped constants in modules seems like a
lexical rule to me, akin to searching Module.nesting.

In a scoped constant reference I wouldn’t expect lexical-like steps to
be followed, so it might be the case that M::XYZ does not have to look
in Object, while C::XYZ needs to because of the ancestors rule. That
would a posteriori explain MRI’s behavior.

We need an authoritative answer, I saw the question was posted to
ruby-core, let’s see.

On Sun, Mar 7, 2010 at 11:34 AM, Xavier N. [email protected] wrote:

would a posteriori explain MRI’s behavior.

We need an authoritative answer, I saw the question was posted to
ruby-core, let’s see.

Just a followup, the spec has been indeed revised, M::X does not look in
Object.

Since I didn’t get a good answer here, I posted the question on ruby-
core, and it looks like I’ve caught the attention of the core team.
So, if you’re interested in the resolution, you should follow:
[ruby-core:28482] Question on scoped constant resolution Class vs Module

On Mar 3, 2010, at 11:01 AM, Peter McLain wrote:

p defined? M::XYZ # => nil
p defined? C::XYZ # => “constant”

Is this difference intended? If so, what is the rationale?


Peter McLain
[email protected]