lib/active_support/dependencies.rb in activesupport-6.0.6.1 vs lib/active_support/dependencies.rb in activesupport-6.1.0.rc1

- old
+ new

@@ -10,11 +10,10 @@ require "active_support/core_ext/module/anonymous" require "active_support/core_ext/object/blank" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/load_error" require "active_support/core_ext/name_error" -require "active_support/core_ext/string/starts_ends_with" require "active_support/dependencies/interlock" require "active_support/inflector" module ActiveSupport #:nodoc: module Dependencies #:nodoc: @@ -260,19 +259,28 @@ Dependencies.require_or_load(file_name) end # :doc: - # Interprets a file using <tt>mechanism</tt> and marks its defined - # constants as autoloaded. <tt>file_name</tt> can be either a string or + # <b>Warning:</b> This method is obsolete in +:zeitwerk+ mode. In + # +:zeitwerk+ mode semantics match Ruby's and you do not need to be + # defensive with load order. Just refer to classes and modules normally. + # If the constant name is dynamic, camelize if needed, and constantize. + # + # In +:classic+ mode, interprets a file using +mechanism+ and marks its + # defined constants as autoloaded. +file_name+ can be either a string or # respond to <tt>to_path</tt>. # - # Use this method in code that absolutely needs a certain constant to be - # defined at that point. A typical use case is to make constant name - # resolution deterministic for constants with the same relative name in - # different namespaces whose evaluation would depend on load order - # otherwise. + # In +:classic+ mode, use this method in code that absolutely needs a + # certain constant to be defined at that point. A typical use case is to + # make constant name resolution deterministic for constants with the same + # relative name in different namespaces whose evaluation would depend on + # load order otherwise. + # + # Engines that do not control the mode in which their parent application + # runs should call +require_dependency+ where needed in case the runtime + # mode is +:classic+. def require_dependency(file_name, message = "No such file to load -- %s.rb") file_name = file_name.to_path if file_name.respond_to?(:to_path) unless file_name.is_a?(String) raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" end @@ -365,16 +373,11 @@ def depend_on(file_name, message = "No such file to load -- %s.rb") path = search_for_file(file_name) require_or_load(path || file_name) rescue LoadError => load_error if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] - load_error_message = if load_error.respond_to?(:original_message) - load_error.original_message - else - load_error.message - end - load_error_message.replace(message % file_name) + load_error.message.replace(message % file_name) load_error.copy_blame!(load_error) end raise end @@ -456,11 +459,11 @@ paths end # Search for a file in autoload_paths matching the provided suffix. def search_for_file(path_suffix) - path_suffix += ".rb" unless path_suffix.ends_with?(".rb") + path_suffix += ".rb" unless path_suffix.end_with?(".rb") autoload_paths.each do |root| path = File.join(root, path_suffix) return path if File.file? path end @@ -476,13 +479,13 @@ end nil end def load_once_path?(path) - # to_s works around a ruby issue where String#starts_with?(Pathname) + # to_s works around a ruby issue where String#start_with?(Pathname) # will raise a TypeError: no implicit conversion of Pathname into String - autoload_once_paths.any? { |base| path.starts_with? base.to_s } + autoload_once_paths.any? { |base| path.start_with?(base.to_s) } end # Attempt to autoload the provided module name by searching for a directory # matching the expected path suffix. If found, the module is created and # assigned to +into+'s constants with the name +const_name+. Provided that @@ -528,22 +531,23 @@ # Load the constant named +const_name+ which is missing from +from_mod+. If # it is not possible to load the constant into from_mod, try its parent # module using +const_missing+. def load_missing_constant(from_mod, const_name) - unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod) + from_mod_name = real_mod_name(from_mod) + unless qualified_const_defined?(from_mod_name) && Inflector.constantize(from_mod_name).equal?(from_mod) raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end qualified_name = qualified_name_for(from_mod, const_name) path_suffix = qualified_name.underscore file_path = search_for_file(path_suffix) if file_path expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, "") + expanded.delete_suffix!(".rb") if loading.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) @@ -587,12 +591,12 @@ rescue NameError => e raise unless e.missing_name? qualified_name_for(parent, const_name) end end - name_error = NameError.new("uninitialized constant #{qualified_name}", const_name) - name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ }) + name_error = uninitialized_constant(qualified_name, const_name, receiver: from_mod) + name_error.set_backtrace(caller.reject { |l| l.start_with? __FILE__ }) raise name_error end # Remove the constants that have been autoloaded, and those that have been # marked for unloading. Before each constant is removed a callback is sent @@ -717,32 +721,32 @@ # Convert the provided const desc to a qualified constant name (as a string). # A module, class, symbol, or string may be provided. def to_constant_name(desc) #:nodoc: case desc - when String then desc.sub(/^::/, "") + when String then desc.delete_prefix("::") when Symbol then desc.to_s when Module real_mod_name(desc) || raise(ArgumentError, "Anonymous modules have no name to be referenced by") else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end end def remove_constant(const) #:nodoc: # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. - normalized = const.to_s.sub(/\A::/, "") + normalized = const.to_s.delete_prefix("::") normalized.sub!(/\A(Object::)+/, "") constants = normalized.split("::") to_remove = constants.pop # Remove the file path from the loaded list. file_path = search_for_file(const.underscore) if file_path expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, "") + expanded.delete_suffix!(".rb") loaded.delete(expanded) end if constants.empty? parent = Object @@ -796,9 +800,19 @@ def log(message) logger.debug("autoloading: #{message}") if logger && verbose end private + if RUBY_VERSION < "2.6" + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name) + end + else + def uninitialized_constant(qualified_name, const_name, receiver:) + NameError.new("uninitialized constant #{qualified_name}", const_name, receiver: receiver) + end + end + # Returns the original name of a class or module even if `name` has been # overridden. def real_mod_name(mod) UNBOUND_METHOD_MODULE_NAME.bind(mod).call end