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
peter.mclain@gemstone.com

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 3/3/10, Peter McLain <peter.mclain@gemstone.com> wrote:

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

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

···

On Wed, Mar 3, 2010 at 8:01 PM, Peter McLain <peter.mclain@gemstone.com> wrote:

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?

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:

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
peter.mclain@gemstone.com

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.

···

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

On 3/3/10, Peter McLain <peter.mclain@gemstone.com> 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.

-- Peter McLain
peter.mclain@gemstone.com

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?

···

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

On Wed, Mar 3, 2010 at 8:01 PM, Peter McLain <peter.mclain@gemstone.com > > wrote:

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?

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

-- Peter McLain
peter.mclain@gemstone.com

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 3/3/10, Peter McLain <peter.mclain@gemstone.com> wrote:

   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.

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 <peter.mclain@gemstone.com> 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.

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 Thu, Mar 4, 2010 at 1:39 AM, Peter McLain <peter.mclain@gemstone.com> wrote:

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.

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

···

On Sun, Mar 7, 2010 at 11:34 AM, Xavier Noria <fxn@hashref.com> wrote:

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.