Project

General

Profile

Actions

Bug #10702

closed

Constant look up inconsistency with constants defined in included modules

Bug #10702: Constant look up inconsistency with constants defined in included modules

Added by kwstannard (Kelly Stannard) almost 11 years ago. Updated over 10 years ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:67372]

Description

https://gist.github.com/kwstannard/c0f722976ba023cc5755

module A module B end end module C include A puts B.inspect # => A::B class D puts B.inspect rescue puts 'failed' # => failed end B = B class E puts B.inspect # => A::B end end 

When you include a module you gain access to that module's constants as seen in line 9. Line 19 shows that you can get constants defined in the nested module when the constant is defined within the module itself.

Line 12 is the bug. Shouldn't class D be able to access module B since the previous line shows that module C has access to Module B?

Updated by Anonymous almost 11 years ago Actions #1 [ruby-core:67375]

D does not inherit from C or A, so it does not have access to B.

After assigning B = B, E can "see" B lexically.

However, if you reopen E as C::E, it will "lose" B:

class C::E B # => uninitialized constant C::E::B (NameError) end 

This is described in http://cirw.in/blog/constant-lookup.html, among other good articles on constant resolution in Ruby.

Updated by nobu (Nobuyoshi Nakada) almost 11 years ago Actions #2 [ruby-core:67385]

  • Description updated (diff)
  • Status changed from Open to Rejected

Updated by kwstannard (Kelly Stannard) almost 11 years ago Actions #3 [ruby-core:67391]

Jack Nagel wrote:

D does not inherit from C or A, so it does not have access to B.

After assigning B = B, E can "see" B lexically.

However, if you reopen E as C::E, it will "lose" B:

class C::E B # => uninitialized constant C::E::B (NameError) end 

This is described in http://cirw.in/blog/constant-lookup.html, among other good articles on constant resolution in Ruby.

I looked at that blog before I posted and if constant lookup worked like it says then what I am doing would work because D is nested in C at the point where I am trying to access B. The nested opening of D should have access to all constants that C has access to but it doesn't. If this was consistent then either I should not be able to access B on line 9 or I should be able to access it on line 12.

Updated by Anonymous over 10 years ago Actions #4

I don't remember the details of the article, but if it is truly implying that the opening of D should have access to any constant accessible from C, then the article is wrong. But I don't think it is implying that.

either I should not be able to access B on line 9

The opening of C has access to B through C's inclusion of (i.e., inheritance from) A.

or I should be able to access it on line 12

The opening of D has access to two sets of constants: those available lexically, and those available through inheritance. B is not part of either of these.

Updated by kwstannard (Kelly Stannard) over 10 years ago Actions #5

Edit: go ahead and skip the rest of this post. Leaving because I don't believe in deleting responses.

I think that it should be found in the lexical lookup. Let me try explaining this again. My understanding of getting nested constants is that if I were to do this:

module X module Y; end module Z Y end end 

Then ruby will do this:

check X::Z for the const Y
check X for the const Y
X::Y is found in X

My confusion comes from how include interacts with the lookup chain. For example:

module A; module B; end; end module X include A end puts X::B # => A::B module X puts B # => A::B end 

This implies that B is now part of the lexical scope of X and points at the const A::B. This makes sense to me. The part that breaks the logical flow is that despite B being seemingly in the lexical scope of X at this point, any attempt to access B from within a module that is inside of X's lexical scope fails.

module A; module B; end; end module X include A end puts X::B # => A::B module X puts B # => A::B module Y puts B # => NameError: uninitialized constant X::Y::B end end 

So if I understand correctly, ruby does this, what i believe to be erroneous, lookup:

check X::Y for B
check X for B
check :: for B
fail

If X::B is not in the lexical lookup chain, then how is it being found anywhere?

Updated by kwstannard (Kelly Stannard) over 10 years ago Actions #6

Kelly Stannard wrote:

module A; module B; end; end module X include A end puts X::B # => A::B module X puts B # => A::B module Y puts B # => NameError: uninitialized constant X::Y::B end end 

Okay, so I re-reading the blog post a 5th time, I think I get that "module X; B; end" is using ancestry and not lexical, which would explain why "module X; module Y; B; end; end" fails. I don't get why X::B works though as it is not within X and therefor does not get to take advantage of X's ancestry lookup.

Updated by Anonymous over 10 years ago Actions #7

I don't get why X::B works though as it is not within X and therefor does not get to take advantage of X's ancestry lookup.

It is through ancestry. Among other things, including A in X includes A's constants in X. (see the documentation for Module#include/Module.append_features)

module A; module B; end; end module X; include A; end X.const_defined?(:B) # => true X.const_defined?(:B, false) # => false module X puts B # => this works because X includes A, which defines B. module Y puts B # => this is an error because (a) Y does not share ancestry with X and (b) B is not defined in the current nesting. end end 

Updated by kwstannard (Kelly Stannard) over 10 years ago Actions #8

Thanks for the response. I can't say that it is intuitive at all, but I think I get what is happening now.

Actions

Also available in: PDF Atom