class Zeitwerk::Loader

def all_dirs

@sig () -> Array[String]

registered loaders. This is a read-only collection.
Returns an array with the absolute paths of the root directories of all
def all_dirs
  Registry.loaders.flat_map(&:dirs).freeze
end

def autoload_file(parent, cname, file)

@sig (Module, Symbol, String) -> void
def autoload_file(parent, cname, file)
oload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
rst autoload for a Ruby file wins, just ignore subsequent ones.
uby?(autoload_path)
adowed_files << file
g("file #{file} is ignored because #{autoload_path} has precedence") if logger

omote_namespace_from_implicit_to_explicit(
dir:    autoload_path,
file:   file,
parent: parent,
cname:  cname
cdef?(parent, cname)
owed_files << file
"file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
ne_autoload(parent, cname, file)

def autoload_path_set_by_me_for?(parent, cname)

@sig (Module, Symbol) -> String?
def autoload_path_set_by_me_for?(parent, cname)
oload_path = strict_autoload_path(parent, cname)
load_path if autoloads.key?(autoload_path)
stry.inception?(cpath(parent, cname))

def autoload_subdir(parent, cname, subdir)

@sig (Module, Symbol, String) -> void
def autoload_subdir(parent, cname, subdir)
oload_path = autoload_path_set_by_me_for?(parent, cname)
h = cpath(parent, cname)
uby?(autoload_path)
Scanning visited a Ruby file first, and now a directory for the same
constant has been found. This means we are dealing with an explicit
namespace whose definition was seen first.
Registering is idempotent, and we have to keep the autoload pointing
to the file. This may run again if more directories are found later
on, no big deal.
gister_explicit_namespace(cpath)
 the existing autoload points to a file, it has to be preserved, if
t, it is fine as it is. In either case, we do not need to override.
st remember the subdirectory conforms this namespace.
space_dirs[cpath] << subdir
!cdef?(parent, cname)
rst time we find this namespace, set an autoload for it.
space_dirs[cpath(parent, cname)] << subdir
ne_autoload(parent, cname, subdir)
r whatever reason the constant that corresponds to this namespace has
ready been defined, we have to recurse.
"the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
ne_autoloads_for_dir(subdir, cget(parent, cname))

def cpath_expected_at(path)

@sig (String | Pathname) -> String?
def cpath_expected_at(path)
  abspath = File.expand_path(path)
  raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
  return unless dir?(abspath) || ruby?(abspath)
  return if ignored_path?(abspath)
  paths = []
  if ruby?(abspath)
    basename = File.basename(abspath, ".rb")
    return if hidden?(basename)
    paths << [basename, abspath]
    walk_up_from = File.dirname(abspath)
  else
    walk_up_from = abspath
  end
  root_namespace = nil
  walk_up(walk_up_from) do |dir|
    break if root_namespace = roots[dir]
    return if ignored_path?(dir)
    basename = File.basename(dir)
    return if hidden?(basename)
    paths << [basename, abspath] unless collapse?(dir)
  end
  return unless root_namespace
  if paths.empty?
    real_mod_name(root_namespace)
  else
    cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
    if root_namespace == Object
      cnames.join("::")
    else
      "#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
    end
  end
end

def define_autoload(parent, cname, abspath)

@sig (Module, Symbol, String) -> void
def define_autoload(parent, cname, abspath)
.autoload(cname, abspath)
ger
uby?(abspath)
g("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")

g("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
ads[abspath] = [parent, cname]
ry.register_autoload(self, abspath)
why in the documentation of Zeitwerk::Registry.inceptions.
 parent.autoload?(cname)
stry.register_inception(cpath(parent, cname), abspath, self)

def define_autoloads_for_dir(dir, parent)

@sig (String, Module) -> void
def define_autoloads_for_dir(dir, parent)
) do |basename, abspath|
uby?(basename)
sename.delete_suffix!(".rb")
toload_file(parent, cname_for(basename, abspath), abspath)

 collapse?(abspath)
define_autoloads_for_dir(abspath, parent)
se
autoload_subdir(parent, cname_for(basename, abspath), abspath)
d

def eager_load_all

@sig () -> void

are skipped.
Broadcasts `eager_load` to all loaders. Those that have not been setup
def eager_load_all
  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

def eager_load_namespace(mod)

@sig (Module) -> void

been setup are skipped.
Broadcasts `eager_load_namespace` to all loaders. Those that have not
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

def for_gem(warn_on_extra_files: true)

@sig (bool) -> Zeitwerk::GemLoader

is private, client code can only rely on the interface.
This method returns a subclass of Zeitwerk::Loader, but the exact type

the same file, in the unlikely case the gem wants to be able to reload.
except that this method returns the same object in subsequent calls from

loader.push_dir(__dir__)
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.tag = File.basename(__FILE__, ".rb")
loader = Zeitwerk::Loader.new

require "zeitwerk"

This is a shortcut for
def for_gem(warn_on_extra_files: true)
  called_from = caller_locations(1, 1).first.path
  Registry.loader_for_gem(called_from, namespace: Object, warn_on_extra_files: warn_on_extra_files)
end

def for_gem_extension(namespace)

@sig (bool) -> Zeitwerk::GemLoader

is private, client code can only rely on the interface.
This method returns a subclass of Zeitwerk::Loader, but the exact type

the same file, in the unlikely case the gem wants to be able to reload.
except that this method returns the same object in subsequent calls from

loader.push_dir(__dir__, namespace: namespace)
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
loader.tag = namespace.name + "-" + File.basename(__FILE__, ".rb")
loader = Zeitwerk::Loader.new

require "zeitwerk"

This is a shortcut for
def for_gem_extension(namespace)
  unless namespace.is_a?(Module) # Note that Class < Module.
    raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
  end
  unless real_mod_name(namespace)
    raise Zeitwerk::Error, "extending anonymous namespaces is unsupported"
  end
  called_from = caller_locations(1, 1).first.path
  Registry.loader_for_gem(called_from, namespace: namespace, warn_on_extra_files: false)
end

def initialize

def initialize
  super
  @autoloads       = {}
  @autoloaded_dirs = []
  @to_unload       = {}
  @namespace_dirs  = Hash.new { |h, cpath| h[cpath] = [] }
  @shadowed_files  = Set.new
  @setup           = false
  @eager_loaded    = false
  @mutex = Mutex.new
  @dirs_autoload_monitor = Monitor.new
  Registry.register_loader(self)
end

def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)

@sig (dir: String, file: String, parent: Module, cname: Symbol) -> void

the file where we've found the namespace is explicitly defined.
`dir` is the directory that would have autovivified a namespace. `file` is
def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
ads.delete(dir)
ry.unregister_autoload(dir)
arlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
_autoload(parent, cname, file)
er_explicit_namespace(cpath(parent, cname))

def raise_if_conflicting_directory(dir)

@sig (String) -> void
def raise_if_conflicting_directory(dir)
synchronize do
slash = dir + "/"
stry.loaders.each do |loader|
xt if loader == self
xt if loader.__ignores?(dir)
ader.__roots.each_key do |root_dir|
next if ignores?(root_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}," \
    " which is already managed by\n\n#{loader.pretty_inspect}\n"
end
d

def register_explicit_namespace(cpath)

@sig (String) -> void
def register_explicit_namespace(cpath)
itNamespace.__register(cpath, self)

def reload

Raises:
  • (Zeitwerk::Error) -
def reload
  raise ReloadingDisabledError unless reloading_enabled?
  raise SetupRequired unless @setup
  unload
  recompute_ignored_paths
  recompute_collapse_dirs
  setup
end

def run_on_unload_callbacks(cpath, value, abspath)

@sig (String, Object, String) -> void
def run_on_unload_callbacks(cpath, value, abspath)
r matters. If present, run the most specific one.
oad_callbacks[cpath]&.each { |c| c.call(value, abspath) }
oad_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }

def setup

@sig () -> void

Sets autoloads in the root namespaces.
def setup
  mutex.synchronize do
    break if @setup
    actual_roots.each do |root_dir, root_namespace|
      define_autoloads_for_dir(root_dir, root_namespace)
    end
    on_setup_callbacks.each(&:call)
    @setup = true
  end
end

def shadowed_file?(file)

@sig (String) -> Boolean

scanned the file. This is the case in the spots where we use it.
The return value of this predicate is only meaningful if the loader has
def shadowed_file?(file)
d_files.member?(file)

def unload

@sig () -> void

`unregister`, which is undocumented too.
means `unload` + `setup`. This one is available to be used together with
This method is public but undocumented. Main interface is `reload`, which

unload them.
else, they are eligible for garbage collection, which would effectively
addition, since said objects are normally not referenced from anywhere
The objects the constants stored are no longer reachable through them. In

Removes loaded constants and configured autoloads.
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.
    #
    # Directories are not stored in $LOADED_FEATURES, keeping track of files
    # is enough.
    unloaded_files = Set.new
    autoloads.each do |abspath, (parent, cname)|
      if parent.autoload?(cname)
        unload_autoload(parent, cname)
      else
        # Could happen if loaded with require_relative. That is unsupported,
        # and the constant path would escape unloadable_cpath? This is just
        # defensive code to clean things up as much as we are able to.
        unload_cref(parent, cname)
        unloaded_files.add(abspath) if ruby?(abspath)
      end
    end
    to_unload.each do |cpath, (abspath, (parent, cname))|
      unless on_unload_callbacks.empty?
        begin
          value = cget(parent, cname)
        rescue ::NameError
          # Perhaps the user deleted the constant by hand, or perhaps an
          # autoload failed to define the expected constant but the user
          # rescued the exception.
        else
          run_on_unload_callbacks(cpath, value, abspath)
        end
      end
      unload_cref(parent, cname)
      unloaded_files.add(abspath) if ruby?(abspath)
    end
    unless unloaded_files.empty?
      # Bootsnap decorates Kernel#require to speed it up using a cache and
      # this optimization does not check if $LOADED_FEATURES has the file.
      #
      # To make it aware of changes, the gem defines singleton methods in
      # $LOADED_FEATURES:
      #
      #   https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb
      #
      # Rails applications may depend on bootsnap, so for unloading to work
      # in that setting it is preferable that we restrict our API choice to
      # one of those methods.
      $LOADED_FEATURES.reject! { |file| unloaded_files.member?(file) }
    end
    autoloads.clear
    autoloaded_dirs.clear
    to_unload.clear
    namespace_dirs.clear
    shadowed_files.clear
    Registry.on_unload(self)
    ExplicitNamespace.__unregister_loader(self)
    @setup        = false
    @eager_loaded = false
  end
end

def unload_autoload(parent, cname)

@sig (Module, Symbol) -> void
def unload_autoload(parent, cname)
arent, cname)
utoload for #{cpath(parent, cname)} removed") if logger

def unload_cref(parent, cname)

@sig (Module, Symbol) -> void
def unload_cref(parent, cname)
s optimistically remove_const. The way we use it, this is going to
eed always if all is good.
arent, cname)
:NameError
e are a few edge scenarios in which this may happen. If the constant
one, that is OK, anyway.
{cpath(parent, cname)} unloaded") if logger

def unloadable_cpath?(cpath)

@sig (String) -> bool

predicate returns `false` if reloading is disabled.
Says if the given constant path would be unloaded on reload. This
def unloadable_cpath?(cpath)
  to_unload.key?(cpath)
end

def unloadable_cpaths

@sig () -> Array[String]

This predicate returns an empty array if reloading is disabled.
Returns an array with the constant paths that would be unloaded on reload.
def unloadable_cpaths
  to_unload.keys.freeze
end

def unregister

@sig () -> void
@experimental

This is a dangerous method.
def unregister
  Registry.unregister_loader(self)
  ExplicitNamespace.__unregister_loader(self)
end