Project

General

Profile

Bug #10702

Constant look up inconsistency with constants defined in included modules

Added by kwstannard (Kelly Stannard) about 5 years ago. Updated almost 5 years ago.

Status:
Rejected
Priority:
Normal
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 jacknagel (Jack Nagel) about 5 years ago

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) about 5 years ago

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

Updated by kwstannard (Kelly Stannard) about 5 years ago

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.

#4

Updated by jacknagel (Jack Nagel) almost 5 years ago

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.

#5

Updated by kwstannard (Kelly Stannard) almost 5 years ago

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?

#6

Updated by kwstannard (Kelly Stannard) almost 5 years ago

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.

#7

Updated by jacknagel (Jack Nagel) almost 5 years ago

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
#8

Updated by kwstannard (Kelly Stannard) almost 5 years ago

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

Also available in: Atom PDF