zeitwerk 2.4.2 → 2.5.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +163 -60
 - data/lib/zeitwerk.rb +2 -0
 - data/lib/zeitwerk/autoloads.rb +69 -0
 - data/lib/zeitwerk/explicit_namespace.rb +8 -2
 - data/lib/zeitwerk/kernel.rb +5 -4
 - data/lib/zeitwerk/loader.rb +75 -412
 - data/lib/zeitwerk/loader/callbacks.rb +9 -8
 - data/lib/zeitwerk/loader/config.rb +308 -0
 - data/lib/zeitwerk/loader/helpers.rb +95 -0
 - data/lib/zeitwerk/registry.rb +7 -7
 - data/lib/zeitwerk/version.rb +1 -1
 - metadata +9 -6
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 7ac5e0ce2eba71a2099ead24de8762e3e737656ff4bcae57c83bb5797e922f72
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 85e7c762a5164301ba5bcce82a31e6c49bc7c6caff2b4abff23c425f10374c69
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: f258acabd660f54702eb904550a109b08bdacff2cecc92d610376376f77ccf70ed421e09c64144930d2a145ebbedb7e9c3c26ff21ad2c1b40dd0537d9947b4d4
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 1612dc2d505fe37b748d81a9055e36a91f9f73267ac273b39a4cbea3736cdd1baa4c557e3059d8f3a9a9807d450e29f3d75c7cb18c94c37ded70768444313336
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -3,41 +3,48 @@ 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            [](https://rubygems.org/gems/zeitwerk)
         
     | 
| 
       6 
     | 
    
         
            -
            [](https://github.com/fxn/zeitwerk/actions?query=event%3Apush)
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
            <!-- TOC -->
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
            - [Introduction](#introduction)
         
     | 
| 
       11 
11 
     | 
    
         
             
            - [Synopsis](#synopsis)
         
     | 
| 
       12 
12 
     | 
    
         
             
            - [File structure](#file-structure)
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 13 
     | 
    
         
            +
              - [The idea: File paths match constant paths](#the-idea-file-paths-match-constant-paths)
         
     | 
| 
      
 14 
     | 
    
         
            +
              - [Inner simple constants](#inner-simple-constants)
         
     | 
| 
      
 15 
     | 
    
         
            +
              - [Root directories and root namespaces](#root-directories-and-root-namespaces)
         
     | 
| 
      
 16 
     | 
    
         
            +
                - [The default root namespace is `Object`](#the-default-root-namespace-is-object)
         
     | 
| 
      
 17 
     | 
    
         
            +
                - [Custom root namespaces](#custom-root-namespaces)
         
     | 
| 
       16 
18 
     | 
    
         
             
                - [Nested root directories](#nested-root-directories)
         
     | 
| 
      
 19 
     | 
    
         
            +
              - [Implicit namespaces](#implicit-namespaces)
         
     | 
| 
      
 20 
     | 
    
         
            +
              - [Explicit namespaces](#explicit-namespaces)
         
     | 
| 
      
 21 
     | 
    
         
            +
              - [Collapsing directories](#collapsing-directories)
         
     | 
| 
       17 
22 
     | 
    
         
             
            - [Usage](#usage)
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                - [ 
     | 
| 
       36 
     | 
    
         
            -
                - [ 
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
      
 23 
     | 
    
         
            +
              - [Setup](#setup)
         
     | 
| 
      
 24 
     | 
    
         
            +
                - [Generic](#generic)
         
     | 
| 
      
 25 
     | 
    
         
            +
                - [for_gem](#for_gem)
         
     | 
| 
      
 26 
     | 
    
         
            +
              - [Autoloading](#autoloading)
         
     | 
| 
      
 27 
     | 
    
         
            +
              - [Eager loading](#eager-loading)
         
     | 
| 
      
 28 
     | 
    
         
            +
              - [Reloading](#reloading)
         
     | 
| 
      
 29 
     | 
    
         
            +
              - [Inflection](#inflection)
         
     | 
| 
      
 30 
     | 
    
         
            +
                - [Zeitwerk::Inflector](#zeitwerkinflector)
         
     | 
| 
      
 31 
     | 
    
         
            +
                - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
         
     | 
| 
      
 32 
     | 
    
         
            +
                - [Custom inflector](#custom-inflector)
         
     | 
| 
      
 33 
     | 
    
         
            +
              - [The on_load callback](#the-on_load-callback)
         
     | 
| 
      
 34 
     | 
    
         
            +
              - [The on_unload callback](#the-on_unload-callback)
         
     | 
| 
      
 35 
     | 
    
         
            +
                - [Technical details](#technical-details)
         
     | 
| 
      
 36 
     | 
    
         
            +
              - [Logging](#logging)
         
     | 
| 
      
 37 
     | 
    
         
            +
                - [Loader tag](#loader-tag)
         
     | 
| 
      
 38 
     | 
    
         
            +
              - [Ignoring parts of the project](#ignoring-parts-of-the-project)
         
     | 
| 
      
 39 
     | 
    
         
            +
                - [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
         
     | 
| 
      
 40 
     | 
    
         
            +
                - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
         
     | 
| 
      
 41 
     | 
    
         
            +
                - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
         
     | 
| 
      
 42 
     | 
    
         
            +
              - [Edge cases](#edge-cases)
         
     | 
| 
      
 43 
     | 
    
         
            +
              - [Reopening third-party namespaces](#reopening-third-party-namespaces)
         
     | 
| 
      
 44 
     | 
    
         
            +
              - [Rules of thumb](#rules-of-thumb)
         
     | 
| 
      
 45 
     | 
    
         
            +
              - [Debuggers](#debuggers)
         
     | 
| 
      
 46 
     | 
    
         
            +
                - [Break](#break)
         
     | 
| 
      
 47 
     | 
    
         
            +
                - [Byebug](#byebug)
         
     | 
| 
       41 
48 
     | 
    
         
             
            - [Pronunciation](#pronunciation)
         
     | 
| 
       42 
49 
     | 
    
         
             
            - [Supported Ruby versions](#supported-ruby-versions)
         
     | 
| 
       43 
50 
     | 
    
         
             
            - [Testing](#testing)
         
     | 
| 
         @@ -117,6 +124,9 @@ Zeitwerk::Loader.eager_load_all 
     | 
|
| 
       117 
124 
     | 
    
         
             
            <a id="markdown-file-structure" name="file-structure"></a>
         
     | 
| 
       118 
125 
     | 
    
         
             
            ## File structure
         
     | 
| 
       119 
126 
     | 
    
         | 
| 
      
 127 
     | 
    
         
            +
            <a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
         
     | 
| 
      
 128 
     | 
    
         
            +
            ### The idea: File paths match constant paths
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
       120 
130 
     | 
    
         
             
            To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
         
     | 
| 
       121 
131 
     | 
    
         | 
| 
       122 
132 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -126,25 +136,57 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz 
     | 
|
| 
       126 
136 
     | 
    
         
             
            lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
         
     | 
| 
       127 
137 
     | 
    
         
             
            ```
         
     | 
| 
       128 
138 
     | 
    
         | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
      
 139 
     | 
    
         
            +
            You can tune that a bit by [collapsing directories](#collapsing-directories), or by [ignoring parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
            <a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
         
     | 
| 
      
 142 
     | 
    
         
            +
            ### Inner simple constants
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
            While a simple constant like `HttpCrawler::MAX_RETRIES` can be defined in its own file:
         
     | 
| 
       130 
145 
     | 
    
         | 
| 
       131 
146 
     | 
    
         
             
            ```ruby
         
     | 
| 
       132 
     | 
    
         
            -
             
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
      
 147 
     | 
    
         
            +
            # http_crawler/max_retries.rb
         
     | 
| 
      
 148 
     | 
    
         
            +
            HttpCrawler::MAX_RETRIES = 10
         
     | 
| 
       134 
149 
     | 
    
         
             
            ```
         
     | 
| 
       135 
150 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
      
 151 
     | 
    
         
            +
            that is not required, you can also define it the regular way:
         
     | 
| 
       137 
152 
     | 
    
         | 
| 
      
 153 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 154 
     | 
    
         
            +
            # http_crawler.rb
         
     | 
| 
      
 155 
     | 
    
         
            +
            class HttpCrawler
         
     | 
| 
      
 156 
     | 
    
         
            +
              MAX_RETRIES = 10
         
     | 
| 
      
 157 
     | 
    
         
            +
            end
         
     | 
| 
       138 
158 
     | 
    
         
             
            ```
         
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            <a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
         
     | 
| 
      
 161 
     | 
    
         
            +
            ### Root directories and root namespaces
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
            Every directory configured with `push_dir` is called a _root directory_, and they represent _root namespaces_.
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
            <a id="markdown-the-default-root-namespace-is-object" name="the-default-root-namespace-is-object"></a>
         
     | 
| 
      
 166 
     | 
    
         
            +
            #### The default root namespace is `Object`
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
            By default, the namespace associated to a root directory is the top-level one: `Object`.
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
            For example, given
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 173 
     | 
    
         
            +
            loader.push_dir("#{__dir__}/models")
         
     | 
| 
      
 174 
     | 
    
         
            +
            loader.push_dir("#{__dir__}/serializers"))
         
     | 
| 
      
 175 
     | 
    
         
            +
            ```
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
            these are the expected classes and modules being defined by these files:
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
            ```
         
     | 
| 
      
 180 
     | 
    
         
            +
            models/user.rb                 -> User
         
     | 
| 
      
 181 
     | 
    
         
            +
            serializers/user_serializer.rb -> UserSerializer
         
     | 
| 
       141 
182 
     | 
    
         
             
            ```
         
     | 
| 
       142 
183 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
             
     | 
| 
      
 184 
     | 
    
         
            +
            <a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
         
     | 
| 
      
 185 
     | 
    
         
            +
            #### Custom root namespaces
         
     | 
| 
       144 
186 
     | 
    
         | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
      
 187 
     | 
    
         
            +
            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.
         
     | 
| 
       146 
188 
     | 
    
         | 
| 
       147 
     | 
    
         
            -
             
     | 
| 
      
 189 
     | 
    
         
            +
            For example, given:
         
     | 
| 
       148 
190 
     | 
    
         | 
| 
       149 
191 
     | 
    
         
             
            ```ruby
         
     | 
| 
       150 
192 
     | 
    
         
             
            require "active_job"
         
     | 
| 
         @@ -152,9 +194,26 @@ require "active_job/queue_adapters" 
     | 
|
| 
       152 
194 
     | 
    
         
             
            loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
         
     | 
| 
       153 
195 
     | 
    
         
             
            ```
         
     | 
| 
       154 
196 
     | 
    
         | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
      
 197 
     | 
    
         
            +
            a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the conventional parent directories, you can (and have to) store the file directly below `adapters`:
         
     | 
| 
       156 
198 
     | 
    
         | 
| 
       157 
     | 
    
         
            -
             
     | 
| 
      
 199 
     | 
    
         
            +
            ```
         
     | 
| 
      
 200 
     | 
    
         
            +
            adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
         
     | 
| 
      
 201 
     | 
    
         
            +
            ```
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
            Please, note that the given root namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::Deliveries`, that one can be reloaded.
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
            <a id="markdown-nested-root-directories" name="nested-root-directories"></a>
         
     | 
| 
      
 206 
     | 
    
         
            +
            #### Nested root directories
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
            Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
            Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
            ```
         
     | 
| 
      
 213 
     | 
    
         
            +
            app/models/concerns/geolocatable.rb
         
     | 
| 
      
 214 
     | 
    
         
            +
            ```
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
            should define `Geolocatable`, not `Concerns::Geolocatable`.
         
     | 
| 
       158 
217 
     | 
    
         | 
| 
       159 
218 
     | 
    
         
             
            <a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
         
     | 
| 
       160 
219 
     | 
    
         
             
            ### Implicit namespaces
         
     | 
| 
         @@ -216,19 +275,6 @@ To illustrate usage of glob patterns, if `actions` in the example above is part 
     | 
|
| 
       216 
275 
     | 
    
         
             
            loader.collapse("#{__dir__}/*/actions")
         
     | 
| 
       217 
276 
     | 
    
         
             
            ```
         
     | 
| 
       218 
277 
     | 
    
         | 
| 
       219 
     | 
    
         
            -
            <a id="markdown-nested-root-directories" name="nested-root-directories"></a>
         
     | 
| 
       220 
     | 
    
         
            -
            ### Nested root directories
         
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
       222 
     | 
    
         
            -
            Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
         
     | 
| 
       223 
     | 
    
         
            -
             
     | 
| 
       224 
     | 
    
         
            -
            Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
         
     | 
| 
       225 
     | 
    
         
            -
             
     | 
| 
       226 
     | 
    
         
            -
            ```
         
     | 
| 
       227 
     | 
    
         
            -
            app/models/concerns/geolocatable.rb
         
     | 
| 
       228 
     | 
    
         
            -
            ```
         
     | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
       230 
     | 
    
         
            -
            should define `Geolocatable`, not `Concerns::Geolocatable`.
         
     | 
| 
       231 
     | 
    
         
            -
             
     | 
| 
       232 
278 
     | 
    
         
             
            <a id="markdown-usage" name="usage"></a>
         
     | 
| 
       233 
279 
     | 
    
         
             
            ## Usage
         
     | 
| 
       234 
280 
     | 
    
         | 
| 
         @@ -522,26 +568,23 @@ With `on_load`, it is easy to schedule code at boot time that initializes `endpo 
     | 
|
| 
       522 
568 
     | 
    
         | 
| 
       523 
569 
     | 
    
         
             
            ```ruby
         
     | 
| 
       524 
570 
     | 
    
         
             
            # config/environments/development.rb
         
     | 
| 
       525 
     | 
    
         
            -
            loader.on_load("SomeApiClient") do
         
     | 
| 
       526 
     | 
    
         
            -
               
     | 
| 
      
 571 
     | 
    
         
            +
            loader.on_load("SomeApiClient") do |klass, _abspath|
         
     | 
| 
      
 572 
     | 
    
         
            +
              klass.endpoint = "https://api.dev"
         
     | 
| 
       527 
573 
     | 
    
         
             
            end
         
     | 
| 
       528 
574 
     | 
    
         | 
| 
       529 
575 
     | 
    
         
             
            # config/environments/production.rb
         
     | 
| 
       530 
     | 
    
         
            -
            loader.on_load("SomeApiClient") do
         
     | 
| 
       531 
     | 
    
         
            -
               
     | 
| 
      
 576 
     | 
    
         
            +
            loader.on_load("SomeApiClient") do |klass, _abspath|
         
     | 
| 
      
 577 
     | 
    
         
            +
              klass.endpoint = "https://api.prod"
         
     | 
| 
       532 
578 
     | 
    
         
             
            end
         
     | 
| 
       533 
579 
     | 
    
         
             
            ```
         
     | 
| 
       534 
580 
     | 
    
         | 
| 
       535 
     | 
    
         
            -
             
     | 
| 
      
 581 
     | 
    
         
            +
            Some uses cases:
         
     | 
| 
       536 
582 
     | 
    
         | 
| 
       537 
583 
     | 
    
         
             
            * Doing something with an autoloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
         
     | 
| 
       538 
584 
     | 
    
         
             
            * Delaying the execution of the block until the class is loaded for performance.
         
     | 
| 
       539 
585 
     | 
    
         
             
            * Delaying the execution of the block until the class is loaded because it follows the adapter pattern and better not to load the class if the user does not need it.
         
     | 
| 
       540 
     | 
    
         
            -
            * Etc.
         
     | 
| 
       541 
586 
     | 
    
         | 
| 
       542 
     | 
    
         
            -
             
     | 
| 
       543 
     | 
    
         
            -
             
     | 
| 
       544 
     | 
    
         
            -
            `on_load` receives the name of the target class or module as a string. The given block is executed every time its corresponding file is loaded. That includes reloads.
         
     | 
| 
      
 587 
     | 
    
         
            +
            `on_load` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is loaded. That includes reloads.
         
     | 
| 
       545 
588 
     | 
    
         | 
| 
       546 
589 
     | 
    
         
             
            Multiple callbacks on the same target are supported, and they run in order of definition.
         
     | 
| 
       547 
590 
     | 
    
         | 
| 
         @@ -549,6 +592,66 @@ The block is executed once the loader has loaded the target. In particular, if t 
     | 
|
| 
       549 
592 
     | 
    
         | 
| 
       550 
593 
     | 
    
         
             
            Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
         
     | 
| 
       551 
594 
     | 
    
         | 
| 
      
 595 
     | 
    
         
            +
            It is also possible to be called when any constant managed by the loader is loaded:
         
     | 
| 
      
 596 
     | 
    
         
            +
             
     | 
| 
      
 597 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 598 
     | 
    
         
            +
            loader.on_load do |cpath, value, abspath|
         
     | 
| 
      
 599 
     | 
    
         
            +
              # ...
         
     | 
| 
      
 600 
     | 
    
         
            +
            end
         
     | 
| 
      
 601 
     | 
    
         
            +
            ```
         
     | 
| 
      
 602 
     | 
    
         
            +
             
     | 
| 
      
 603 
     | 
    
         
            +
            The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
         
     | 
| 
      
 604 
     | 
    
         
            +
             
     | 
| 
      
 605 
     | 
    
         
            +
            Multiple callbacks like these are supported, and they run in order of definition.
         
     | 
| 
      
 606 
     | 
    
         
            +
             
     | 
| 
      
 607 
     | 
    
         
            +
            There are use cases for this last catch-all callback, but they are rare. If you just need to understand how things are being loaded for debugging purposes, please remember that `Zeitwerk::Loader#log!` logs plenty of information.
         
     | 
| 
      
 608 
     | 
    
         
            +
             
     | 
| 
      
 609 
     | 
    
         
            +
            If both types of callbacks are defined, the specific ones run first.
         
     | 
| 
      
 610 
     | 
    
         
            +
             
     | 
| 
      
 611 
     | 
    
         
            +
            <a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
         
     | 
| 
      
 612 
     | 
    
         
            +
            ### The on_unload callback
         
     | 
| 
      
 613 
     | 
    
         
            +
             
     | 
| 
      
 614 
     | 
    
         
            +
            When reloading is enabled, you may occasionally need to execute something before a certain autoloaded class or module is unloaded. The `on_unload` callback allows you to do that.
         
     | 
| 
      
 615 
     | 
    
         
            +
             
     | 
| 
      
 616 
     | 
    
         
            +
            For example, let's imagine that a `Country` class fetches a list of countries and caches them when it is loaded. You might want to clear that cache if unloaded:
         
     | 
| 
      
 617 
     | 
    
         
            +
             
     | 
| 
      
 618 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 619 
     | 
    
         
            +
            loader.on_unload("Country") do |klass, _abspath|
         
     | 
| 
      
 620 
     | 
    
         
            +
              klass.clear_cache
         
     | 
| 
      
 621 
     | 
    
         
            +
            end
         
     | 
| 
      
 622 
     | 
    
         
            +
            ```
         
     | 
| 
      
 623 
     | 
    
         
            +
             
     | 
| 
      
 624 
     | 
    
         
            +
            `on_unload` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is unloaded.
         
     | 
| 
      
 625 
     | 
    
         
            +
             
     | 
| 
      
 626 
     | 
    
         
            +
            `on_unload` blocks are executed before the class is unloaded, but in the middle of unloading, which happens in an unspecified order. Therefore, **that callback should not refer to any reloadable constant because there is no guarantee the constant works there**. Those blocks should rely on objects only, as in the example above, or regular constants not managed by the loader. This remark is transitive, applies to any methods invoked within the block.
         
     | 
| 
      
 627 
     | 
    
         
            +
             
     | 
| 
      
 628 
     | 
    
         
            +
            Multiple callbacks on the same target are supported, and they run in order of definition.
         
     | 
| 
      
 629 
     | 
    
         
            +
             
     | 
| 
      
 630 
     | 
    
         
            +
            Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
         
     | 
| 
      
 631 
     | 
    
         
            +
             
     | 
| 
      
 632 
     | 
    
         
            +
            It is also possible to be called when any constant managed by the loader is unloaded:
         
     | 
| 
      
 633 
     | 
    
         
            +
             
     | 
| 
      
 634 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 635 
     | 
    
         
            +
            loader.on_unload do |cpath, value, abspath|
         
     | 
| 
      
 636 
     | 
    
         
            +
              # ...
         
     | 
| 
      
 637 
     | 
    
         
            +
            end
         
     | 
| 
      
 638 
     | 
    
         
            +
            ```
         
     | 
| 
      
 639 
     | 
    
         
            +
             
     | 
| 
      
 640 
     | 
    
         
            +
            The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
         
     | 
| 
      
 641 
     | 
    
         
            +
             
     | 
| 
      
 642 
     | 
    
         
            +
            Multiple callbacks like these are supported, and they run in order of definition.
         
     | 
| 
      
 643 
     | 
    
         
            +
             
     | 
| 
      
 644 
     | 
    
         
            +
            If both types of callbacks are defined, the specific ones run first.
         
     | 
| 
      
 645 
     | 
    
         
            +
             
     | 
| 
      
 646 
     | 
    
         
            +
            <a id="markdown-technical-details" name="technical-details"></a>
         
     | 
| 
      
 647 
     | 
    
         
            +
            #### Technical details
         
     | 
| 
      
 648 
     | 
    
         
            +
             
     | 
| 
      
 649 
     | 
    
         
            +
            Zeitwerk uses the word "unload" to ease communication and for symmetry with `on_load`. However, in Ruby you cannot unload things for real. So, when does `on_unload` technically happen?
         
     | 
| 
      
 650 
     | 
    
         
            +
             
     | 
| 
      
 651 
     | 
    
         
            +
            When unloading, Zeitwerk issues `Module#remove_const` calls. Classes and modules are no longer reachable through their constants, and `on_unload` callbacks are executed right before those calls.
         
     | 
| 
      
 652 
     | 
    
         
            +
             
     | 
| 
      
 653 
     | 
    
         
            +
            Technically, though, the objects themselves are still alive, but if everything is used as expected and they are not stored in any non-reloadable place (don't do that), they are ready for garbage collection, which is when the real unloading happens.
         
     | 
| 
      
 654 
     | 
    
         
            +
             
     | 
| 
       552 
655 
     | 
    
         
             
            <a id="markdown-logging" name="logging"></a>
         
     | 
| 
       553 
656 
     | 
    
         
             
            ### Logging
         
     | 
| 
       554 
657 
     | 
    
         | 
    
        data/lib/zeitwerk.rb
    CHANGED
    
    | 
         @@ -3,10 +3,12 @@ 
     | 
|
| 
       3 
3 
     | 
    
         
             
            module Zeitwerk
         
     | 
| 
       4 
4 
     | 
    
         
             
              require_relative "zeitwerk/real_mod_name"
         
     | 
| 
       5 
5 
     | 
    
         
             
              require_relative "zeitwerk/loader"
         
     | 
| 
      
 6 
     | 
    
         
            +
              require_relative "zeitwerk/autoloads"
         
     | 
| 
       6 
7 
     | 
    
         
             
              require_relative "zeitwerk/registry"
         
     | 
| 
       7 
8 
     | 
    
         
             
              require_relative "zeitwerk/explicit_namespace"
         
     | 
| 
       8 
9 
     | 
    
         
             
              require_relative "zeitwerk/inflector"
         
     | 
| 
       9 
10 
     | 
    
         
             
              require_relative "zeitwerk/gem_inflector"
         
     | 
| 
       10 
11 
     | 
    
         
             
              require_relative "zeitwerk/kernel"
         
     | 
| 
       11 
12 
     | 
    
         
             
              require_relative "zeitwerk/error"
         
     | 
| 
      
 13 
     | 
    
         
            +
              require_relative "zeitwerk/version"
         
     | 
| 
       12 
14 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Zeitwerk
         
     | 
| 
      
 2 
     | 
    
         
            +
              # @private
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Autoloads
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Maps crefs for which an autoload has been defined to the corresponding
         
     | 
| 
      
 5 
     | 
    
         
            +
                # absolute path.
         
     | 
| 
      
 6 
     | 
    
         
            +
                #
         
     | 
| 
      
 7 
     | 
    
         
            +
                #   [Object, :User]  => "/Users/fxn/blog/app/models/user.rb"
         
     | 
| 
      
 8 
     | 
    
         
            +
                #   [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
         
     | 
| 
      
 9 
     | 
    
         
            +
                #   ...
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                # This colection is transient, callbacks delete its entries as autoloads get
         
     | 
| 
      
 12 
     | 
    
         
            +
                # executed.
         
     | 
| 
      
 13 
     | 
    
         
            +
                #
         
     | 
| 
      
 14 
     | 
    
         
            +
                # @sig Hash[[Module, Symbol], String]
         
     | 
| 
      
 15 
     | 
    
         
            +
                attr_reader :c2a
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # This is the inverse of c2a, for inverse lookups.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # @sig Hash[String, [Module, Symbol]]
         
     | 
| 
      
 20 
     | 
    
         
            +
                attr_reader :a2c
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                # @sig () -> void
         
     | 
| 
      
 23 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @c2a = {}
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @a2c = {}
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                # @sig (Module, Symbol, String) -> void
         
     | 
| 
      
 29 
     | 
    
         
            +
                def define(parent, cname, abspath)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  parent.autoload(cname, abspath)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  cref = [parent, cname]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  c2a[cref] = abspath
         
     | 
| 
      
 33 
     | 
    
         
            +
                  a2c[abspath] = cref
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                # @sig () { () -> [[Module, Symbol], String] } -> void
         
     | 
| 
      
 37 
     | 
    
         
            +
                def each(&block)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  c2a.each(&block)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                # @sig (Module, Symbol) -> String?
         
     | 
| 
      
 42 
     | 
    
         
            +
                def abspath_for(parent, cname)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  c2a[[parent, cname]]
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                # @sig (String) -> [Module, Symbol]?
         
     | 
| 
      
 47 
     | 
    
         
            +
                def cref_for(abspath)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  a2c[abspath]
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                # @sig (String) -> [Module, Symbol]?
         
     | 
| 
      
 52 
     | 
    
         
            +
                def delete(abspath)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  cref = a2c.delete(abspath)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  c2a.delete(cref)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  cref
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                # @sig () -> void
         
     | 
| 
      
 59 
     | 
    
         
            +
                def clear
         
     | 
| 
      
 60 
     | 
    
         
            +
                  c2a.clear
         
     | 
| 
      
 61 
     | 
    
         
            +
                  a2c.clear
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                # @sig () -> bool
         
     | 
| 
      
 65 
     | 
    
         
            +
                def empty?
         
     | 
| 
      
 66 
     | 
    
         
            +
                  c2a.empty? && a2c.empty?
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -62,8 +62,14 @@ module Zeitwerk 
     | 
|
| 
       62 
62 
     | 
    
         
             
                    # than accessing its name.
         
     | 
| 
       63 
63 
     | 
    
         
             
                    return if event.self.singleton_class?
         
     | 
| 
       64 
64 
     | 
    
         | 
| 
       65 
     | 
    
         
            -
                    #  
     | 
| 
       66 
     | 
    
         
            -
                    #  
     | 
| 
      
 65 
     | 
    
         
            +
                    # It might be tempting to return if name.nil?, to avoid the computation
         
     | 
| 
      
 66 
     | 
    
         
            +
                    # of a hash code and delete call. But Ruby does not trigger the :class
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # event on Class.new or Module.new, so that would incur in an extra call
         
     | 
| 
      
 68 
     | 
    
         
            +
                    # for nothing.
         
     | 
| 
      
 69 
     | 
    
         
            +
                    #
         
     | 
| 
      
 70 
     | 
    
         
            +
                    # On the other hand, if we were called, cpaths is not empty. Otherwise
         
     | 
| 
      
 71 
     | 
    
         
            +
                    # the tracer is disabled. So we do need to go ahead with the hash code
         
     | 
| 
      
 72 
     | 
    
         
            +
                    # computation and delete call.
         
     | 
| 
       67 
73 
     | 
    
         
             
                    if loader = cpaths.delete(real_mod_name(event.self))
         
     | 
| 
       68 
74 
     | 
    
         
             
                      loader.on_namespace_loaded(event.self)
         
     | 
| 
       69 
75 
     | 
    
         
             
                      disable_tracer_if_unneeded
         
     | 
    
        data/lib/zeitwerk/kernel.rb
    CHANGED
    
    | 
         @@ -12,7 +12,8 @@ module Kernel 
     | 
|
| 
       12 
12 
     | 
    
         
             
              # On the other hand, if you publish a new version of a gem that is now managed
         
     | 
| 
       13 
13 
     | 
    
         
             
              # by Zeitwerk, client code can reference directly your classes and modules and
         
     | 
| 
       14 
14 
     | 
    
         
             
              # should not require anything. But if someone has legacy require calls around,
         
     | 
| 
       15 
     | 
    
         
            -
              # they will work as expected, and in a compatible way.
         
     | 
| 
      
 15 
     | 
    
         
            +
              # they will work as expected, and in a compatible way. This feature is by now
         
     | 
| 
      
 16 
     | 
    
         
            +
              # EXPERIMENTAL and UNDOCUMENTED.
         
     | 
| 
       16 
17 
     | 
    
         
             
              #
         
     | 
| 
       17 
18 
     | 
    
         
             
              # We cannot decorate with prepend + super because Kernel has already been
         
     | 
| 
       18 
19 
     | 
    
         
             
              # included in Object, and changes in ancestors don't get propagated into
         
     | 
| 
         @@ -33,9 +34,9 @@ module Kernel 
     | 
|
| 
       33 
34 
     | 
    
         
             
                else
         
     | 
| 
       34 
35 
     | 
    
         
             
                  zeitwerk_original_require(path).tap do |required|
         
     | 
| 
       35 
36 
     | 
    
         
             
                    if required
         
     | 
| 
       36 
     | 
    
         
            -
                       
     | 
| 
       37 
     | 
    
         
            -
                      if loader = Zeitwerk::Registry.loader_for( 
     | 
| 
       38 
     | 
    
         
            -
                        loader.on_file_autoloaded( 
     | 
| 
      
 37 
     | 
    
         
            +
                      abspath = $LOADED_FEATURES.last
         
     | 
| 
      
 38 
     | 
    
         
            +
                      if loader = Zeitwerk::Registry.loader_for(abspath)
         
     | 
| 
      
 39 
     | 
    
         
            +
                        loader.on_file_autoloaded(abspath)
         
     | 
| 
       39 
40 
     | 
    
         
             
                      end
         
     | 
| 
       40 
41 
     | 
    
         
             
                    end
         
     | 
| 
       41 
42 
     | 
    
         
             
                  end
         
     | 
    
        data/lib/zeitwerk/loader.rb
    CHANGED
    
    | 
         @@ -5,78 +5,31 @@ require "securerandom" 
     | 
|
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module Zeitwerk
         
     | 
| 
       7 
7 
     | 
    
         
             
              class Loader
         
     | 
| 
      
 8 
     | 
    
         
            +
                require_relative "loader/helpers"
         
     | 
| 
       8 
9 
     | 
    
         
             
                require_relative "loader/callbacks"
         
     | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
                include RealModName
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                # @sig String
         
     | 
| 
       13 
     | 
    
         
            -
                attr_reader :tag
         
     | 
| 
      
 10 
     | 
    
         
            +
                require_relative "loader/config"
         
     | 
| 
       14 
11 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
                 
     | 
| 
       16 
     | 
    
         
            -
                 
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                 
     | 
| 
       19 
     | 
    
         
            -
                attr_accessor :logger
         
     | 
| 
      
 12 
     | 
    
         
            +
                include RealModName
         
     | 
| 
      
 13 
     | 
    
         
            +
                include Callbacks
         
     | 
| 
      
 14 
     | 
    
         
            +
                include Helpers
         
     | 
| 
      
 15 
     | 
    
         
            +
                include Config
         
     | 
| 
       20 
16 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                #  
     | 
| 
       22 
     | 
    
         
            -
                #  
     | 
| 
       23 
     | 
    
         
            -
                # needed for detecting nested paths.
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Keeps track of autoloads defined by the loader which have not been
         
     | 
| 
      
 18 
     | 
    
         
            +
                # executed so far.
         
     | 
| 
       24 
19 
     | 
    
         
             
                #
         
     | 
| 
       25 
     | 
    
         
            -
                # 
     | 
| 
       26 
     | 
    
         
            -
                #   "/Users/fxn/blog/app/channels" => true,
         
     | 
| 
       27 
     | 
    
         
            -
                #   ...
         
     | 
| 
      
 20 
     | 
    
         
            +
                # This metadata helps us implement a few things:
         
     | 
| 
       28 
21 
     | 
    
         
             
                #
         
     | 
| 
       29 
     | 
    
         
            -
                #  
     | 
| 
       30 
     | 
    
         
            -
                #  
     | 
| 
      
 22 
     | 
    
         
            +
                # 1. When autoloads are triggered, ensure they define the expected constant
         
     | 
| 
      
 23 
     | 
    
         
            +
                #    and invoke user callbacks. If reloading is enabled, remember cref and
         
     | 
| 
      
 24 
     | 
    
         
            +
                #    abspath for later unloading logic.
         
     | 
| 
       31 
25 
     | 
    
         
             
                #
         
     | 
| 
       32 
     | 
    
         
            -
                #  
     | 
| 
       33 
     | 
    
         
            -
                # @sig Hash[String, true]
         
     | 
| 
       34 
     | 
    
         
            -
                attr_reader :root_dirs
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                # Absolute paths of files or directories that have to be preloaded.
         
     | 
| 
      
 26 
     | 
    
         
            +
                # 2. When unloading, remove autoloads that have not been executed.
         
     | 
| 
       37 
27 
     | 
    
         
             
                #
         
     | 
| 
       38 
     | 
    
         
            -
                #  
     | 
| 
       39 
     | 
    
         
            -
                #  
     | 
| 
       40 
     | 
    
         
            -
                attr_reader :preloads
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
                # Absolute paths of files, directories, or glob patterns to be totally
         
     | 
| 
       43 
     | 
    
         
            -
                # ignored.
         
     | 
| 
      
 28 
     | 
    
         
            +
                # 3. Eager load with a recursive const_get, rather than a recursive require,
         
     | 
| 
      
 29 
     | 
    
         
            +
                #    for consistency with lazy loading.
         
     | 
| 
       44 
30 
     | 
    
         
             
                #
         
     | 
| 
       45 
31 
     | 
    
         
             
                # @private
         
     | 
| 
       46 
     | 
    
         
            -
                # @sig  
     | 
| 
       47 
     | 
    
         
            -
                attr_reader :ignored_glob_patterns
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
                # The actual collection of absolute file and directory names at the time the
         
     | 
| 
       50 
     | 
    
         
            -
                # ignored glob patterns were expanded. Computed on setup, and recomputed on
         
     | 
| 
       51 
     | 
    
         
            -
                # reload.
         
     | 
| 
       52 
     | 
    
         
            -
                #
         
     | 
| 
       53 
     | 
    
         
            -
                # @private
         
     | 
| 
       54 
     | 
    
         
            -
                # @sig Set[String]
         
     | 
| 
       55 
     | 
    
         
            -
                attr_reader :ignored_paths
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
                # Absolute paths of directories or glob patterns to be collapsed.
         
     | 
| 
       58 
     | 
    
         
            -
                #
         
     | 
| 
       59 
     | 
    
         
            -
                # @private
         
     | 
| 
       60 
     | 
    
         
            -
                # @sig Set[String]
         
     | 
| 
       61 
     | 
    
         
            -
                attr_reader :collapse_glob_patterns
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
                # The actual collection of absolute directory names at the time the collapse
         
     | 
| 
       64 
     | 
    
         
            -
                # glob patterns were expanded. Computed on setup, and recomputed on reload.
         
     | 
| 
       65 
     | 
    
         
            -
                #
         
     | 
| 
       66 
     | 
    
         
            -
                # @private
         
     | 
| 
       67 
     | 
    
         
            -
                # @sig Set[String]
         
     | 
| 
       68 
     | 
    
         
            -
                attr_reader :collapse_dirs
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
                # Maps real absolute paths for which an autoload has been set ---and not
         
     | 
| 
       71 
     | 
    
         
            -
                # executed--- to their corresponding parent class or module and constant
         
     | 
| 
       72 
     | 
    
         
            -
                # name.
         
     | 
| 
       73 
     | 
    
         
            -
                #
         
     | 
| 
       74 
     | 
    
         
            -
                #   "/Users/fxn/blog/app/models/user.rb"          => [Object, :User],
         
     | 
| 
       75 
     | 
    
         
            -
                #   "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
         
     | 
| 
       76 
     | 
    
         
            -
                #   ...
         
     | 
| 
       77 
     | 
    
         
            -
                #
         
     | 
| 
       78 
     | 
    
         
            -
                # @private
         
     | 
| 
       79 
     | 
    
         
            -
                # @sig Hash[String, [Module, Symbol]]
         
     | 
| 
      
 32 
     | 
    
         
            +
                # @sig Zeitwerk::Autoloads
         
     | 
| 
       80 
33 
     | 
    
         
             
                attr_reader :autoloads
         
     | 
| 
       81 
34 
     | 
    
         | 
| 
       82 
35 
     | 
    
         
             
                # We keep track of autoloaded directories to remove them from the registry
         
     | 
| 
         @@ -93,8 +46,8 @@ module Zeitwerk 
     | 
|
| 
       93 
46 
     | 
    
         
             
                #
         
     | 
| 
       94 
47 
     | 
    
         
             
                #   "Admin::Role" => [".../admin/role.rb", [Admin, :Role]]
         
     | 
| 
       95 
48 
     | 
    
         
             
                #
         
     | 
| 
       96 
     | 
    
         
            -
                # The cpath as key helps implementing unloadable_cpath? The  
     | 
| 
       97 
     | 
    
         
            -
                #  
     | 
| 
      
 49 
     | 
    
         
            +
                # The cpath as key helps implementing unloadable_cpath? The file name is
         
     | 
| 
      
 50 
     | 
    
         
            +
                # stored in order to be able to delete it from $LOADED_FEATURES, and the
         
     | 
| 
       98 
51 
     | 
    
         
             
                # pair [Module, Symbol] is used to remove_const the constant from the class
         
     | 
| 
       99 
52 
     | 
    
         
             
                # or module object.
         
     | 
| 
       100 
53 
     | 
    
         
             
                #
         
     | 
| 
         @@ -123,15 +76,6 @@ module Zeitwerk 
     | 
|
| 
       123 
76 
     | 
    
         
             
                # @sig Hash[String, Array[String]]
         
     | 
| 
       124 
77 
     | 
    
         
             
                attr_reader :lazy_subdirs
         
     | 
| 
       125 
78 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                # Absolute paths of files or directories not to be eager loaded.
         
     | 
| 
       127 
     | 
    
         
            -
                #
         
     | 
| 
       128 
     | 
    
         
            -
                # @private
         
     | 
| 
       129 
     | 
    
         
            -
                # @sig Set[String]
         
     | 
| 
       130 
     | 
    
         
            -
                attr_reader :eager_load_exclusions
         
     | 
| 
       131 
     | 
    
         
            -
             
     | 
| 
       132 
     | 
    
         
            -
                # User-oriented callbacks to be fired when a constant is loaded.
         
     | 
| 
       133 
     | 
    
         
            -
                attr_reader :on_load_callbacks
         
     | 
| 
       134 
     | 
    
         
            -
             
     | 
| 
       135 
79 
     | 
    
         
             
                # @private
         
     | 
| 
       136 
80 
     | 
    
         
             
                # @sig Mutex
         
     | 
| 
       137 
81 
     | 
    
         
             
                attr_reader :mutex
         
     | 
| 
         @@ -141,150 +85,21 @@ module Zeitwerk 
     | 
|
| 
       141 
85 
     | 
    
         
             
                attr_reader :mutex2
         
     | 
| 
       142 
86 
     | 
    
         | 
| 
       143 
87 
     | 
    
         
             
                def initialize
         
     | 
| 
       144 
     | 
    
         
            -
                   
     | 
| 
       145 
     | 
    
         
            -
             
     | 
| 
       146 
     | 
    
         
            -
                  @tag       = SecureRandom.hex(3)
         
     | 
| 
       147 
     | 
    
         
            -
                  @inflector = Inflector.new
         
     | 
| 
       148 
     | 
    
         
            -
                  @logger    = self.class.default_logger
         
     | 
| 
       149 
     | 
    
         
            -
             
     | 
| 
       150 
     | 
    
         
            -
                  @root_dirs              = {}
         
     | 
| 
       151 
     | 
    
         
            -
                  @preloads               = []
         
     | 
| 
       152 
     | 
    
         
            -
                  @ignored_glob_patterns  = Set.new
         
     | 
| 
       153 
     | 
    
         
            -
                  @ignored_paths          = Set.new
         
     | 
| 
       154 
     | 
    
         
            -
                  @collapse_glob_patterns = Set.new
         
     | 
| 
       155 
     | 
    
         
            -
                  @collapse_dirs          = Set.new
         
     | 
| 
       156 
     | 
    
         
            -
                  @autoloads              = {}
         
     | 
| 
       157 
     | 
    
         
            -
                  @autoloaded_dirs        = []
         
     | 
| 
       158 
     | 
    
         
            -
                  @to_unload              = {}
         
     | 
| 
       159 
     | 
    
         
            -
                  @lazy_subdirs           = {}
         
     | 
| 
       160 
     | 
    
         
            -
                  @eager_load_exclusions  = Set.new
         
     | 
| 
       161 
     | 
    
         
            -
                  @on_load_callbacks      = {}
         
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
                  # TODO: find a better name for these mutexes.
         
     | 
| 
       164 
     | 
    
         
            -
                  @mutex        = Mutex.new
         
     | 
| 
       165 
     | 
    
         
            -
                  @mutex2       = Mutex.new
         
     | 
| 
       166 
     | 
    
         
            -
                  @setup        = false
         
     | 
| 
       167 
     | 
    
         
            -
                  @eager_loaded = false
         
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
                  @reloading_enabled = false
         
     | 
| 
       170 
     | 
    
         
            -
             
     | 
| 
       171 
     | 
    
         
            -
                  Registry.register_loader(self)
         
     | 
| 
       172 
     | 
    
         
            -
                end
         
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
                # Sets a tag for the loader, useful for logging.
         
     | 
| 
       175 
     | 
    
         
            -
                #
         
     | 
| 
       176 
     | 
    
         
            -
                # @param tag [#to_s]
         
     | 
| 
       177 
     | 
    
         
            -
                # @sig (#to_s) -> void
         
     | 
| 
       178 
     | 
    
         
            -
                def tag=(tag)
         
     | 
| 
       179 
     | 
    
         
            -
                  @tag = tag.to_s
         
     | 
| 
       180 
     | 
    
         
            -
                end
         
     | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
       182 
     | 
    
         
            -
                # Absolute paths of the root directories. This is a read-only collection,
         
     | 
| 
       183 
     | 
    
         
            -
                # please push here via `push_dir`.
         
     | 
| 
       184 
     | 
    
         
            -
                #
         
     | 
| 
       185 
     | 
    
         
            -
                # @sig () -> Array[String]
         
     | 
| 
       186 
     | 
    
         
            -
                def dirs
         
     | 
| 
       187 
     | 
    
         
            -
                  root_dirs.keys.freeze
         
     | 
| 
       188 
     | 
    
         
            -
                end
         
     | 
| 
       189 
     | 
    
         
            -
             
     | 
| 
       190 
     | 
    
         
            -
                # Pushes `path` to the list of root directories.
         
     | 
| 
       191 
     | 
    
         
            -
                #
         
     | 
| 
       192 
     | 
    
         
            -
                # Raises `Zeitwerk::Error` if `path` does not exist, or if another loader in
         
     | 
| 
       193 
     | 
    
         
            -
                # the same process already manages that directory or one of its ascendants
         
     | 
| 
       194 
     | 
    
         
            -
                # or descendants.
         
     | 
| 
       195 
     | 
    
         
            -
                #
         
     | 
| 
       196 
     | 
    
         
            -
                # @raise [Zeitwerk::Error]
         
     | 
| 
       197 
     | 
    
         
            -
                # @sig (String | Pathname, Module) -> void
         
     | 
| 
       198 
     | 
    
         
            -
                def push_dir(path, namespace: Object)
         
     | 
| 
       199 
     | 
    
         
            -
                  # Note that Class < Module.
         
     | 
| 
       200 
     | 
    
         
            -
                  unless namespace.is_a?(Module)
         
     | 
| 
       201 
     | 
    
         
            -
                    raise Error, "#{namespace.inspect} is not a class or module object, should be"
         
     | 
| 
       202 
     | 
    
         
            -
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  super
         
     | 
| 
       203 
89 
     | 
    
         | 
| 
       204 
     | 
    
         
            -
                   
     | 
| 
       205 
     | 
    
         
            -
                   
     | 
| 
       206 
     | 
    
         
            -
             
     | 
| 
       207 
     | 
    
         
            -
             
     | 
| 
       208 
     | 
    
         
            -
                   
     | 
| 
       209 
     | 
    
         
            -
             
     | 
| 
       210 
     | 
    
         
            -
                   
     | 
| 
       211 
     | 
    
         
            -
                 
     | 
| 
      
 90 
     | 
    
         
            +
                  @autoloads       = Autoloads.new
         
     | 
| 
      
 91 
     | 
    
         
            +
                  @autoloaded_dirs = []
         
     | 
| 
      
 92 
     | 
    
         
            +
                  @to_unload       = {}
         
     | 
| 
      
 93 
     | 
    
         
            +
                  @lazy_subdirs    = Hash.new { |h, cpath| h[cpath] = [] }
         
     | 
| 
      
 94 
     | 
    
         
            +
                  @mutex           = Mutex.new
         
     | 
| 
      
 95 
     | 
    
         
            +
                  @mutex2          = Mutex.new
         
     | 
| 
      
 96 
     | 
    
         
            +
                  @setup           = false
         
     | 
| 
      
 97 
     | 
    
         
            +
                  @eager_loaded    = false
         
     | 
| 
       212 
98 
     | 
    
         | 
| 
       213 
     | 
    
         
            -
             
     | 
| 
       214 
     | 
    
         
            -
                # There is no way to undo this, either you want to reload or you don't.
         
     | 
| 
       215 
     | 
    
         
            -
                #
         
     | 
| 
       216 
     | 
    
         
            -
                # @raise [Zeitwerk::Error]
         
     | 
| 
       217 
     | 
    
         
            -
                # @sig () -> void
         
     | 
| 
       218 
     | 
    
         
            -
                def enable_reloading
         
     | 
| 
       219 
     | 
    
         
            -
                  mutex.synchronize do
         
     | 
| 
       220 
     | 
    
         
            -
                    break if @reloading_enabled
         
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
       222 
     | 
    
         
            -
                    if @setup
         
     | 
| 
       223 
     | 
    
         
            -
                      raise Error, "cannot enable reloading after setup"
         
     | 
| 
       224 
     | 
    
         
            -
                    else
         
     | 
| 
       225 
     | 
    
         
            -
                      @reloading_enabled = true
         
     | 
| 
       226 
     | 
    
         
            -
                    end
         
     | 
| 
       227 
     | 
    
         
            -
                  end
         
     | 
| 
       228 
     | 
    
         
            -
                end
         
     | 
| 
       229 
     | 
    
         
            -
             
     | 
| 
       230 
     | 
    
         
            -
                # @sig () -> bool
         
     | 
| 
       231 
     | 
    
         
            -
                def reloading_enabled?
         
     | 
| 
       232 
     | 
    
         
            -
                  @reloading_enabled
         
     | 
| 
       233 
     | 
    
         
            -
                end
         
     | 
| 
       234 
     | 
    
         
            -
             
     | 
| 
       235 
     | 
    
         
            -
                # Files or directories to be preloaded instead of lazy loaded.
         
     | 
| 
       236 
     | 
    
         
            -
                #
         
     | 
| 
       237 
     | 
    
         
            -
                # @sig (*(String | Pathname | Array[String | Pathname])) -> void
         
     | 
| 
       238 
     | 
    
         
            -
                def preload(*paths)
         
     | 
| 
       239 
     | 
    
         
            -
                  mutex.synchronize do
         
     | 
| 
       240 
     | 
    
         
            -
                    expand_paths(paths).each do |abspath|
         
     | 
| 
       241 
     | 
    
         
            -
                      preloads << abspath
         
     | 
| 
       242 
     | 
    
         
            -
                      do_preload_abspath(abspath) if @setup
         
     | 
| 
       243 
     | 
    
         
            -
                    end
         
     | 
| 
       244 
     | 
    
         
            -
                  end
         
     | 
| 
       245 
     | 
    
         
            -
                end
         
     | 
| 
       246 
     | 
    
         
            -
             
     | 
| 
       247 
     | 
    
         
            -
                # Configure files, directories, or glob patterns to be totally ignored.
         
     | 
| 
       248 
     | 
    
         
            -
                #
         
     | 
| 
       249 
     | 
    
         
            -
                # @sig (*(String | Pathname | Array[String | Pathname])) -> void
         
     | 
| 
       250 
     | 
    
         
            -
                def ignore(*glob_patterns)
         
     | 
| 
       251 
     | 
    
         
            -
                  glob_patterns = expand_paths(glob_patterns)
         
     | 
| 
       252 
     | 
    
         
            -
                  mutex.synchronize do
         
     | 
| 
       253 
     | 
    
         
            -
                    ignored_glob_patterns.merge(glob_patterns)
         
     | 
| 
       254 
     | 
    
         
            -
                    ignored_paths.merge(expand_glob_patterns(glob_patterns))
         
     | 
| 
       255 
     | 
    
         
            -
                  end
         
     | 
| 
       256 
     | 
    
         
            -
                end
         
     | 
| 
       257 
     | 
    
         
            -
             
     | 
| 
       258 
     | 
    
         
            -
                # Configure directories or glob patterns to be collapsed.
         
     | 
| 
       259 
     | 
    
         
            -
                #
         
     | 
| 
       260 
     | 
    
         
            -
                # @sig (*(String | Pathname | Array[String | Pathname])) -> void
         
     | 
| 
       261 
     | 
    
         
            -
                def collapse(*glob_patterns)
         
     | 
| 
       262 
     | 
    
         
            -
                  glob_patterns = expand_paths(glob_patterns)
         
     | 
| 
       263 
     | 
    
         
            -
                  mutex.synchronize do
         
     | 
| 
       264 
     | 
    
         
            -
                    collapse_glob_patterns.merge(glob_patterns)
         
     | 
| 
       265 
     | 
    
         
            -
                    collapse_dirs.merge(expand_glob_patterns(glob_patterns))
         
     | 
| 
       266 
     | 
    
         
            -
                  end
         
     | 
| 
       267 
     | 
    
         
            -
                end
         
     | 
| 
       268 
     | 
    
         
            -
             
     | 
| 
       269 
     | 
    
         
            -
                # Configure a block to be invoked once a certain constant path is loaded.
         
     | 
| 
       270 
     | 
    
         
            -
                # Supports multiple callbacks, and if there are many, they are executed in
         
     | 
| 
       271 
     | 
    
         
            -
                # the order in which they were defined.
         
     | 
| 
       272 
     | 
    
         
            -
                #
         
     | 
| 
       273 
     | 
    
         
            -
                #   loader.on_load("SomeApiClient") do
         
     | 
| 
       274 
     | 
    
         
            -
                #     SomeApiClient.endpoint = "https://api.dev"
         
     | 
| 
       275 
     | 
    
         
            -
                #   end
         
     | 
| 
       276 
     | 
    
         
            -
                #
         
     | 
| 
       277 
     | 
    
         
            -
                # @raise [TypeError]
         
     | 
| 
       278 
     | 
    
         
            -
                # @sig (String) { () -> void } -> void
         
     | 
| 
       279 
     | 
    
         
            -
                def on_load(cpath, &block)
         
     | 
| 
       280 
     | 
    
         
            -
                  raise TypeError, "on_load only accepts strings" unless cpath.is_a?(String)
         
     | 
| 
       281 
     | 
    
         
            -
             
     | 
| 
       282 
     | 
    
         
            -
                  mutex.synchronize do
         
     | 
| 
       283 
     | 
    
         
            -
                    (on_load_callbacks[cpath] ||= []) << block
         
     | 
| 
       284 
     | 
    
         
            -
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  Registry.register_loader(self)
         
     | 
| 
       285 
100 
     | 
    
         
             
                end
         
     | 
| 
       286 
101 
     | 
    
         | 
| 
       287 
     | 
    
         
            -
                # Sets autoloads in the root namespace 
     | 
| 
      
 102 
     | 
    
         
            +
                # Sets autoloads in the root namespace.
         
     | 
| 
       288 
103 
     | 
    
         
             
                #
         
     | 
| 
       289 
104 
     | 
    
         
             
                # @sig () -> void
         
     | 
| 
       290 
105 
     | 
    
         
             
                def setup
         
     | 
| 
         @@ -294,7 +109,6 @@ module Zeitwerk 
     | 
|
| 
       294 
109 
     | 
    
         
             
                    actual_root_dirs.each do |root_dir, namespace|
         
     | 
| 
       295 
110 
     | 
    
         
             
                      set_autoloads_in_dir(root_dir, namespace)
         
     | 
| 
       296 
111 
     | 
    
         
             
                    end
         
     | 
| 
       297 
     | 
    
         
            -
                    do_preload
         
     | 
| 
       298 
112 
     | 
    
         | 
| 
       299 
113 
     | 
    
         
             
                    @setup = true
         
     | 
| 
       300 
114 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -319,21 +133,26 @@ module Zeitwerk 
     | 
|
| 
       319 
133 
     | 
    
         
             
                    # is enough.
         
     | 
| 
       320 
134 
     | 
    
         
             
                    unloaded_files = Set.new
         
     | 
| 
       321 
135 
     | 
    
         | 
| 
       322 
     | 
    
         
            -
                    autoloads.each do | 
     | 
| 
      
 136 
     | 
    
         
            +
                    autoloads.each do |(parent, cname), abspath|
         
     | 
| 
       323 
137 
     | 
    
         
             
                      if parent.autoload?(cname)
         
     | 
| 
       324 
138 
     | 
    
         
             
                        unload_autoload(parent, cname)
         
     | 
| 
       325 
139 
     | 
    
         
             
                      else
         
     | 
| 
       326 
140 
     | 
    
         
             
                        # Could happen if loaded with require_relative. That is unsupported,
         
     | 
| 
       327 
141 
     | 
    
         
             
                        # and the constant path would escape unloadable_cpath? This is just
         
     | 
| 
       328 
142 
     | 
    
         
             
                        # defensive code to clean things up as much as we are able to.
         
     | 
| 
       329 
     | 
    
         
            -
                        unload_cref(parent, cname) 
     | 
| 
       330 
     | 
    
         
            -
                        unloaded_files.add( 
     | 
| 
      
 143 
     | 
    
         
            +
                        unload_cref(parent, cname)  if cdef?(parent, cname)
         
     | 
| 
      
 144 
     | 
    
         
            +
                        unloaded_files.add(abspath) if ruby?(abspath)
         
     | 
| 
       331 
145 
     | 
    
         
             
                      end
         
     | 
| 
       332 
146 
     | 
    
         
             
                    end
         
     | 
| 
       333 
147 
     | 
    
         | 
| 
       334 
     | 
    
         
            -
                    to_unload. 
     | 
| 
       335 
     | 
    
         
            -
                       
     | 
| 
       336 
     | 
    
         
            -
             
     | 
| 
      
 148 
     | 
    
         
            +
                    to_unload.each do |cpath, (abspath, (parent, cname))|
         
     | 
| 
      
 149 
     | 
    
         
            +
                      unless on_unload_callbacks.empty?
         
     | 
| 
      
 150 
     | 
    
         
            +
                        value = parent.const_get(cname)
         
     | 
| 
      
 151 
     | 
    
         
            +
                        run_on_unload_callbacks(cpath, value, abspath)
         
     | 
| 
      
 152 
     | 
    
         
            +
                      end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                      unload_cref(parent, cname)  if cdef?(parent, cname)
         
     | 
| 
      
 155 
     | 
    
         
            +
                      unloaded_files.add(abspath) if ruby?(abspath)
         
     | 
| 
       337 
156 
     | 
    
         
             
                    end
         
     | 
| 
       338 
157 
     | 
    
         | 
| 
       339 
158 
     | 
    
         
             
                    unless unloaded_files.empty?
         
     | 
| 
         @@ -393,27 +212,29 @@ module Zeitwerk 
     | 
|
| 
       393 
212 
     | 
    
         
             
                  mutex.synchronize do
         
     | 
| 
       394 
213 
     | 
    
         
             
                    break if @eager_loaded
         
     | 
| 
       395 
214 
     | 
    
         | 
| 
      
 215 
     | 
    
         
            +
                    log("eager load start") if logger
         
     | 
| 
      
 216 
     | 
    
         
            +
             
     | 
| 
       396 
217 
     | 
    
         
             
                    queue = []
         
     | 
| 
       397 
218 
     | 
    
         
             
                    actual_root_dirs.each do |root_dir, namespace|
         
     | 
| 
       398 
     | 
    
         
            -
                      queue << [namespace, root_dir] unless  
     | 
| 
      
 219 
     | 
    
         
            +
                      queue << [namespace, root_dir] unless excluded_from_eager_load?(root_dir)
         
     | 
| 
       399 
220 
     | 
    
         
             
                    end
         
     | 
| 
       400 
221 
     | 
    
         | 
| 
       401 
222 
     | 
    
         
             
                    while to_eager_load = queue.shift
         
     | 
| 
       402 
223 
     | 
    
         
             
                      namespace, dir = to_eager_load
         
     | 
| 
       403 
224 
     | 
    
         | 
| 
       404 
225 
     | 
    
         
             
                      ls(dir) do |basename, abspath|
         
     | 
| 
       405 
     | 
    
         
            -
                        next if  
     | 
| 
      
 226 
     | 
    
         
            +
                        next if excluded_from_eager_load?(abspath)
         
     | 
| 
       406 
227 
     | 
    
         | 
| 
       407 
228 
     | 
    
         
             
                        if ruby?(abspath)
         
     | 
| 
       408 
     | 
    
         
            -
                          if cref = autoloads 
     | 
| 
       409 
     | 
    
         
            -
                             
     | 
| 
      
 229 
     | 
    
         
            +
                          if cref = autoloads.cref_for(abspath)
         
     | 
| 
      
 230 
     | 
    
         
            +
                            cget(*cref)
         
     | 
| 
       410 
231 
     | 
    
         
             
                          end
         
     | 
| 
       411 
232 
     | 
    
         
             
                        elsif dir?(abspath) && !root_dirs.key?(abspath)
         
     | 
| 
       412 
     | 
    
         
            -
                          if  
     | 
| 
      
 233 
     | 
    
         
            +
                          if collapse?(abspath)
         
     | 
| 
       413 
234 
     | 
    
         
             
                            queue << [namespace, abspath]
         
     | 
| 
       414 
235 
     | 
    
         
             
                          else
         
     | 
| 
       415 
236 
     | 
    
         
             
                            cname = inflector.camelize(basename, abspath)
         
     | 
| 
       416 
     | 
    
         
            -
                            queue << [namespace 
     | 
| 
      
 237 
     | 
    
         
            +
                            queue << [cget(namespace, cname), abspath]
         
     | 
| 
       417 
238 
     | 
    
         
             
                          end
         
     | 
| 
       418 
239 
     | 
    
         
             
                        end
         
     | 
| 
       419 
240 
     | 
    
         
             
                      end
         
     | 
| 
         @@ -425,15 +246,9 @@ module Zeitwerk 
     | 
|
| 
       425 
246 
     | 
    
         
             
                    autoloaded_dirs.clear
         
     | 
| 
       426 
247 
     | 
    
         | 
| 
       427 
248 
     | 
    
         
             
                    @eager_loaded = true
         
     | 
| 
       428 
     | 
    
         
            -
                  end
         
     | 
| 
       429 
     | 
    
         
            -
                end
         
     | 
| 
       430 
249 
     | 
    
         | 
| 
       431 
     | 
    
         
            -
             
     | 
| 
       432 
     | 
    
         
            -
             
     | 
| 
       433 
     | 
    
         
            -
                #
         
     | 
| 
       434 
     | 
    
         
            -
                # @sig (*(String | Pathname | Array[String | Pathname])) -> void
         
     | 
| 
       435 
     | 
    
         
            -
                def do_not_eager_load(*paths)
         
     | 
| 
       436 
     | 
    
         
            -
                  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
         
     | 
| 
      
 250 
     | 
    
         
            +
                    log("eager load end") if logger
         
     | 
| 
      
 251 
     | 
    
         
            +
                  end
         
     | 
| 
       437 
252 
     | 
    
         
             
                end
         
     | 
| 
       438 
253 
     | 
    
         | 
| 
       439 
254 
     | 
    
         
             
                # Says if the given constant path would be unloaded on reload. This
         
     | 
| 
         @@ -452,28 +267,6 @@ module Zeitwerk 
     | 
|
| 
       452 
267 
     | 
    
         
             
                  to_unload.keys.freeze
         
     | 
| 
       453 
268 
     | 
    
         
             
                end
         
     | 
| 
       454 
269 
     | 
    
         | 
| 
       455 
     | 
    
         
            -
                # Logs to `$stdout`, handy shortcut for debugging.
         
     | 
| 
       456 
     | 
    
         
            -
                #
         
     | 
| 
       457 
     | 
    
         
            -
                # @sig () -> void
         
     | 
| 
       458 
     | 
    
         
            -
                def log!
         
     | 
| 
       459 
     | 
    
         
            -
                  @logger = ->(msg) { puts msg }
         
     | 
| 
       460 
     | 
    
         
            -
                end
         
     | 
| 
       461 
     | 
    
         
            -
             
     | 
| 
       462 
     | 
    
         
            -
                # @private
         
     | 
| 
       463 
     | 
    
         
            -
                # @sig (String) -> bool
         
     | 
| 
       464 
     | 
    
         
            -
                def manages?(dir)
         
     | 
| 
       465 
     | 
    
         
            -
                  dir = dir + "/"
         
     | 
| 
       466 
     | 
    
         
            -
                  ignored_paths.each do |ignored_path|
         
     | 
| 
       467 
     | 
    
         
            -
                    return false if dir.start_with?(ignored_path + "/")
         
     | 
| 
       468 
     | 
    
         
            -
                  end
         
     | 
| 
       469 
     | 
    
         
            -
             
     | 
| 
       470 
     | 
    
         
            -
                  root_dirs.each_key do |root_dir|
         
     | 
| 
       471 
     | 
    
         
            -
                    return true if root_dir.start_with?(dir) || dir.start_with?(root_dir + "/")
         
     | 
| 
       472 
     | 
    
         
            -
                  end
         
     | 
| 
       473 
     | 
    
         
            -
             
     | 
| 
       474 
     | 
    
         
            -
                  false
         
     | 
| 
       475 
     | 
    
         
            -
                end
         
     | 
| 
       476 
     | 
    
         
            -
             
     | 
| 
       477 
270 
     | 
    
         
             
                # --- Class methods ---------------------------------------------------------------------------
         
     | 
| 
       478 
271 
     | 
    
         | 
| 
       479 
272 
     | 
    
         
             
                class << self
         
     | 
| 
         @@ -521,19 +314,12 @@ module Zeitwerk 
     | 
|
| 
       521 
314 
     | 
    
         | 
| 
       522 
315 
     | 
    
         
             
                private # -------------------------------------------------------------------------------------
         
     | 
| 
       523 
316 
     | 
    
         | 
| 
       524 
     | 
    
         
            -
                # @sig () -> Array[String]
         
     | 
| 
       525 
     | 
    
         
            -
                def actual_root_dirs
         
     | 
| 
       526 
     | 
    
         
            -
                  root_dirs.reject do |root_dir, _namespace|
         
     | 
| 
       527 
     | 
    
         
            -
                    !dir?(root_dir) || ignored_paths.member?(root_dir)
         
     | 
| 
       528 
     | 
    
         
            -
                  end
         
     | 
| 
       529 
     | 
    
         
            -
                end
         
     | 
| 
       530 
     | 
    
         
            -
             
     | 
| 
       531 
317 
     | 
    
         
             
                # @sig (String, Module) -> void
         
     | 
| 
       532 
318 
     | 
    
         
             
                def set_autoloads_in_dir(dir, parent)
         
     | 
| 
       533 
319 
     | 
    
         
             
                  ls(dir) do |basename, abspath|
         
     | 
| 
       534 
320 
     | 
    
         
             
                    begin
         
     | 
| 
       535 
321 
     | 
    
         
             
                      if ruby?(basename)
         
     | 
| 
       536 
     | 
    
         
            -
                        basename 
     | 
| 
      
 322 
     | 
    
         
            +
                        basename.delete_suffix!(".rb")
         
     | 
| 
       537 
323 
     | 
    
         
             
                        cname = inflector.camelize(basename, abspath).to_sym
         
     | 
| 
       538 
324 
     | 
    
         
             
                        autoload_file(parent, cname, abspath)
         
     | 
| 
       539 
325 
     | 
    
         
             
                      elsif dir?(abspath)
         
     | 
| 
         @@ -543,9 +329,9 @@ module Zeitwerk 
     | 
|
| 
       543 
329 
     | 
    
         
             
                        # To resolve the ambiguity file name -> constant path this introduces,
         
     | 
| 
       544 
330 
     | 
    
         
             
                        # the `app/models/concerns` directory is totally ignored as a namespace,
         
     | 
| 
       545 
331 
     | 
    
         
             
                        # it counts only as root. The guard checks that.
         
     | 
| 
       546 
     | 
    
         
            -
                        unless  
     | 
| 
      
 332 
     | 
    
         
            +
                        unless root_dir?(abspath)
         
     | 
| 
       547 
333 
     | 
    
         
             
                          cname = inflector.camelize(basename, abspath).to_sym
         
     | 
| 
       548 
     | 
    
         
            -
                          if  
     | 
| 
      
 334 
     | 
    
         
            +
                          if collapse?(abspath)
         
     | 
| 
       549 
335 
     | 
    
         
             
                            set_autoloads_in_dir(abspath, parent)
         
     | 
| 
       550 
336 
     | 
    
         
             
                          else
         
     | 
| 
       551 
337 
     | 
    
         
             
                            autoload_subdir(parent, cname, abspath)
         
     | 
| 
         @@ -573,27 +359,28 @@ module Zeitwerk 
     | 
|
| 
       573 
359 
     | 
    
         | 
| 
       574 
360 
     | 
    
         
             
                # @sig (Module, Symbol, String) -> void
         
     | 
| 
       575 
361 
     | 
    
         
             
                def autoload_subdir(parent, cname, subdir)
         
     | 
| 
       576 
     | 
    
         
            -
                  if autoload_path =  
     | 
| 
      
 362 
     | 
    
         
            +
                  if autoload_path = autoloads.abspath_for(parent, cname)
         
     | 
| 
       577 
363 
     | 
    
         
             
                    cpath = cpath(parent, cname)
         
     | 
| 
       578 
364 
     | 
    
         
             
                    register_explicit_namespace(cpath) if ruby?(autoload_path)
         
     | 
| 
       579 
365 
     | 
    
         
             
                    # We do not need to issue another autoload, the existing one is enough
         
     | 
| 
       580 
366 
     | 
    
         
             
                    # no matter if it is for a file or a directory. Just remember the
         
     | 
| 
       581 
367 
     | 
    
         
             
                    # subdirectory has to be visited if the namespace is used.
         
     | 
| 
       582 
     | 
    
         
            -
                     
     | 
| 
      
 368 
     | 
    
         
            +
                    lazy_subdirs[cpath] << subdir
         
     | 
| 
       583 
369 
     | 
    
         
             
                  elsif !cdef?(parent, cname)
         
     | 
| 
       584 
370 
     | 
    
         
             
                    # First time we find this namespace, set an autoload for it.
         
     | 
| 
       585 
     | 
    
         
            -
                     
     | 
| 
      
 371 
     | 
    
         
            +
                    lazy_subdirs[cpath(parent, cname)] << subdir
         
     | 
| 
       586 
372 
     | 
    
         
             
                    set_autoload(parent, cname, subdir)
         
     | 
| 
       587 
373 
     | 
    
         
             
                  else
         
     | 
| 
       588 
374 
     | 
    
         
             
                    # For whatever reason the constant that corresponds to this namespace has
         
     | 
| 
       589 
375 
     | 
    
         
             
                    # already been defined, we have to recurse.
         
     | 
| 
       590 
     | 
    
         
            -
                     
     | 
| 
      
 376 
     | 
    
         
            +
                    log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
         
     | 
| 
      
 377 
     | 
    
         
            +
                    set_autoloads_in_dir(subdir, cget(parent, cname))
         
     | 
| 
       591 
378 
     | 
    
         
             
                  end
         
     | 
| 
       592 
379 
     | 
    
         
             
                end
         
     | 
| 
       593 
380 
     | 
    
         | 
| 
       594 
381 
     | 
    
         
             
                # @sig (Module, Symbol, String) -> void
         
     | 
| 
       595 
382 
     | 
    
         
             
                def autoload_file(parent, cname, file)
         
     | 
| 
       596 
     | 
    
         
            -
                  if autoload_path =  
     | 
| 
      
 383 
     | 
    
         
            +
                  if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
         
     | 
| 
       597 
384 
     | 
    
         
             
                    # First autoload for a Ruby file wins, just ignore subsequent ones.
         
     | 
| 
       598 
385 
     | 
    
         
             
                    if ruby?(autoload_path)
         
     | 
| 
       599 
386 
     | 
    
         
             
                      log("file #{file} is ignored because #{autoload_path} has precedence") if logger
         
     | 
| 
         @@ -620,163 +407,32 @@ module Zeitwerk 
     | 
|
| 
       620 
407 
     | 
    
         
             
                  autoloads.delete(dir)
         
     | 
| 
       621 
408 
     | 
    
         
             
                  Registry.unregister_autoload(dir)
         
     | 
| 
       622 
409 
     | 
    
         | 
| 
      
 410 
     | 
    
         
            +
                  log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
         
     | 
| 
      
 411 
     | 
    
         
            +
             
     | 
| 
       623 
412 
     | 
    
         
             
                  set_autoload(parent, cname, file)
         
     | 
| 
       624 
413 
     | 
    
         
             
                  register_explicit_namespace(cpath(parent, cname))
         
     | 
| 
       625 
414 
     | 
    
         
             
                end
         
     | 
| 
       626 
415 
     | 
    
         | 
| 
       627 
416 
     | 
    
         
             
                # @sig (Module, Symbol, String) -> void
         
     | 
| 
       628 
417 
     | 
    
         
             
                def set_autoload(parent, cname, abspath)
         
     | 
| 
       629 
     | 
    
         
            -
                   
     | 
| 
       630 
     | 
    
         
            -
             
     | 
| 
       631 
     | 
    
         
            -
                  # be able to do a lookup later in Kernel#require for manual require calls.
         
     | 
| 
       632 
     | 
    
         
            -
                  #
         
     | 
| 
       633 
     | 
    
         
            -
                  # We freeze realpath because that saves allocations in Module#autoload.
         
     | 
| 
       634 
     | 
    
         
            -
                  # See #125.
         
     | 
| 
       635 
     | 
    
         
            -
                  realpath = File.realpath(abspath).freeze
         
     | 
| 
       636 
     | 
    
         
            -
                  parent.autoload(cname, realpath)
         
     | 
| 
      
 418 
     | 
    
         
            +
                  autoloads.define(parent, cname, abspath)
         
     | 
| 
      
 419 
     | 
    
         
            +
             
     | 
| 
       637 
420 
     | 
    
         
             
                  if logger
         
     | 
| 
       638 
     | 
    
         
            -
                    if ruby?( 
     | 
| 
       639 
     | 
    
         
            -
                      log("autoload set for #{cpath(parent, cname)}, to be loaded from #{ 
     | 
| 
      
 421 
     | 
    
         
            +
                    if ruby?(abspath)
         
     | 
| 
      
 422 
     | 
    
         
            +
                      log("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")
         
     | 
| 
       640 
423 
     | 
    
         
             
                    else
         
     | 
| 
       641 
     | 
    
         
            -
                      log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{ 
     | 
| 
      
 424 
     | 
    
         
            +
                      log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
         
     | 
| 
       642 
425 
     | 
    
         
             
                    end
         
     | 
| 
       643 
426 
     | 
    
         
             
                  end
         
     | 
| 
       644 
427 
     | 
    
         | 
| 
       645 
     | 
    
         
            -
                   
     | 
| 
       646 
     | 
    
         
            -
                  Registry.register_autoload(self, realpath)
         
     | 
| 
      
 428 
     | 
    
         
            +
                  Registry.register_autoload(self, abspath)
         
     | 
| 
       647 
429 
     | 
    
         | 
| 
       648 
430 
     | 
    
         
             
                  # See why in the documentation of Zeitwerk::Registry.inceptions.
         
     | 
| 
       649 
431 
     | 
    
         
             
                  unless parent.autoload?(cname)
         
     | 
| 
       650 
     | 
    
         
            -
                    Registry.register_inception(cpath(parent, cname),  
     | 
| 
       651 
     | 
    
         
            -
                  end
         
     | 
| 
       652 
     | 
    
         
            -
                end
         
     | 
| 
       653 
     | 
    
         
            -
             
     | 
| 
       654 
     | 
    
         
            -
                # @sig (Module, Symbol) -> String?
         
     | 
| 
       655 
     | 
    
         
            -
                def autoload_for?(parent, cname)
         
     | 
| 
       656 
     | 
    
         
            -
                  strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
         
     | 
| 
       657 
     | 
    
         
            -
                end
         
     | 
| 
       658 
     | 
    
         
            -
             
     | 
| 
       659 
     | 
    
         
            -
                # The autoload? predicate takes into account the ancestor chain of the
         
     | 
| 
       660 
     | 
    
         
            -
                # receiver, like const_defined? and other methods in the constants API do.
         
     | 
| 
       661 
     | 
    
         
            -
                #
         
     | 
| 
       662 
     | 
    
         
            -
                # For example, given
         
     | 
| 
       663 
     | 
    
         
            -
                #
         
     | 
| 
       664 
     | 
    
         
            -
                #   class A
         
     | 
| 
       665 
     | 
    
         
            -
                #     autoload :X, "x.rb"
         
     | 
| 
       666 
     | 
    
         
            -
                #   end
         
     | 
| 
       667 
     | 
    
         
            -
                #
         
     | 
| 
       668 
     | 
    
         
            -
                #   class B < A
         
     | 
| 
       669 
     | 
    
         
            -
                #   end
         
     | 
| 
       670 
     | 
    
         
            -
                #
         
     | 
| 
       671 
     | 
    
         
            -
                # B.autoload?(:X) returns "x.rb".
         
     | 
| 
       672 
     | 
    
         
            -
                #
         
     | 
| 
       673 
     | 
    
         
            -
                # We need a way to strictly check in parent ignoring ancestors.
         
     | 
| 
       674 
     | 
    
         
            -
                #
         
     | 
| 
       675 
     | 
    
         
            -
                # @sig (Module, Symbol) -> String?
         
     | 
| 
       676 
     | 
    
         
            -
                if method(:autoload?).arity == 1
         
     | 
| 
       677 
     | 
    
         
            -
                  def strict_autoload_path(parent, cname)
         
     | 
| 
       678 
     | 
    
         
            -
                    parent.autoload?(cname) if cdef?(parent, cname)
         
     | 
| 
       679 
     | 
    
         
            -
                  end
         
     | 
| 
       680 
     | 
    
         
            -
                else
         
     | 
| 
       681 
     | 
    
         
            -
                  def strict_autoload_path(parent, cname)
         
     | 
| 
       682 
     | 
    
         
            -
                    parent.autoload?(cname, false)
         
     | 
| 
       683 
     | 
    
         
            -
                  end
         
     | 
| 
       684 
     | 
    
         
            -
                end
         
     | 
| 
       685 
     | 
    
         
            -
             
     | 
| 
       686 
     | 
    
         
            -
                # This method is called this way because I prefer `preload` to be the method
         
     | 
| 
       687 
     | 
    
         
            -
                # name to configure preloads in the public interface.
         
     | 
| 
       688 
     | 
    
         
            -
                #
         
     | 
| 
       689 
     | 
    
         
            -
                # @sig () -> void
         
     | 
| 
       690 
     | 
    
         
            -
                def do_preload
         
     | 
| 
       691 
     | 
    
         
            -
                  preloads.each do |abspath|
         
     | 
| 
       692 
     | 
    
         
            -
                    do_preload_abspath(abspath)
         
     | 
| 
      
 432 
     | 
    
         
            +
                    Registry.register_inception(cpath(parent, cname), abspath, self)
         
     | 
| 
       693 
433 
     | 
    
         
             
                  end
         
     | 
| 
       694 
434 
     | 
    
         
             
                end
         
     | 
| 
       695 
435 
     | 
    
         | 
| 
       696 
     | 
    
         
            -
                # @sig (String) -> void
         
     | 
| 
       697 
     | 
    
         
            -
                def do_preload_abspath(abspath)
         
     | 
| 
       698 
     | 
    
         
            -
                  if ruby?(abspath)
         
     | 
| 
       699 
     | 
    
         
            -
                    do_preload_file(abspath)
         
     | 
| 
       700 
     | 
    
         
            -
                  elsif dir?(abspath)
         
     | 
| 
       701 
     | 
    
         
            -
                    do_preload_dir(abspath)
         
     | 
| 
       702 
     | 
    
         
            -
                  end
         
     | 
| 
       703 
     | 
    
         
            -
                end
         
     | 
| 
       704 
     | 
    
         
            -
             
     | 
| 
       705 
     | 
    
         
            -
                # @sig (String) -> void
         
     | 
| 
       706 
     | 
    
         
            -
                def do_preload_dir(dir)
         
     | 
| 
       707 
     | 
    
         
            -
                  ls(dir) do |_basename, abspath|
         
     | 
| 
       708 
     | 
    
         
            -
                    do_preload_abspath(abspath)
         
     | 
| 
       709 
     | 
    
         
            -
                  end
         
     | 
| 
       710 
     | 
    
         
            -
                end
         
     | 
| 
       711 
     | 
    
         
            -
             
     | 
| 
       712 
     | 
    
         
            -
                # @sig (String) -> bool
         
     | 
| 
       713 
     | 
    
         
            -
                def do_preload_file(file)
         
     | 
| 
       714 
     | 
    
         
            -
                  log("preloading #{file}") if logger
         
     | 
| 
       715 
     | 
    
         
            -
                  require file
         
     | 
| 
       716 
     | 
    
         
            -
                end
         
     | 
| 
       717 
     | 
    
         
            -
             
     | 
| 
       718 
     | 
    
         
            -
                # @sig (Module, Symbol) -> String
         
     | 
| 
       719 
     | 
    
         
            -
                def cpath(parent, cname)
         
     | 
| 
       720 
     | 
    
         
            -
                  parent.equal?(Object) ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
         
     | 
| 
       721 
     | 
    
         
            -
                end
         
     | 
| 
       722 
     | 
    
         
            -
             
     | 
| 
       723 
     | 
    
         
            -
                # @sig (String) { (String, String) -> void } -> void
         
     | 
| 
       724 
     | 
    
         
            -
                def ls(dir)
         
     | 
| 
       725 
     | 
    
         
            -
                  Dir.foreach(dir) do |basename|
         
     | 
| 
       726 
     | 
    
         
            -
                    next if basename.start_with?(".")
         
     | 
| 
       727 
     | 
    
         
            -
             
     | 
| 
       728 
     | 
    
         
            -
                    abspath = File.join(dir, basename)
         
     | 
| 
       729 
     | 
    
         
            -
                    next if ignored_paths.member?(abspath)
         
     | 
| 
       730 
     | 
    
         
            -
             
     | 
| 
       731 
     | 
    
         
            -
                    # We freeze abspath because that saves allocations when passed later to
         
     | 
| 
       732 
     | 
    
         
            -
                    # File methods. See #125.
         
     | 
| 
       733 
     | 
    
         
            -
                    yield basename, abspath.freeze
         
     | 
| 
       734 
     | 
    
         
            -
                  end
         
     | 
| 
       735 
     | 
    
         
            -
                end
         
     | 
| 
       736 
     | 
    
         
            -
             
     | 
| 
       737 
     | 
    
         
            -
                # @sig (String) -> bool
         
     | 
| 
       738 
     | 
    
         
            -
                def ruby?(path)
         
     | 
| 
       739 
     | 
    
         
            -
                  path.end_with?(".rb")
         
     | 
| 
       740 
     | 
    
         
            -
                end
         
     | 
| 
       741 
     | 
    
         
            -
             
     | 
| 
       742 
     | 
    
         
            -
                # @sig (String) -> bool
         
     | 
| 
       743 
     | 
    
         
            -
                def dir?(path)
         
     | 
| 
       744 
     | 
    
         
            -
                  File.directory?(path)
         
     | 
| 
       745 
     | 
    
         
            -
                end
         
     | 
| 
       746 
     | 
    
         
            -
             
     | 
| 
       747 
     | 
    
         
            -
                # @sig (String | Pathname | Array[String | Pathname]) -> Array[String]
         
     | 
| 
       748 
     | 
    
         
            -
                def expand_paths(paths)
         
     | 
| 
       749 
     | 
    
         
            -
                  paths.flatten.map! { |path| File.expand_path(path) }
         
     | 
| 
       750 
     | 
    
         
            -
                end
         
     | 
| 
       751 
     | 
    
         
            -
             
     | 
| 
       752 
     | 
    
         
            -
                # @sig (Array[String]) -> Array[String]
         
     | 
| 
       753 
     | 
    
         
            -
                def expand_glob_patterns(glob_patterns)
         
     | 
| 
       754 
     | 
    
         
            -
                  # Note that Dir.glob works with regular file names just fine. That is,
         
     | 
| 
       755 
     | 
    
         
            -
                  # glob patterns technically need no wildcards.
         
     | 
| 
       756 
     | 
    
         
            -
                  glob_patterns.flat_map { |glob_pattern| Dir.glob(glob_pattern) }
         
     | 
| 
       757 
     | 
    
         
            -
                end
         
     | 
| 
       758 
     | 
    
         
            -
             
     | 
| 
       759 
     | 
    
         
            -
                # @sig () -> void
         
     | 
| 
       760 
     | 
    
         
            -
                def recompute_ignored_paths
         
     | 
| 
       761 
     | 
    
         
            -
                  ignored_paths.replace(expand_glob_patterns(ignored_glob_patterns))
         
     | 
| 
       762 
     | 
    
         
            -
                end
         
     | 
| 
       763 
     | 
    
         
            -
             
     | 
| 
       764 
     | 
    
         
            -
                # @sig () -> void
         
     | 
| 
       765 
     | 
    
         
            -
                def recompute_collapse_dirs
         
     | 
| 
       766 
     | 
    
         
            -
                  collapse_dirs.replace(expand_glob_patterns(collapse_glob_patterns))
         
     | 
| 
       767 
     | 
    
         
            -
                end
         
     | 
| 
       768 
     | 
    
         
            -
             
     | 
| 
       769 
     | 
    
         
            -
                # @sig (String) -> void
         
     | 
| 
       770 
     | 
    
         
            -
                def log(message)
         
     | 
| 
       771 
     | 
    
         
            -
                  method_name = logger.respond_to?(:debug) ? :debug : :call
         
     | 
| 
       772 
     | 
    
         
            -
                  logger.send(method_name, "Zeitwerk@#{tag}: #{message}")
         
     | 
| 
       773 
     | 
    
         
            -
                end
         
     | 
| 
       774 
     | 
    
         
            -
             
     | 
| 
       775 
     | 
    
         
            -
                # @sig (Module, Symbol) -> bool
         
     | 
| 
       776 
     | 
    
         
            -
                def cdef?(parent, cname)
         
     | 
| 
       777 
     | 
    
         
            -
                  parent.const_defined?(cname, false)
         
     | 
| 
       778 
     | 
    
         
            -
                end
         
     | 
| 
       779 
     | 
    
         
            -
             
     | 
| 
       780 
436 
     | 
    
         
             
                # @sig (String) -> void
         
     | 
| 
       781 
437 
     | 
    
         
             
                def register_explicit_namespace(cpath)
         
     | 
| 
       782 
438 
     | 
    
         
             
                  ExplicitNamespace.register(cpath, self)
         
     | 
| 
         @@ -797,6 +453,13 @@ module Zeitwerk 
     | 
|
| 
       797 
453 
     | 
    
         
             
                  end
         
     | 
| 
       798 
454 
     | 
    
         
             
                end
         
     | 
| 
       799 
455 
     | 
    
         | 
| 
      
 456 
     | 
    
         
            +
                # @sig (String, Object, String) -> void
         
     | 
| 
      
 457 
     | 
    
         
            +
                def run_on_unload_callbacks(cpath, value, abspath)
         
     | 
| 
      
 458 
     | 
    
         
            +
                  # Order matters. If present, run the most specific one.
         
     | 
| 
      
 459 
     | 
    
         
            +
                  on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
         
     | 
| 
      
 460 
     | 
    
         
            +
                  on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
         
     | 
| 
      
 461 
     | 
    
         
            +
                end
         
     | 
| 
      
 462 
     | 
    
         
            +
             
     | 
| 
       800 
463 
     | 
    
         
             
                # @sig (Module, Symbol) -> void
         
     | 
| 
       801 
464 
     | 
    
         
             
                def unload_autoload(parent, cname)
         
     | 
| 
       802 
465 
     | 
    
         
             
                  parent.__send__(:remove_const, cname)
         
     |