better error message for constants autoloaded from anonymous modules [fixes #13204]
load_missing_constant is a private method that basically plays the role of const_missing. This method has an error condition that is surprising: it raises if the class or module already has the missing constant. How is it possible that if the class of module has the constant Ruby has called const_missing in the first place? The answer is that the from_mod argument is self except for anonymous modules, because const_missing passes down Object in such case (see the comment in the source code of the patch for the rationale). But then, it is better to pass down Object *if Object is also missing the constant* and otherwise err with an informative message right away.
This commit is contained in:
parent
8ef1ef1b82
commit
01c9782fa2
@ -176,14 +176,22 @@ def self.exclude_from(base)
|
|||||||
end
|
end
|
||||||
|
|
||||||
def const_missing(const_name)
|
def const_missing(const_name)
|
||||||
# The interpreter does not pass nesting information, and in the
|
from_mod = anonymous? ? guess_for_anonymous(const_name) : self
|
||||||
# case of anonymous modules we cannot even make the trade-off of
|
|
||||||
# assuming their name reflects the nesting. Resort to Object as
|
|
||||||
# the only meaningful guess we can make.
|
|
||||||
from_mod = anonymous? ? ::Object : self
|
|
||||||
Dependencies.load_missing_constant(from_mod, const_name)
|
Dependencies.load_missing_constant(from_mod, const_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Dependencies assumes the name of the module reflects the nesting (unless
|
||||||
|
# it can be proven that is not the case), and the path to the file that
|
||||||
|
# defines the constant. Anonymous modules cannot follow these conventions
|
||||||
|
# and we assume therefore the user wants to refer to a top-level constant.
|
||||||
|
def guess_for_anonymous(const_name)
|
||||||
|
if Object.const_defined?(const_name)
|
||||||
|
raise NameError, "#{const_name} cannot be autoloaded from an anonymous class or module"
|
||||||
|
else
|
||||||
|
Object
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def unloadable(const_desc = self)
|
def unloadable(const_desc = self)
|
||||||
super(const_desc)
|
super(const_desc)
|
||||||
end
|
end
|
||||||
@ -456,8 +464,6 @@ def load_missing_constant(from_mod, const_name)
|
|||||||
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
|
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
|
||||||
end
|
end
|
||||||
|
|
||||||
raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
|
|
||||||
|
|
||||||
qualified_name = qualified_name_for from_mod, const_name
|
qualified_name = qualified_name_for from_mod, const_name
|
||||||
path_suffix = qualified_name.underscore
|
path_suffix = qualified_name.underscore
|
||||||
|
|
||||||
|
@ -530,29 +530,21 @@ module A
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_const_missing_should_not_double_load
|
def test_const_missing_in_anonymous_modules_loads_top_level_constants
|
||||||
$counting_loaded_times = 0
|
|
||||||
with_autoloading_fixtures do
|
with_autoloading_fixtures do
|
||||||
require_dependency '././counting_loader'
|
# class_eval STRING pushes the class to the nesting of the eval'ed code.
|
||||||
assert_equal 1, $counting_loaded_times
|
klass = Class.new.class_eval "E"
|
||||||
assert_raise(NameError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader }
|
assert_equal E, klass
|
||||||
assert_equal 1, $counting_loaded_times
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_const_missing_within_anonymous_module
|
def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
|
||||||
$counting_loaded_times = 0
|
|
||||||
m = Module.new
|
|
||||||
m.module_eval "def a() CountingLoader; end"
|
|
||||||
extend m
|
|
||||||
with_autoloading_fixtures do
|
with_autoloading_fixtures do
|
||||||
kls = nil
|
require_dependency 'e'
|
||||||
assert_nothing_raised { kls = a }
|
|
||||||
assert_equal "CountingLoader", kls.name
|
|
||||||
assert_equal 1, $counting_loaded_times
|
|
||||||
|
|
||||||
assert_nothing_raised { kls = a }
|
mod = Module.new
|
||||||
assert_equal 1, $counting_loaded_times
|
msg = 'E cannot be autoloaded from an anonymous class or module'
|
||||||
|
assert_raise(NameError, msg) { mod::E }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user