Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rubygem-zeitwerk for 
openSUSE:Factory checked in at 2022-12-13 18:57:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-zeitwerk (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-zeitwerk.new.1835 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-zeitwerk"

Tue Dec 13 18:57:04 2022 rev:15 rq:1042665 version:2.6.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-zeitwerk/rubygem-zeitwerk.changes        
2022-10-12 18:26:55.837985598 +0200
+++ 
/work/SRC/openSUSE:Factory/.rubygem-zeitwerk.new.1835/rubygem-zeitwerk.changes  
    2022-12-13 18:57:33.535873028 +0100
@@ -1,0 +2,6 @@
+Wed Dec  7 11:39:03 UTC 2022 - Stephan Kulow <[email protected]>
+
+updated to version 2.6.6
+  no changelog found
+
+-------------------------------------------------------------------

Old:
----
  zeitwerk-2.6.1.gem

New:
----
  zeitwerk-2.6.6.gem

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rubygem-zeitwerk.spec ++++++
--- /var/tmp/diff_new_pack.4MXi9d/_old  2022-12-13 18:57:34.067875868 +0100
+++ /var/tmp/diff_new_pack.4MXi9d/_new  2022-12-13 18:57:34.071875889 +0100
@@ -24,7 +24,7 @@
 #
 
 Name:           rubygem-zeitwerk
-Version:        2.6.1
+Version:        2.6.6
 Release:        0
 %define mod_name zeitwerk
 %define mod_full_name %{mod_name}-%{version}

++++++ zeitwerk-2.6.1.gem -> zeitwerk-2.6.6.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/README.md new/README.md
--- old/README.md       2022-10-01 00:18:05.000000000 +0200
+++ new/README.md       2022-11-08 09:02:43.000000000 +0100
@@ -27,8 +27,14 @@
   - [Autoloading](#autoloading)
   - [Eager loading](#eager-loading)
     - [Eager load exclusions](#eager-load-exclusions)
+    - [Eager load directories](#eager-load-directories)
+    - [Eager load namespaces](#eager-load-namespaces)
+    - [Eager load namespaces shared by several 
loaders](#eager-load-namespaces-shared-by-several-loaders)
     - [Global eager load](#global-eager-load)
+  - [Loading individual files](#loading-individual-files)
   - [Reloading](#reloading)
+    - [Configuration and usage](#configuration-and-usage)
+    - [Thread-safety](#thread-safety)
   - [Inflection](#inflection)
     - [Zeitwerk::Inflector](#zeitwerkinflector)
     - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
@@ -44,6 +50,7 @@
     - [Use case: Files that do not follow the 
conventions](#use-case-files-that-do-not-follow-the-conventions)
     - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
     - [Use case: Test files mixed with implementation 
files](#use-case-test-files-mixed-with-implementation-files)
+  - [Shadowed files](#shadowed-files)
   - [Edge cases](#edge-cases)
   - [Beware of circular dependencies](#beware-of-circular-dependencies)
   - [Reopening third-party namespaces](#reopening-third-party-namespaces)
@@ -206,7 +213,7 @@
 <a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
 #### Custom root namespaces
 
-While `Object` is by far the most common root namespace, you can associate a 
different one to a particular root directory. The method `push_dir` accepts a 
class or module object in the optional `namespace` keyword argument.
+While `Object` is by far the most common root namespace, you can associate a 
different one to a particular root directory. The method `push_dir` accepts a 
non-anonymous class or module object in the optional `namespace` keyword 
argument.
 
 For example, given:
 
@@ -442,6 +449,8 @@
 
 Eager loading is synchronized and idempotent.
 
+Attempting to eager load without previously calling `setup` raises 
`Zeitwerk::SetupRequired`.
+
 <a id="markdown-eager-load-exclusions" name="eager-load-exclusions"></a>
 #### Eager load exclusions
 
@@ -464,6 +473,81 @@
 
 The `force` flag does not affect ignored files and directories, those are 
still ignored.
 
+<a id="markdown-eager-load-directories" name="eager-load-directories"></a>
+#### Eager load directories
+
+The method `Zeitwerk::Loader#eager_load_dir` eager loads a given directory, 
recursively:
+
+```ruby
+loader.eager_load_dir("#{__dir__}/custom_web_app/routes")
+```
+
+This is useful when the loader is not eager loading the entire project, but 
you still need some subtree to be loaded for things to function properly.
+
+Both strings and `Pathname` objects are supported as arguments. If the 
argument is not a directory managed by the receiver, the method raises 
`Zeitwerk::Error`.
+
+[Eager load exclusions](#eager-load-exclusions), [ignored files and 
directories](#ignoring-parts-of-the-project), and [shadowed 
files](https://github.com/fxn/zeitwerk#shadowed-files) are not eager loaded.
+
+`Zeitwerk::Loader#eager_load_dir` is idempotent, but compatible with 
reloading. If you eager load a directory and then reload, eager loading that 
directory will load its (current) contents again.
+
+The method checks if a regular eager load was already executed, in which case 
it returns fast.
+
+Nested root directories which are descendants of the argument are skipped. 
Those subtrees are considered to be conceptually apart.
+
+Attempting to eager load a directory without previously calling `setup` raises 
`Zeitwerk::SetupRequired`.
+
+<a id="markdown-eager-load-namespaces" name="eager-load-namespaces"></a>
+#### Eager load namespaces
+
+The method `Zeitwerk::Loader#eager_load_namespace` eager loads a given 
namespace, recursively:
+
+```ruby
+loader.eager_load_namespace(MyApp::Routes)
+```
+
+This is useful when the loader is not eager loading the entire project, but 
you still need some namespace to be loaded for things to function properly.
+
+The argument has to be a class or module object and the method raises 
`Zeitwerk::Error` otherwise.
+
+If the namespace is spread over multiple directories in the receiver's source 
tree, they are all eager loaded. For example, if you have a structure like
+
+```
+root_dir1/my_app/routes
+root_dir2/my_app/routes
+root_dir3/my_app/routes
+```
+
+where `root_directory{1,2,3}` are root directories, eager loading 
`MyApp::Routes` will eager load the contents of the three corresponding 
directories.
+
+There might exist external source trees implementing part of the namespace. 
This happens routinely, because top-level constants are stored in the globally 
shared `Object`. It happens also when deliberately [reopening third-party 
namespaces](reopening-third-party-namespaces). Such external code is not eager 
loaded, the implementation is carefully scoped to what the receiver manages to 
avoid side-effects elsewhere.
+
+This method is flexible about what it accepts. Its semantics have to be 
interpreted as: "_If_ you manage this namespace, or part of this namespace, 
please eager load what you got". In particular, if the receiver does not manage 
the namespace, it will simply do nothing, this is not an error condition.
+
+[Eager load exclusions](#eager-load-exclusions), [ignored files and 
directories](#ignoring-parts-of-the-project), and [shadowed 
files](https://github.com/fxn/zeitwerk#shadowed-files) are not eager loaded.
+
+`Zeitwerk::Loader#eager_load_namespace` is idempotent, but compatible with 
reloading. If you eager load a namespace and then reload, eager loading that 
namespace will load its (current) descendants again.
+
+The method checks if a regular eager load was already executed, in which case 
it returns fast.
+
+If root directories are assigned to custom namespaces, the method behaves as 
you'd expect, according to the namespacing relationship between the custom 
namespace and the argument.
+
+Attempting to eager load a namespace without previously calling `setup` raises 
`Zeitwerk::SetupRequired`.
+
+<a id="markdown-eager-load-namespaces-shared-by-several-loaders" 
name="eager-load-namespaces-shared-by-several-loaders"></a>
+#### Eager load namespaces shared by several loaders
+
+The method `Zeitwerk::Loader.eager_load_namespace` broadcasts 
`eager_load_namespace` to all loaders.
+
+```ruby
+Zeitwerk::Loader.eager_load_namespace(MyFramework::Routes)
+```
+
+This may be handy, for example, if a framework supports plugins and a shared 
namespace needs to be eager loaded for the framework to function properly.
+
+Please, note that loaders only eager load namespaces they manage, as 
documented above. Therefore, this method does not allow you to eager load 
namespaces not managed by Zeitwerk loaders.
+
+This method does not require that all registered loaders have `setup` already 
invoked, since that is out of your control. If there's any in that state, it is 
simply skipped.
+
 <a id="markdown-global-eager-load" name="global-eager-load"></a>
 #### Global eager load
 
@@ -479,9 +563,31 @@
 
 This method does not accept the `force` flag, since in general it wouldn't be 
a good idea to force eager loading in 3rd party code.
 
+This method does not require that all registered loaders have `setup` already 
invoked, since that is out of your control. If there's any in that state, it is 
simply skipped.
+
+<a id="markdown-loading-individual-files" name="loading-individual-files"></a>
+### Loading individual files
+
+The method `Zeitwerk::Loader#load_file` loads an individual Ruby file:
+
+```ruby
+loader.load_file("#{__dir__}/custom_web_app/routes.rb")
+```
+
+This is useful when the loader is not eager loading the entire project, but 
you still need an individual file to be loaded for things to function properly.
+
+Both strings and `Pathname` objects are supported as arguments. The method 
raises `Zeitwerk::Error` if the argument is not a Ruby file, is 
[ignored](#ignoring-parts-of-the-project), is 
[shadowed](https://github.com/fxn/zeitwerk#shadowed-files), or is not managed 
by the receiver.
+
+`Zeitwerk::Loader#load_file` is idempotent, but compatible with reloading. If 
you load a file and then reload, a new call will load its (current) contents 
again.
+
+If you want to eager load a directory, `Zeitwerk::Loader#eager_load_dir` is 
more efficient than invoking `Zeitwerk::Loader#load_file` on its files.
+
 <a id="markdown-reloading" name="reloading"></a>
 ### Reloading
 
+<a id="markdown-configuration-and-usage" name="configuration-and-usage"></a>
+#### Configuration and usage
+
 Zeitwerk is able to reload code, but you need to enable this feature:
 
 ```ruby
@@ -495,7 +601,7 @@
 
 There is no way to undo this, either you want to reload or you don't.
 
-Enabling reloading after setup raises `Zeitwerk::Error`. Attempting to reload 
without having it enabled raises `Zeitwerk::ReloadingDisabledError`.
+Enabling reloading after setup raises `Zeitwerk::Error`. Attempting to reload 
without having it enabled raises `Zeitwerk::ReloadingDisabledError`. Attempting 
to reload without previously calling `setup` raises `Zeitwerk::SetupRequired`.
 
 Generally speaking, reloading is useful while developing running services like 
web applications. Gems that implement regular libraries, so to speak, or 
services running in testing or production environments, won't normally have a 
use case for reloading. If reloading is not enabled, Zeitwerk is able to use 
less memory.
 
@@ -503,12 +609,34 @@
 
 It is important to highlight that this is an instance method. Don't worry 
about project dependencies managed by Zeitwerk, their loaders are independent.
 
-Reloading is not thread-safe:
+<a id="markdown-thread-safety" name="thread-safety"></a>
+#### Thread-safety
 
-* You should not reload while another thread is reloading.
-* You should not autoload while another thread is reloading.
+In order to reload safely, no other thread can be autoloading or reloading 
concurrently. Client code is responsible for this coordination.
 
-In order to reload in a thread-safe manner, frameworks need to implement some 
coordination. For example, a web framework that serves each request with its 
own thread may have a globally accessible read/write lock: When a request comes 
in, the framework acquires the lock for reading at the beginning, and releases 
it at the end. On the other hand, the code in the framework responsible for the 
call to `Zeitwerk::Loader#reload` needs to acquire the lock for writing.
+For example, a web framework that serves each request in its own thread and 
has reloading enabled could create a read-write lock on boot like this:
+
+```ruby
+require "concurrent/atomic/read_write_lock"
+
+MyFramework::RELOAD_RW_LOCK = Concurrent::ReadWriteLock.new
+```
+
+You acquire the lock for reading for serving each individual request:
+
+```ruby
+MyFramework::RELOAD_RW_LOCK.with_read_lock do
+  serve(request)
+end
+```
+
+Then, when a reload is triggered, just acquire the lock for writing in order 
to execute the method call safely:
+
+```ruby
+MyFramework::RELOAD_RW_LOCK.with_write_lock do
+  loader.reload
+end
+```
 
 On reloading, client code has to update anything that would otherwise be 
storing a stale object. For example, if the routing layer of a web framework 
stores reloadable controller class objects or instances in internal structures, 
on reload it has to refresh them somehow, possibly reevaluating routes.
 
@@ -905,10 +1033,39 @@
 loader.setup
 ```
 
+<a id="markdown-shadowed-files" name="shadowed-files"></a>
+### Shadowed files
+
+In Ruby, if you have several files called `foo.rb` in different directories of 
`$LOAD_PATH` and execute
+
+```ruby
+require "foo"
+```
+
+the first one found gets loaded, and the rest are ignored.
+
+Zeitwerk behaves in a similar way. If `foo.rb` is present in several root 
directories (at the same namespace level), the constant `Foo` is autoloaded 
from the first one, and the rest of the files are not evaluated. If logging is 
enabled, you'll see something like
+
+```
+file #{file} is ignored because #{previous_occurrence} has precedence
+```
+
+(This message is not public interface and may change, you cannot rely on that 
exact wording.)
+
+Even if there's only one `foo.rb`, if the constant `Foo` is already defined 
when Zeitwerk finds `foo.rb`, then the file is ignored too. This could happen 
if `Foo` was defined by a dependency, for example. If logging is enabled, 
you'll see something like
+
+```
+file #{file} is ignored because #{constant_path} is already defined
+```
+
+(This message is not public interface and may change, you cannot rely on that 
exact wording.)
+
+Shadowing only applies to Ruby files, namespace definition can be spread over 
multiple directories. And you can also reopen third-party namespaces if done 
[orderly](#reopening-third-party-namespaces).
+
 <a id="markdown-edge-cases" name="edge-cases"></a>
 ### Edge cases
 
-A class or module that acts as a namespace:
+[Explicit namespaces](#explicit-namespaces) like `Trip` here:
 
 ```ruby
 # trip.rb
@@ -922,7 +1079,7 @@
 end
 ```
 
-has to be defined with the `class` or `module` keywords, as in the example 
above.
+have to be defined with the `class`/`module` keywords, as in the example above.
 
 For technical reasons, raw constant assignment is not supported:
 
@@ -984,7 +1141,7 @@
 require "active_job/queue_adapters"
 
 require "zeitwerk"
-# By passign the flag, we acknowledge the extra directory lib/active_job
+# By passing the flag, we acknowledge the extra directory lib/active_job
 # has to be managed by the loader and no warning has to be issued for it.
 loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
 loader.setup
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/error.rb new/lib/zeitwerk/error.rb
--- old/lib/zeitwerk/error.rb   2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/error.rb   2022-11-08 09:02:43.000000000 +0100
@@ -12,4 +12,10 @@
 
   class NameError < ::NameError
   end
+
+  class SetupRequired < Error
+    def initialize
+      super("please, finish your configuration and call Zeitwerk::Loader#setup 
once all is ready")
+    end
+  end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/explicit_namespace.rb 
new/lib/zeitwerk/explicit_namespace.rb
--- old/lib/zeitwerk/explicit_namespace.rb      2022-10-01 00:18:05.000000000 
+0200
+++ new/lib/zeitwerk/explicit_namespace.rb      2022-11-08 09:02:43.000000000 
+0100
@@ -11,28 +11,28 @@
   module ExplicitNamespace # :nodoc: all
     class << self
       include RealModName
+      extend Internal
 
       # Maps constant paths that correspond to explicit namespaces according to
       # the file system, to the loader responsible for them.
       #
-      # @private
       # @sig Hash[String, Zeitwerk::Loader]
       attr_reader :cpaths
+      private :cpaths
 
-      # @private
       # @sig Mutex
       attr_reader :mutex
+      private :mutex
 
-      # @private
       # @sig TracePoint
       attr_reader :tracer
+      private :tracer
 
       # Asserts `cpath` corresponds to an explicit namespace for which `loader`
       # is responsible.
       #
-      # @private
       # @sig (String, Zeitwerk::Loader) -> void
-      def register(cpath, loader)
+      internal def register(cpath, loader)
         mutex.synchronize do
           cpaths[cpath] = loader
           # We check enabled? because, looking at the C source code, enabling 
an
@@ -41,24 +41,21 @@
         end
       end
 
-      # @private
       # @sig (Zeitwerk::Loader) -> void
-      def unregister_loader(loader)
+      internal def unregister_loader(loader)
         cpaths.delete_if { |_cpath, l| l == loader }
         disable_tracer_if_unneeded
       end
 
-      private
-
       # @sig () -> void
-      def disable_tracer_if_unneeded
+      private def disable_tracer_if_unneeded
         mutex.synchronize do
           tracer.disable if cpaths.empty?
         end
       end
 
       # @sig (TracePoint) -> void
-      def tracepoint_class_callback(event)
+      private def tracepoint_class_callback(event)
         # If the class is a singleton class, we won't do anything with it so we
         # can bail out immediately. This is several orders of magnitude faster
         # than accessing its name.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/gem_loader.rb 
new/lib/zeitwerk/gem_loader.rb
--- old/lib/zeitwerk/gem_loader.rb      2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/gem_loader.rb      2022-11-08 09:02:43.000000000 +0100
@@ -43,7 +43,7 @@
         next if abspath == expected_namespace_dir
 
         basename_without_ext = basename.delete_suffix(".rb")
-        cname = inflector.camelize(basename_without_ext, abspath)
+        cname = inflector.camelize(basename_without_ext, abspath).to_sym
         ftype = dir?(abspath) ? "directory" : "file"
 
         warn(<<~EOS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/internal.rb new/lib/zeitwerk/internal.rb
--- old/lib/zeitwerk/internal.rb        1970-01-01 01:00:00.000000000 +0100
+++ new/lib/zeitwerk/internal.rb        2022-11-08 09:02:43.000000000 +0100
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# This is a private module.
+module Zeitwerk::Internal
+  def internal(method_name)
+    private method_name
+
+    mangled = "__#{method_name}"
+    alias_method mangled, method_name
+    public mangled
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/kernel.rb new/lib/zeitwerk/kernel.rb
--- old/lib/zeitwerk/kernel.rb  2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/kernel.rb  2022-11-08 09:02:43.000000000 +0100
@@ -19,6 +19,9 @@
   # included in Object, and changes in ancestors don't get propagated into
   # already existing ancestor chains on Ruby < 3.0.
   alias_method :zeitwerk_original_require, :require
+  class << self
+    alias_method :zeitwerk_original_require, :require
+  end
 
   # @sig (String) -> true | false
   def require(path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/loader/callbacks.rb 
new/lib/zeitwerk/loader/callbacks.rb
--- old/lib/zeitwerk/loader/callbacks.rb        2022-10-01 00:18:05.000000000 
+0200
+++ new/lib/zeitwerk/loader/callbacks.rb        2022-11-08 09:02:43.000000000 
+0100
@@ -42,7 +42,7 @@
     # Without the mutex and subsequent delete call, t2 would reset the module.
     # That not only would reassign the constant (undesirable per se) but, 
worse,
     # the module object created by t2 wouldn't have any of the autoloads for 
its
-    # children, since t1 would have correctly deleted its lazy_subdirs entry.
+    # children, since t1 would have correctly deleted its namespace_dirs entry.
     mutex2.synchronize do
       if cref = autoloads.delete(dir)
         autovivified_module = cref[0].const_set(cref[1], Module.new)
@@ -71,9 +71,9 @@
   # @private
   # @sig (Module) -> void
   def on_namespace_loaded(namespace)
-    if subdirs = lazy_subdirs.delete(real_mod_name(namespace))
-      subdirs.each do |subdir|
-        set_autoloads_in_dir(subdir, namespace)
+    if dirs = namespace_dirs.delete(real_mod_name(namespace))
+      dirs.each do |dir|
+        set_autoloads_in_dir(dir, namespace)
       end
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/loader/config.rb 
new/lib/zeitwerk/loader/config.rb
--- old/lib/zeitwerk/loader/config.rb   2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/loader/config.rb   2022-11-08 09:02:43.000000000 +0100
@@ -4,86 +4,91 @@
 require "securerandom"
 
 module Zeitwerk::Loader::Config
-  # Absolute paths of the root directories. Stored in a hash to preserve
-  # order, easily handle duplicates, have a fast lookup needed for detecting
-  # nested paths, and store custom namespaces as values.
+  extend Zeitwerk::Internal
+  include Zeitwerk::RealModName
+
+  # @sig #camelize
+  attr_accessor :inflector
+
+  # @sig #call | #debug | nil
+  attr_accessor :logger
+
+  # Absolute paths of the root directories, mapped to their respective root 
namespaces:
   #
-  #   "/Users/fxn/blog/app/assets"   => Object,
   #   "/Users/fxn/blog/app/channels" => Object,
-  #   "/Users/fxn/blog/adapters"     => ActiveJob::QueueAdapters,
+  #   "/Users/fxn/blog/app/adapters" => ActiveJob::QueueAdapters,
   #   ...
   #
+  # Stored in a hash to preserve order, easily handle duplicates, and have a
+  # fast lookup by directory.
+  #
   # This is a private collection maintained by the loader. The public
   # interface for it is `push_dir` and `dirs`.
   #
-  # @private
   # @sig Hash[String, Module]
-  attr_reader :root_dirs
-
-  # @sig #camelize
-  attr_accessor :inflector
+  attr_reader :roots
+  internal :roots
 
   # Absolute paths of files, directories, or glob patterns to be totally
   # ignored.
   #
-  # @private
   # @sig Set[String]
   attr_reader :ignored_glob_patterns
+  private :ignored_glob_patterns
 
   # The actual collection of absolute file and directory names at the time the
   # ignored glob patterns were expanded. Computed on setup, and recomputed on
   # reload.
   #
-  # @private
   # @sig Set[String]
   attr_reader :ignored_paths
+  private :ignored_paths
 
   # Absolute paths of directories or glob patterns to be collapsed.
   #
-  # @private
   # @sig Set[String]
   attr_reader :collapse_glob_patterns
+  private :collapse_glob_patterns
 
   # The actual collection of absolute directory names at the time the collapse
   # glob patterns were expanded. Computed on setup, and recomputed on reload.
   #
-  # @private
   # @sig Set[String]
   attr_reader :collapse_dirs
+  private :collapse_dirs
 
   # Absolute paths of files or directories not to be eager loaded.
   #
-  # @private
   # @sig Set[String]
   attr_reader :eager_load_exclusions
+  private :eager_load_exclusions
 
   # User-oriented callbacks to be fired on setup and on reload.
   #
-  # @private
   # @sig Array[{ () -> void }]
   attr_reader :on_setup_callbacks
+  private :on_setup_callbacks
 
   # User-oriented callbacks to be fired when a constant is loaded.
   #
-  # @private
   # @sig Hash[String, Array[{ (Object, String) -> void }]]
   #      Hash[Symbol, Array[{ (String, Object, String) -> void }]]
   attr_reader :on_load_callbacks
+  private :on_load_callbacks
 
   # User-oriented callbacks to be fired before constants are removed.
   #
-  # @private
   # @sig Hash[String, Array[{ (Object, String) -> void }]]
   #      Hash[Symbol, Array[{ (String, Object, String) -> void }]]
   attr_reader :on_unload_callbacks
-
-  # @sig #call | #debug | nil
-  attr_accessor :logger
+  private :on_unload_callbacks
 
   def initialize
-    @initialized_at         = Time.now
-    @root_dirs              = {}
     @inflector              = Zeitwerk::Inflector.new
+    @logger                 = self.class.default_logger
+    @tag                    = SecureRandom.hex(3)
+    @initialized_at         = Time.now
+    @roots                  = {}
     @ignored_glob_patterns  = Set.new
     @ignored_paths          = Set.new
     @collapse_glob_patterns = Set.new
@@ -93,8 +98,6 @@
     @on_setup_callbacks     = []
     @on_load_callbacks      = {}
     @on_unload_callbacks    = {}
-    @logger                 = self.class.default_logger
-    @tag                    = SecureRandom.hex(3)
   end
 
   # Pushes `path` to the list of root directories.
@@ -111,10 +114,14 @@
       raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module 
object, should be"
     end
 
+    unless real_mod_name(namespace)
+      raise Zeitwerk::Error, "root namespaces cannot be anonymous"
+    end
+
     abspath = File.expand_path(path)
     if dir?(abspath)
       raise_if_conflicting_directory(abspath)
-      root_dirs[abspath] = namespace
+      roots[abspath] = namespace
     else
       raise Zeitwerk::Error, "the root directory #{abspath} does not exist"
     end
@@ -147,9 +154,9 @@
   # @sig () -> Array[String] | Hash[String, Module]
   def dirs(namespaces: false)
     if namespaces
-      root_dirs.clone
+      roots.clone
     else
-      root_dirs.keys
+      roots.keys
     end.freeze
   end
 
@@ -273,57 +280,76 @@
     @logger = ->(msg) { puts msg }
   end
 
-  # @private
+  # Returns true if the argument has been configured to be ignored, or is a
+  # descendant of an ignored directory.
+  #
   # @sig (String) -> bool
-  def ignores?(abspath)
-    ignored_paths.any? do |ignored_path|
-      ignored_path == abspath || (dir?(ignored_path) && 
abspath.start_with?(ignored_path + "/"))
+  internal def ignores?(abspath)
+    # Common use case.
+    return false if ignored_paths.empty?
+
+    walk_up(abspath) do |abspath|
+      return true  if ignored_path?(abspath)
+      return false if roots.key?(abspath)
     end
+
+    false
   end
 
-  private
+  # @sig (String) -> bool
+  private def ignored_path?(abspath)
+    ignored_paths.member?(abspath)
+  end
 
   # @sig () -> Array[String]
-  def actual_root_dirs
-    root_dirs.reject do |root_dir, _namespace|
-      !dir?(root_dir) || ignored_paths.member?(root_dir)
+  private def actual_roots
+    roots.reject do |root_dir, _root_namespace|
+      !dir?(root_dir) || ignored_path?(root_dir)
     end
   end
 
   # @sig (String) -> bool
-  def root_dir?(dir)
-    root_dirs.key?(dir)
+  private def root_dir?(dir)
+    roots.key?(dir)
   end
 
   # @sig (String) -> bool
-  def excluded_from_eager_load?(abspath)
-    eager_load_exclusions.member?(abspath)
+  private def excluded_from_eager_load?(abspath)
+    # Optimize this common use case.
+    return false if eager_load_exclusions.empty?
+
+    walk_up(abspath) do |abspath|
+      return true  if eager_load_exclusions.member?(abspath)
+      return false if roots.key?(abspath)
+    end
+
+    false
   end
 
   # @sig (String) -> bool
-  def collapse?(dir)
+  private def collapse?(dir)
     collapse_dirs.member?(dir)
   end
 
   # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
-  def expand_paths(paths)
+  private def expand_paths(paths)
     paths.flatten.map! { |path| File.expand_path(path) }
   end
 
   # @sig (Array[String]) -> Array[String]
-  def expand_glob_patterns(glob_patterns)
+  private def expand_glob_patterns(glob_patterns)
     # Note that Dir.glob works with regular file names just fine. That is,
     # glob patterns technically need no wildcards.
     glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
   end
 
   # @sig () -> void
-  def recompute_ignored_paths
+  private def recompute_ignored_paths
     ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
   end
 
   # @sig () -> void
-  def recompute_collapse_dirs
+  private def recompute_collapse_dirs
     collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/loader/eager_load.rb 
new/lib/zeitwerk/loader/eager_load.rb
--- old/lib/zeitwerk/loader/eager_load.rb       1970-01-01 01:00:00.000000000 
+0100
+++ new/lib/zeitwerk/loader/eager_load.rb       2022-11-08 09:02:43.000000000 
+0100
@@ -0,0 +1,228 @@
+module Zeitwerk::Loader::EagerLoad
+  # Eager loads all files in the root directories, recursively. Files do not
+  # need to be in `$LOAD_PATH`, absolute file names are used. Ignored and
+  # shadowed files are not eager loaded. You can opt-out specifically in
+  # specific files and directories with `do_not_eager_load`, and that can be
+  # overridden passing `force: true`.
+  #
+  # @sig (true | false) -> void
+  def eager_load(force: false)
+    mutex.synchronize do
+      break if @eager_loaded
+      raise Zeitwerk::SetupRequired unless @setup
+
+      log("eager load start") if logger
+
+      actual_roots.each do |root_dir, root_namespace|
+        actual_eager_load_dir(root_dir, root_namespace, force: force)
+      end
+
+      autoloaded_dirs.each do |autoloaded_dir|
+        Zeitwerk::Registry.unregister_autoload(autoloaded_dir)
+      end
+      autoloaded_dirs.clear
+
+      @eager_loaded = true
+
+      log("eager load end") if logger
+    end
+  end
+
+  # @sig (String | Pathname) -> void
+  def eager_load_dir(path)
+    raise Zeitwerk::SetupRequired unless @setup
+
+    abspath = File.expand_path(path)
+
+    raise Zeitwerk::Error.new("#{abspath} is not a directory") unless 
dir?(abspath)
+
+    cnames = []
+
+    root_namespace = nil
+    walk_up(abspath) do |dir|
+      return if ignored_path?(dir)
+      return if eager_load_exclusions.member?(dir)
+
+      break if root_namespace = roots[dir]
+
+      unless collapse?(dir)
+        basename = File.basename(dir)
+        cnames << inflector.camelize(basename, dir).to_sym
+      end
+    end
+
+    raise Zeitwerk::Error.new("I do not manage #{abspath}") unless 
root_namespace
+
+    return if @eager_loaded
+
+    namespace = root_namespace
+    cnames.reverse_each do |cname|
+      # Can happen if there are no Ruby files. This is not an error condition,
+      # the directory is actually managed. Could have Ruby files later.
+      return unless cdef?(namespace, cname)
+      namespace = cget(namespace, cname)
+    end
+
+    # A shortcircuiting test depends on the invocation of this method. Please
+    # keep them in sync if refactored.
+    actual_eager_load_dir(abspath, namespace)
+  end
+
+  # @sig (Module) -> void
+  def eager_load_namespace(mod)
+    raise Zeitwerk::SetupRequired unless @setup
+
+    unless mod.is_a?(Module)
+      raise Zeitwerk::Error, "#{mod.inspect} is not a class or module object"
+    end
+
+    return if @eager_loaded
+
+    mod_name = real_mod_name(mod)
+    return unless mod_name
+
+    actual_roots.each do |root_dir, root_namespace|
+      if mod.equal?(Object)
+        # A shortcircuiting test depends on the invocation of this method.
+        # Please keep them in sync if refactored.
+        actual_eager_load_dir(root_dir, root_namespace)
+      elsif root_namespace.equal?(Object)
+        eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
+      else
+        root_namespace_name = real_mod_name(root_namespace)
+        if root_namespace_name.start_with?(mod_name + "::")
+          actual_eager_load_dir(root_dir, root_namespace)
+        elsif mod_name == root_namespace_name
+          actual_eager_load_dir(root_dir, root_namespace)
+        elsif mod_name.start_with?(root_namespace_name + "::")
+          eager_load_child_namespace(mod, mod_name, root_dir, root_namespace)
+        else
+          # Unrelated constant hierarchies, do nothing.
+        end
+      end
+    end
+  end
+
+  # Loads the given Ruby file.
+  #
+  # Raises if the argument is ignored, shadowed, or not managed by the 
receiver.
+  #
+  # The method is implemented as `constantize` for files, in a sense, to be 
able
+  # to descend orderly and make sure the file is loadable.
+  #
+  # @sig (String | Pathname) -> void
+  def load_file(path)
+    abspath = File.expand_path(path)
+
+    raise Zeitwerk::Error.new("#{abspath} does not exist") unless 
File.exist?(abspath)
+    raise Zeitwerk::Error.new("#{abspath} is not a Ruby file") if 
dir?(abspath) || !ruby?(abspath)
+    raise Zeitwerk::Error.new("#{abspath} is ignored") if 
ignored_path?(abspath)
+
+    basename = File.basename(abspath, ".rb")
+    base_cname = inflector.camelize(basename, abspath).to_sym
+
+    root_namespace = nil
+    cnames = []
+
+    walk_up(File.dirname(abspath)) do |dir|
+      raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(dir)
+
+      break if root_namespace = roots[dir]
+
+      unless collapse?(dir)
+        basename = File.basename(dir)
+        cnames << inflector.camelize(basename, dir).to_sym
+      end
+    end
+
+    raise Zeitwerk::Error.new("I do not manage #{abspath}") unless 
root_namespace
+
+    namespace = root_namespace
+    cnames.reverse_each do |cname|
+      namespace = cget(namespace, cname)
+    end
+
+    raise Zeitwerk::Error.new("#{abspath} is shadowed") if 
shadowed_file?(abspath)
+
+    cget(namespace, base_cname)
+  end
+
+  # The caller is responsible for making sure `namespace` is the namespace that
+  # corresponds to `dir`.
+  #
+  # @sig (String, Module, Boolean) -> void
+  private def actual_eager_load_dir(dir, namespace, force: false)
+    honour_exclusions = !force
+    return if honour_exclusions && excluded_from_eager_load?(dir)
+
+    log("eager load directory #{dir} start") if logger
+
+    queue = [[dir, namespace]]
+    while to_eager_load = queue.shift
+      dir, namespace = to_eager_load
+
+      ls(dir) do |basename, abspath|
+        next if honour_exclusions && eager_load_exclusions.member?(abspath)
+
+        if ruby?(abspath)
+          if (cref = autoloads[abspath]) && !shadowed_file?(abspath)
+            cget(*cref)
+          end
+        else
+          if collapse?(abspath)
+            queue << [abspath, namespace]
+          else
+            cname = inflector.camelize(basename, abspath).to_sym
+            queue << [abspath, cget(namespace, cname)]
+          end
+        end
+      end
+    end
+
+    log("eager load directory #{dir} end") if logger
+  end
+
+  # In order to invoke this method, the caller has to ensure `child` is a
+  # strict namespace descendendant of `root_namespace`.
+  #
+  # @sig (Module, String, Module, Boolean) -> void
+  private def eager_load_child_namespace(child, child_name, root_dir, 
root_namespace)
+    suffix = child_name
+    unless root_namespace.equal?(Object)
+      suffix = suffix.delete_prefix(real_mod_name(root_namespace) + "::")
+    end
+
+    # These directories are at the same namespace level, there may be more if
+    # we find collapsed ones. As we scan, we look for matches for the first
+    # segment, and store them in `next_dirs`. If there are any, we look for
+    # the next segments in those matches. Repeat.
+    #
+    # If we exhaust the search locating directories that match all segments,
+    # we just need to eager load those ones.
+    dirs = [root_dir]
+    next_dirs = []
+
+    suffix.split("::").each do |segment|
+      while dir = dirs.shift
+        ls(dir) do |basename, abspath|
+          next unless dir?(abspath)
+
+          if collapse?(abspath)
+            dirs << abspath
+          elsif segment == inflector.camelize(basename, abspath)
+            next_dirs << abspath
+          end
+        end
+      end
+
+      return if next_dirs.empty?
+
+      dirs.replace(next_dirs)
+      next_dirs.clear
+    end
+
+    dirs.each do |dir|
+      actual_eager_load_dir(dir, child)
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/loader/helpers.rb 
new/lib/zeitwerk/loader/helpers.rb
--- old/lib/zeitwerk/loader/helpers.rb  2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/loader/helpers.rb  2022-11-08 09:02:43.000000000 +0100
@@ -1,12 +1,10 @@
 # frozen_string_literal: true
 
 module Zeitwerk::Loader::Helpers
-  private
-
   # --- Logging 
-----------------------------------------------------------------------------------
 
   # @sig (String) -> void
-  def log(message)
+  private def log(message)
     method_name = logger.respond_to?(:debug) ? :debug : :call
     logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
   end
@@ -14,7 +12,7 @@
   # --- Files and directories 
---------------------------------------------------------------------
 
   # @sig (String) { (String, String) -> void } -> void
-  def ls(dir)
+  private def ls(dir)
     children = Dir.children(dir)
 
     # The order in which a directory is listed depends on the file system.
@@ -28,10 +26,11 @@
       next if hidden?(basename)
 
       abspath = File.join(dir, basename)
-      next if ignored_paths.member?(abspath)
+      next if ignored_path?(abspath)
 
       if dir?(abspath)
-        next unless has_at_least_one_ruby_file?(abspath)
+        next if roots.key?(abspath)
+        next if !has_at_least_one_ruby_file?(abspath)
       else
         next unless ruby?(abspath)
       end
@@ -43,7 +42,7 @@
   end
 
   # @sig (String) -> bool
-  def has_at_least_one_ruby_file?(dir)
+  private def has_at_least_one_ruby_file?(dir)
     to_visit = [dir]
 
     while dir = to_visit.shift
@@ -60,20 +59,29 @@
   end
 
   # @sig (String) -> bool
-  def ruby?(path)
+  private def ruby?(path)
     path.end_with?(".rb")
   end
 
   # @sig (String) -> bool
-  def dir?(path)
+  private def dir?(path)
     File.directory?(path)
   end
 
   # @sig (String) -> bool
-  def hidden?(basename)
+  private def hidden?(basename)
     basename.start_with?(".")
   end
 
+  # @sig (String) { (String) -> void } -> void
+  private def walk_up(abspath)
+    loop do
+      yield abspath
+      abspath, basename = File.split(abspath)
+      break if basename == "/"
+    end
+  end
+
   # --- Constants 
---------------------------------------------------------------------------------
 
   # The autoload? predicate takes into account the ancestor chain of the
@@ -94,11 +102,11 @@
   #
   # @sig (Module, Symbol) -> String?
   if method(:autoload?).arity == 1
-    def strict_autoload_path(parent, cname)
+    private def strict_autoload_path(parent, cname)
       parent.autoload?(cname) if cdef?(parent, cname)
     end
   else
-    def strict_autoload_path(parent, cname)
+    private def strict_autoload_path(parent, cname)
       parent.autoload?(cname, false)
     end
   end
@@ -107,23 +115,23 @@
   if Symbol.method_defined?(:name)
     # Symbol#name was introduced in Ruby 3.0. It returns always the same
     # frozen object, so we may save a few string allocations.
-    def cpath(parent, cname)
+    private def cpath(parent, cname)
       Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}"
     end
   else
-    def cpath(parent, cname)
+    private def cpath(parent, cname)
       Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
     end
   end
 
   # @sig (Module, Symbol) -> bool
-  def cdef?(parent, cname)
+  private def cdef?(parent, cname)
     parent.const_defined?(cname, false)
   end
 
   # @raise [NameError]
   # @sig (Module, Symbol) -> Object
-  def cget(parent, cname)
+  private def cget(parent, cname)
     parent.const_get(cname, false)
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/loader.rb new/lib/zeitwerk/loader.rb
--- old/lib/zeitwerk/loader.rb  2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/loader.rb  2022-11-08 09:02:43.000000000 +0100
@@ -7,11 +7,13 @@
     require_relative "loader/helpers"
     require_relative "loader/callbacks"
     require_relative "loader/config"
+    require_relative "loader/eager_load"
 
     include RealModName
     include Callbacks
     include Helpers
     include Config
+    include EagerLoad
 
     MUTEX = Mutex.new
     private_constant :MUTEX
@@ -54,7 +56,7 @@
     # @sig Hash[String, [String, [Module, Symbol]]]
     attr_reader :to_unload
 
-    # Maps constant paths of namespaces to arrays of corresponding directories.
+    # Maps namespace constant paths to their respective directories.
     #
     # For example, given this mapping:
     #
@@ -64,13 +66,24 @@
     #     ...
     #   ]
     #
-    # when `Admin` gets defined we know that it plays the role of a namespace 
and
-    # that its children are spread over those directories. We'll visit them to 
set
-    # up the corresponding autoloads.
+    # when `Admin` gets defined we know that it plays the role of a namespace
+    # and that its children are spread over those directories. We'll visit them
+    # to set up the corresponding autoloads.
     #
     # @private
     # @sig Hash[String, Array[String]]
-    attr_reader :lazy_subdirs
+    attr_reader :namespace_dirs
+
+    # A shadowed file is a file managed by this loader that is ignored when
+    # setting autoloads because its matching constant is already taken.
+    #
+    # This private set is populated as we descend. For example, if the loader
+    # has only scanned the top-level, `shadowed_files` does not have shadowed
+    # files that may exist deep in the project tree yet.
+    #
+    # @private
+    # @sig Set[String]
+    attr_reader :shadowed_files
 
     # @private
     # @sig Mutex
@@ -86,7 +99,8 @@
       @autoloads       = {}
       @autoloaded_dirs = []
       @to_unload       = {}
-      @lazy_subdirs    = Hash.new { |h, cpath| h[cpath] = [] }
+      @namespace_dirs  = Hash.new { |h, cpath| h[cpath] = [] }
+      @shadowed_files  = Set.new
       @mutex           = Mutex.new
       @mutex2          = Mutex.new
       @setup           = false
@@ -95,15 +109,15 @@
       Registry.register_loader(self)
     end
 
-    # Sets autoloads in the root namespace.
+    # Sets autoloads in the root namespaces.
     #
     # @sig () -> void
     def setup
       mutex.synchronize do
         break if @setup
 
-        actual_root_dirs.each do |root_dir, namespace|
-          set_autoloads_in_dir(root_dir, namespace)
+        actual_roots.each do |root_dir, root_namespace|
+          set_autoloads_in_dir(root_dir, root_namespace)
         end
 
         on_setup_callbacks.each(&:call)
@@ -126,6 +140,8 @@
     # @sig () -> void
     def unload
       mutex.synchronize do
+        raise SetupRequired unless @setup
+
         # We are going to keep track of the files that were required by our
         # autoloads to later remove them from $LOADED_FEATURES, thus making 
them
         # loadable by Kernel#require again.
@@ -181,10 +197,11 @@
         autoloads.clear
         autoloaded_dirs.clear
         to_unload.clear
-        lazy_subdirs.clear
+        namespace_dirs.clear
+        shadowed_files.clear
 
         Registry.on_unload(self)
-        ExplicitNamespace.unregister_loader(self)
+        ExplicitNamespace.__unregister_loader(self)
 
         @setup        = false
         @eager_loaded = false
@@ -201,6 +218,7 @@
     # @sig () -> void
     def reload
       raise ReloadingDisabledError unless reloading_enabled?
+      raise SetupRequired unless @setup
 
       unload
       recompute_ignored_paths
@@ -208,58 +226,6 @@
       setup
     end
 
-    # Eager loads all files in the root directories, recursively. Files do not
-    # need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
-    # are not eager loaded. You can opt-out specifically in specific files and
-    # directories with `do_not_eager_load`, and that can be overridden passing
-    # `force: true`.
-    #
-    # @sig (true | false) -> void
-    def eager_load(force: false)
-      mutex.synchronize do
-        break if @eager_loaded
-
-        log("eager load start") if logger
-
-        honour_exclusions = !force
-
-        queue = []
-        actual_root_dirs.each do |root_dir, namespace|
-          queue << [namespace, root_dir] unless honour_exclusions && 
excluded_from_eager_load?(root_dir)
-        end
-
-        while to_eager_load = queue.shift
-          namespace, dir = to_eager_load
-
-          ls(dir) do |basename, abspath|
-            next if honour_exclusions && excluded_from_eager_load?(abspath)
-
-            if ruby?(abspath)
-              if cref = autoloads[abspath]
-                cget(*cref)
-              end
-            elsif !root_dirs.key?(abspath)
-              if collapse?(abspath)
-                queue << [namespace, abspath]
-              else
-                cname = inflector.camelize(basename, abspath)
-                queue << [cget(namespace, cname), abspath]
-              end
-            end
-          end
-        end
-
-        autoloaded_dirs.each do |autoloaded_dir|
-          Registry.unregister_autoload(autoloaded_dir)
-        end
-        autoloaded_dirs.clear
-
-        @eager_loaded = true
-
-        log("eager load end") if logger
-      end
-    end
-
     # Says if the given constant path would be unloaded on reload. This
     # predicate returns `false` if reloading is disabled.
     #
@@ -282,7 +248,16 @@
     # @sig () -> void
     def unregister
       Registry.unregister_loader(self)
-      ExplicitNamespace.unregister_loader(self)
+      ExplicitNamespace.__unregister_loader(self)
+    end
+
+    # The return value of this predicate is only meaningful if the loader has
+    # scanned the file. This is the case in the spots where we use it.
+    #
+    # @private
+    # @sig (String) -> Boolean
+    def shadowed_file?(file)
+      shadowed_files.member?(file)
     end
 
     # --- Class methods 
---------------------------------------------------------------------------
@@ -311,11 +286,32 @@
         Registry.loader_for_gem(called_from, warn_on_extra_files: 
warn_on_extra_files)
       end
 
-      # Broadcasts `eager_load` to all loaders.
+      # Broadcasts `eager_load` to all loaders. Those that have not been setup
+      # are skipped.
       #
       # @sig () -> void
       def eager_load_all
-        Registry.loaders.each(&:eager_load)
+        Registry.loaders.each do |loader|
+          begin
+            loader.eager_load
+          rescue SetupRequired
+            # This is fine, we eager load what can be eager loaded.
+          end
+        end
+      end
+
+      # Broadcasts `eager_load_namespace` to all loaders. Those that have not
+      # been setup are skipped.
+      #
+      # @sig (Module) -> void
+      def eager_load_namespace(mod)
+        Registry.loaders.each do |loader|
+          begin
+            loader.eager_load_namespace(mod)
+          rescue SetupRequired
+            # This is fine, we eager load what can be eager loaded.
+          end
+        end
       end
 
       # Returns an array with the absolute paths of the root directories of all
@@ -338,19 +334,11 @@
             cname = inflector.camelize(basename, abspath).to_sym
             autoload_file(parent, cname, abspath)
           else
-            # In a Rails application, `app/models/concerns` is a subdirectory 
of
-            # `app/models`, but both of them are root directories.
-            #
-            # To resolve the ambiguity file name -> constant path this 
introduces,
-            # the `app/models/concerns` directory is totally ignored as a 
namespace,
-            # it counts only as root. The guard checks that.
-            unless root_dir?(abspath)
+            if collapse?(abspath)
+              set_autoloads_in_dir(abspath, parent)
+            else
               cname = inflector.camelize(basename, abspath).to_sym
-              if collapse?(abspath)
-                set_autoloads_in_dir(abspath, parent)
-              else
-                autoload_subdir(parent, cname, abspath)
-              end
+              autoload_subdir(parent, cname, abspath)
             end
           end
         rescue ::NameError => error
@@ -380,10 +368,10 @@
         # We do not need to issue another autoload, the existing one is enough
         # no matter if it is for a file or a directory. Just remember the
         # subdirectory has to be visited if the namespace is used.
-        lazy_subdirs[cpath] << subdir
+        namespace_dirs[cpath] << subdir
       elsif !cdef?(parent, cname)
         # First time we find this namespace, set an autoload for it.
-        lazy_subdirs[cpath(parent, cname)] << subdir
+        namespace_dirs[cpath(parent, cname)] << subdir
         set_autoload(parent, cname, subdir)
       else
         # For whatever reason the constant that corresponds to this namespace 
has
@@ -398,6 +386,7 @@
       if autoload_path = strict_autoload_path(parent, cname) || 
Registry.inception?(cpath(parent, cname))
         # First autoload for a Ruby file wins, just ignore subsequent ones.
         if ruby?(autoload_path)
+          shadowed_files << file
           log("file #{file} is ignored because #{autoload_path} has 
precedence") if logger
         else
           promote_namespace_from_implicit_to_explicit(
@@ -408,6 +397,7 @@
           )
         end
       elsif cdef?(parent, cname)
+        shadowed_files << file
         log("file #{file} is ignored because #{cpath(parent, cname)} is 
already defined") if logger
       else
         set_autoload(parent, cname, file)
@@ -460,25 +450,26 @@
 
     # @sig (String) -> void
     def register_explicit_namespace(cpath)
-      ExplicitNamespace.register(cpath, self)
+      ExplicitNamespace.__register(cpath, self)
     end
 
     # @sig (String) -> void
     def raise_if_conflicting_directory(dir)
       MUTEX.synchronize do
+        dir_slash = dir + "/"
+
         Registry.loaders.each do |loader|
           next if loader == self
-          next if loader.ignores?(dir)
+          next if loader.__ignores?(dir)
 
-          dir = dir + "/"
-          loader.root_dirs.each do |root_dir, _namespace|
+          loader.__roots.each_key do |root_dir|
             next if ignores?(root_dir)
 
-            root_dir = root_dir + "/"
-            if dir.start_with?(root_dir) || root_dir.start_with?(dir)
+            root_dir_slash = root_dir + "/"
+            if dir_slash.start_with?(root_dir_slash) || 
root_dir_slash.start_with?(dir_slash)
               require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
               raise Error,
-                "loader\n\n#{pretty_inspect}\n\nwants to manage directory 
#{dir.chop}," \
+                "loader\n\n#{pretty_inspect}\n\nwants to manage directory 
#{dir}," \
                 " which is already managed by\n\n#{loader.pretty_inspect}\n"
               EOS
             end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk/version.rb new/lib/zeitwerk/version.rb
--- old/lib/zeitwerk/version.rb 2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk/version.rb 2022-11-08 09:02:43.000000000 +0100
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Zeitwerk
-  VERSION = "2.6.1"
+  VERSION = "2.6.6"
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/zeitwerk.rb new/lib/zeitwerk.rb
--- old/lib/zeitwerk.rb 2022-10-01 00:18:05.000000000 +0200
+++ new/lib/zeitwerk.rb 2022-11-08 09:02:43.000000000 +0100
@@ -2,6 +2,7 @@
 
 module Zeitwerk
   require_relative "zeitwerk/real_mod_name"
+  require_relative "zeitwerk/internal"
   require_relative "zeitwerk/loader"
   require_relative "zeitwerk/gem_loader"
   require_relative "zeitwerk/registry"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2022-10-01 00:18:05.000000000 +0200
+++ new/metadata        2022-11-08 09:02:43.000000000 +0100
@@ -1,14 +1,14 @@
 --- !ruby/object:Gem::Specification
 name: zeitwerk
 version: !ruby/object:Gem::Version
-  version: 2.6.1
+  version: 2.6.6
 platform: ruby
 authors:
 - Xavier Noria
 autorequire:
 bindir: bin
 cert_chain: []
-date: 2022-09-30 00:00:00.000000000 Z
+date: 2022-11-08 00:00:00.000000000 Z
 dependencies: []
 description: |2
       Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -28,10 +28,12 @@
 - lib/zeitwerk/gem_inflector.rb
 - lib/zeitwerk/gem_loader.rb
 - lib/zeitwerk/inflector.rb
+- lib/zeitwerk/internal.rb
 - lib/zeitwerk/kernel.rb
 - lib/zeitwerk/loader.rb
 - lib/zeitwerk/loader/callbacks.rb
 - lib/zeitwerk/loader/config.rb
+- lib/zeitwerk/loader/eager_load.rb
 - lib/zeitwerk/loader/helpers.rb
 - lib/zeitwerk/real_mod_name.rb
 - lib/zeitwerk/registry.rb

Reply via email to