zeitwerk 2.6.16 → 2.7.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b69678cbbd525cff55aa4cd08d1f3a785d69cac3e851e3cc2a168ef029163a3
4
- data.tar.gz: 9da1ded3c41f766feee609adedc9706d4efb4cfd3701c6944f0e781986d578ef
3
+ metadata.gz: 1500c4f54c6ac7e64eef74c8702682764fa845533a5b16c67a48ee11781ef3e0
4
+ data.tar.gz: f7201576b8b59ab3786cd4bc6fd58f3cd3afa3d8dcc1e86477121000f06ac50a
5
5
  SHA512:
6
- metadata.gz: 76f28b7d765b0209f489cb23f939143a6d1800e997e9a1722a0482aaf4e942f59eef26ec1e9e98a7e722710ba01c3de706f01a64594688310ca7908edb5c4de1
7
- data.tar.gz: 2da1dfd280a4fb987f281094e45ce9c1797323680cd90f586c60660516995262aaeb4d38c55e8c330a5c879f5471567633ffe72298371c33a05b5cb6f3805285
6
+ metadata.gz: ab113d90c9a42ec5c75c6ebe68ab5a04395f1b33228de42de6952f2e673a329f30de85477977ec11d582df82f40c4dc5c08e8ba2305f46a6d07ac4e88c564887
7
+ data.tar.gz: 73fb9dc78a9a7148119b306f93a513f841e4888d421fbf178963bbfa1368a8c7c66cd8661a28e8d95d14f51dab16fb4ff886f23183ec13a74847c45c1850f30d
data/README.md CHANGED
@@ -54,7 +54,6 @@
54
54
  - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
55
55
  - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
56
56
  - [Shadowed files](#shadowed-files)
57
- - [Edge cases](#edge-cases)
58
57
  - [Beware of circular dependencies](#beware-of-circular-dependencies)
59
58
  - [Reopening third-party namespaces](#reopening-third-party-namespaces)
60
59
  - [Introspection](#introspection)
@@ -63,7 +62,6 @@
63
62
  - [`Zeitwerk::Loader#all_expected_cpaths`](#zeitwerkloaderall_expected_cpaths)
64
63
  - [Encodings](#encodings)
65
64
  - [Rules of thumb](#rules-of-thumb)
66
- - [Debuggers](#debuggers)
67
65
  - [Pronunciation](#pronunciation)
68
66
  - [Supported Ruby versions](#supported-ruby-versions)
69
67
  - [Testing](#testing)
@@ -283,6 +281,8 @@ class Hotel < ApplicationRecord
283
281
  end
284
282
  ```
285
283
 
284
+ When autoloaded, Zeitwerk verifies the expected constant (`Hotel` in the example) stores a class or module object. If it doesn't, `Zeitwerk::Error` is raised.
285
+
286
286
  An explicit namespace must be managed by one single loader. Loaders that reopen namespaces owned by other projects are responsible for loading their constants before setup.
287
287
 
288
288
  <a id="markdown-collapsing-directories" name="collapsing-directories"></a>
@@ -1178,36 +1178,6 @@ file #{file} is ignored because #{constant_path} is already defined
1178
1178
 
1179
1179
  Shadowing only applies to Ruby files, namespace definition can be spread over multiple directories. And you can also reopen third-party namespaces if done [orderly](#reopening-third-party-namespaces).
1180
1180
 
1181
- <a id="markdown-edge-cases" name="edge-cases"></a>
1182
- ### Edge cases
1183
-
1184
- [Explicit namespaces](#explicit-namespaces) like `Trip` here:
1185
-
1186
- ```ruby
1187
- # trip.rb
1188
- class Trip
1189
- include Geolocation
1190
- end
1191
-
1192
- # trip/geolocation.rb
1193
- module Trip::Geolocation
1194
- ...
1195
- end
1196
- ```
1197
-
1198
- have to be defined with the `class`/`module` keywords, as in the example above.
1199
-
1200
- For technical reasons, raw constant assignment is not supported:
1201
-
1202
- ```ruby
1203
- # trip.rb
1204
- Trip = Class { ...} # NOT SUPPORTED
1205
- Trip = Struct.new { ... } # NOT SUPPORTED
1206
- Trip = Data.define { ... } # NOT SUPPORTED
1207
- ```
1208
-
1209
- This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
1210
-
1211
1181
  <a id="markdown-beware-of-circular-dependencies" name="beware-of-circular-dependencies"></a>
1212
1182
  ### Beware of circular dependencies
1213
1183
 
@@ -1406,15 +1376,6 @@ The test suite passes on Windows with codepage `Windows-1252` if all the involve
1406
1376
 
1407
1377
  6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
1408
1378
 
1409
- <a id="markdown-debuggers" name="debuggers"></a>
1410
- ### Debuggers
1411
-
1412
- Zeitwerk and [debug.rb](https://github.com/ruby/debug) are fully compatible if CRuby is ≥ 3.1 (see [ruby/debug#558](https://github.com/ruby/debug/pull/558)).
1413
-
1414
- [Byebug](https://github.com/deivid-rodriguez/byebug) is compatible except for an edge case explained in [deivid-rodriguez/byebug#564](https://github.com/deivid-rodriguez/byebug/issues/564). Prior to CRuby 3.1, `debug.rb` has a similar edge incompatibility.
1415
-
1416
- [Break](https://github.com/gsamokovarov/break) is fully compatible.
1417
-
1418
1379
  <a id="markdown-pronunciation" name="pronunciation"></a>
1419
1380
  ## Pronunciation
1420
1381
 
@@ -1423,9 +1384,12 @@ Zeitwerk and [debug.rb](https://github.com/ruby/debug) are fully compatible if C
1423
1384
  <a id="markdown-supported-ruby-versions" name="supported-ruby-versions"></a>
1424
1385
  ## Supported Ruby versions
1425
1386
 
1426
- Zeitwerk works with CRuby 2.5 and above.
1387
+ Starting with version 2.7, Zeitwerk requires Ruby 3.2 or newer.
1427
1388
 
1428
- On TruffleRuby all is good except for thread-safety. Right now, in TruffleRuby `Module#autoload` does not block threads accessing a constant that is being autoloaded. CRuby prevents such access to avoid concurrent threads from seeing partial evaluations of the corresponding file. Zeitwerk inherits autoloading thread-safety from this property. This is not an issue if your project gets eager loaded, or if you lazy load in single-threaded environments. (See https://github.com/oracle/truffleruby/issues/2431.)
1389
+ Zeitwerk 2.7 requires TruffleRuby 24.1.2+ due to https://github.com/oracle/truffleruby/issues/3683.
1390
+ Alternatively, TruffleRuby users can use a `< 2.7` version constraint for the `zeitwerk` gem.
1391
+ As of this writing, [autoloading is not fully thread-safe yet on TruffleRuby](https://github.com/oracle/truffleruby/issues/2431).
1392
+ If your program is multi-threaded, you need to eager load before threads are created.
1429
1393
 
1430
1394
  JRuby 9.3.0.0 is almost there. As of this writing, the test suite of Zeitwerk passes on JRuby except for three tests. (See https://github.com/jruby/jruby/issues/6781.)
1431
1395
 
@@ -19,9 +19,9 @@ module Kernel
19
19
  alias_method :zeitwerk_original_require, :require
20
20
  end
21
21
 
22
- # @sig (String) -> true | false
22
+ #: (String) -> bool
23
23
  def require(path)
24
- if loader = Zeitwerk::Registry.loader_for(path)
24
+ if loader = Zeitwerk::Registry.autoloads.registered?(path)
25
25
  if path.end_with?(".rb")
26
26
  required = zeitwerk_original_require(path)
27
27
  loader.__on_file_autoloaded(path) if required
@@ -34,7 +34,7 @@ module Kernel
34
34
  required = zeitwerk_original_require(path)
35
35
  if required
36
36
  abspath = $LOADED_FEATURES.last
37
- if loader = Zeitwerk::Registry.loader_for(abspath)
37
+ if loader = Zeitwerk::Registry.autoloads.registered?(abspath)
38
38
  loader.__on_file_autoloaded(abspath)
39
39
  end
40
40
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk::ConstAdded # :nodoc:
4
+ #: (Symbol) -> void
5
+ def const_added(cname)
6
+ if loader = Zeitwerk::Registry.explicit_namespaces.loader_for(self, cname)
7
+ namespace = const_get(cname, false)
8
+ cref = Zeitwerk::Cref.new(self, cname)
9
+
10
+ unless namespace.is_a?(Module)
11
+ raise Zeitwerk::Error, "#{cref} is expected to be a namespace, should be a class or module (got #{namespace.class})"
12
+ end
13
+
14
+ loader.__on_namespace_loaded(cref, namespace)
15
+ end
16
+ super
17
+ end
18
+
19
+ Module.prepend(self)
20
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Description of the structure
4
+ # ----------------------------
5
+ #
6
+ # This class emulates a hash table whose keys are of type Zeitwerk::Cref.
7
+ #
8
+ # It is a synchronized 2-level hash.
9
+ #
10
+ # The keys of the top one, stored in `@map`, are class and module objects, but
11
+ # their hash code is forced to be their object IDs because class and module
12
+ # objects may not be hashable (https://github.com/fxn/zeitwerk/issues/188).
13
+ #
14
+ # Then, each one of them stores a hash table with their constants and values.
15
+ # Constants are stored as symbols.
16
+ #
17
+ # For example, if we store values 0, 1, and 2 for the crefs that would
18
+ # correspond to `M::X`, `M::Y`, and `N::Z`, the map will look like this:
19
+ #
20
+ # { M => { X: 0, :Y => 1 }, N => { Z: 2 } }
21
+ #
22
+ # This structure is internal, so only the needed interface is implemented.
23
+ #
24
+ # Alternative approaches
25
+ # -----------------------
26
+ #
27
+ # 1. We could also use a 1-level hash whose keys are constant paths. In the
28
+ # example above it would be:
29
+ #
30
+ # { "M::X" => 0, "M::Y" => 1, "N::Z" => 2 }
31
+ #
32
+ # The gem used this approach for several years.
33
+ #
34
+ # 2. Write a custom `hash`/`eql?` in Zeitwerk::Cref. Hash code would be
35
+ #
36
+ # real_mod_hash(@mod) ^ @cname.hash
37
+ #
38
+ # where `real_mod_hash(@mod)` would actually be a call to the real `hash`
39
+ # method in Module. Like what we do for module names to bypass overrides.
40
+ #
41
+ # 3. Similar to 2, but use
42
+ #
43
+ # @mod.object_id ^ @cname.object_id
44
+ #
45
+ # as hash code instead.
46
+ #
47
+ # Benchamrks
48
+ # ----------
49
+ #
50
+ # Writing:
51
+ #
52
+ # map - baseline
53
+ # (3) - 1.74x slower
54
+ # (2) - 2.91x slower
55
+ # (1) - 3.87x slower
56
+ #
57
+ # Reading:
58
+ #
59
+ # map - baseline
60
+ # (3) - 1.99x slower
61
+ # (2) - 2.80x slower
62
+ # (1) - 3.48x slower
63
+ #
64
+ # Extra ball
65
+ # ----------
66
+ #
67
+ # In addition to that, the map is synchronized and provides `delete_mod_cname`,
68
+ # which is ad-hoc for the hot path in `const_added`, we do not need to create
69
+ # unnecessary cref objects for constants we do not manage (but we do not know in
70
+ # advance there).
71
+
72
+ #: [Value]
73
+ class Zeitwerk::Cref::Map # :nodoc: all
74
+ #: () -> void
75
+ def initialize
76
+ @map = {}
77
+ @map.compare_by_identity
78
+ @mutex = Mutex.new
79
+ end
80
+
81
+ #: (Zeitwerk::Cref, Value) -> Value
82
+ def []=(cref, value)
83
+ @mutex.synchronize do
84
+ cnames = (@map[cref.mod] ||= {})
85
+ cnames[cref.cname] = value
86
+ end
87
+ end
88
+
89
+ #: (Zeitwerk::Cref) -> Value?
90
+ def [](cref)
91
+ @mutex.synchronize do
92
+ @map[cref.mod]&.[](cref.cname)
93
+ end
94
+ end
95
+
96
+ #: (Zeitwerk::Cref, { () -> Value }) -> Value
97
+ def get_or_set(cref, &block)
98
+ @mutex.synchronize do
99
+ cnames = (@map[cref.mod] ||= {})
100
+ cnames.fetch(cref.cname) { cnames[cref.cname] = block.call }
101
+ end
102
+ end
103
+
104
+ #: (Zeitwerk::Cref) -> Value?
105
+ def delete(cref)
106
+ delete_mod_cname(cref.mod, cref.cname)
107
+ end
108
+
109
+ # Ad-hoc for loader_for, called from const_added. That is a hot path, I prefer
110
+ # to not create a cref in every call, since that is global.
111
+ #
112
+ #: (Module, Symbol) -> Value?
113
+ def delete_mod_cname(mod, cname)
114
+ @mutex.synchronize do
115
+ if cnames = @map[mod]
116
+ value = cnames.delete(cname)
117
+ @map.delete(mod) if cnames.empty?
118
+ value
119
+ end
120
+ end
121
+ end
122
+
123
+ #: (Value) -> void
124
+ def delete_by_value(value)
125
+ @mutex.synchronize do
126
+ @map.delete_if do |mod, cnames|
127
+ cnames.delete_if { _2 == value }
128
+ cnames.empty?
129
+ end
130
+ end
131
+ end
132
+
133
+ # Order of yielded crefs is undefined.
134
+ #
135
+ #: () { (Zeitwerk::Cref) -> void } -> void
136
+ def each_key
137
+ @mutex.synchronize do
138
+ @map.each do |mod, cnames|
139
+ cnames.each_key do |cname|
140
+ yield Zeitwerk::Cref.new(mod, cname)
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ #: () -> void
147
+ def clear
148
+ @mutex.synchronize do
149
+ @map.clear
150
+ end
151
+ end
152
+
153
+ #: () -> bool
154
+ def empty? # for tests
155
+ @mutex.synchronize do
156
+ @map.empty?
157
+ end
158
+ end
159
+ end
data/lib/zeitwerk/cref.rb CHANGED
@@ -2,97 +2,67 @@
2
2
 
3
3
  # This private class encapsulates pairs (mod, cname).
4
4
  #
5
- # Objects represent the constant cname in the class or module object mod, and
6
- # have API to manage them that encapsulates the constants API. Examples:
5
+ # Objects represent the constant `cname` in the class or module object `mod`,
6
+ # and have API to manage them. Examples:
7
7
  #
8
8
  # cref.path
9
9
  # cref.set(value)
10
10
  # cref.get
11
11
  #
12
- # The constant may or may not exist in mod.
12
+ # The constant may or may not exist in `mod`.
13
13
  class Zeitwerk::Cref
14
+ require_relative "cref/map"
15
+
14
16
  include Zeitwerk::RealModName
15
17
 
16
- # @sig Symbol
18
+ #: Module
19
+ attr_reader :mod
20
+
21
+ #: Symbol
17
22
  attr_reader :cname
18
23
 
19
24
  # The type of the first argument is Module because Class < Module, class
20
25
  # objects are also valid.
21
26
  #
22
- # @sig (Module, Symbol) -> void
27
+ #: (Module, Symbol) -> void
23
28
  def initialize(mod, cname)
24
29
  @mod = mod
25
30
  @cname = cname
26
31
  @path = nil
27
32
  end
28
33
 
29
- if Symbol.method_defined?(:name)
30
- # Symbol#name was introduced in Ruby 3.0. It returns always the same
31
- # frozen object, so we may save a few string allocations.
32
- #
33
- # @sig () -> String
34
- def path
35
- @path ||= Object.equal?(@mod) ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}"
36
- end
37
- else
38
- # @sig () -> String
39
- def path
40
- @path ||= Object.equal?(@mod) ? @cname.to_s : "#{real_mod_name(@mod)}::#{@cname}"
41
- end
34
+ #: () -> String
35
+ def path
36
+ @path ||= Object == @mod ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}".freeze
42
37
  end
38
+ alias to_s path
43
39
 
44
- # The autoload? predicate takes into account the ancestor chain of the
45
- # receiver, like const_defined? and other methods in the constants API do.
46
- #
47
- # For example, given
48
- #
49
- # class A
50
- # autoload :X, "x.rb"
51
- # end
52
- #
53
- # class B < A
54
- # end
55
- #
56
- # B.autoload?(:X) returns "x.rb".
57
- #
58
- # We need a way to retrieve it ignoring ancestors.
59
- #
60
- # @sig () -> String?
61
- if method(:autoload?).arity == 1
62
- # @sig () -> String?
63
- def autoload?
64
- @mod.autoload?(@cname) if self.defined?
65
- end
66
- else
67
- # @sig () -> String?
68
- def autoload?
69
- @mod.autoload?(@cname, false)
70
- end
40
+ #: () -> String?
41
+ def autoload?
42
+ @mod.autoload?(@cname, false)
71
43
  end
72
44
 
73
- # @sig (String) -> bool
45
+ #: (String) -> nil
74
46
  def autoload(abspath)
75
47
  @mod.autoload(@cname, abspath)
76
48
  end
77
49
 
78
- # @sig () -> bool
50
+ #: () -> bool
79
51
  def defined?
80
52
  @mod.const_defined?(@cname, false)
81
53
  end
82
54
 
83
- # @sig (Object) -> Object
55
+ #: (top) -> top
84
56
  def set(value)
85
57
  @mod.const_set(@cname, value)
86
58
  end
87
59
 
88
- # @raise [NameError]
89
- # @sig () -> Object
60
+ #: () -> top ! NameError
90
61
  def get
91
62
  @mod.const_get(@cname, false)
92
63
  end
93
64
 
94
- # @raise [NameError]
95
- # @sig () -> void
65
+ #: () -> void ! NameError
96
66
  def remove
97
67
  @mod.__send__(:remove_const, @cname)
98
68
  end
@@ -5,6 +5,7 @@ module Zeitwerk
5
5
  end
6
6
 
7
7
  class ReloadingDisabledError < Error
8
+ #: () -> void
8
9
  def initialize
9
10
  super("can't reload, please call loader.enable_reloading before setup")
10
11
  end
@@ -14,6 +15,7 @@ module Zeitwerk
14
15
  end
15
16
 
16
17
  class SetupRequired < Error
18
+ #: () -> void
17
19
  def initialize
18
20
  super("please, finish your configuration and call Zeitwerk::Loader#setup once all is ready")
19
21
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Zeitwerk
4
4
  class GemInflector < Inflector
5
- # @sig (String) -> void
5
+ #: (String) -> void
6
6
  def initialize(root_file)
7
7
  namespace = File.basename(root_file, ".rb")
8
8
  root_dir = File.dirname(root_file)
9
9
  @version_file = File.join(root_dir, namespace, "version.rb")
10
10
  end
11
11
 
12
- # @sig (String, String) -> String
12
+ #: (String, String) -> String
13
13
  def camelize(basename, abspath)
14
14
  abspath == @version_file ? "VERSION" : super
15
15
  end
@@ -10,12 +10,12 @@ module Zeitwerk
10
10
  private_class_method :new
11
11
 
12
12
  # @private
13
- # @sig (String, bool) -> Zeitwerk::GemLoader
13
+ #: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::GemLoader
14
14
  def self.__new(root_file, namespace:, warn_on_extra_files:)
15
15
  new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
16
16
  end
17
17
 
18
- # @sig (String, bool) -> void
18
+ #: (String, namespace: Module, warn_on_extra_files: boolish) -> void
19
19
  def initialize(root_file, namespace:, warn_on_extra_files:)
20
20
  super()
21
21
 
@@ -30,7 +30,7 @@ module Zeitwerk
30
30
  push_dir(@root_dir, namespace: namespace)
31
31
  end
32
32
 
33
- # @sig () -> void
33
+ #: () -> void
34
34
  def setup
35
35
  warn_on_extra_files if @warn_on_extra_files
36
36
  super
@@ -38,7 +38,7 @@ module Zeitwerk
38
38
 
39
39
  private
40
40
 
41
- # @sig () -> void
41
+ #: () -> void
42
42
  def warn_on_extra_files
43
43
  expected_namespace_dir = @root_file.delete_suffix(".rb")
44
44
 
@@ -11,7 +11,7 @@ module Zeitwerk
11
11
  #
12
12
  # Takes into account hard-coded mappings configured with `inflect`.
13
13
  #
14
- # @sig (String, String) -> String
14
+ #: (String, String) -> String
15
15
  def camelize(basename, _abspath)
16
16
  overrides[basename] || basename.split('_').each(&:capitalize!).join
17
17
  end
@@ -28,7 +28,7 @@ module Zeitwerk
28
28
  # inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
29
29
  # inflector.camelize("users_controller", abspath) # => "UsersController"
30
30
  #
31
- # @sig (Hash[String, String]) -> void
31
+ #: (Hash[String, String]) -> void
32
32
  def inflect(inflections)
33
33
  overrides.merge!(inflections)
34
34
  end
@@ -38,7 +38,7 @@ module Zeitwerk
38
38
  # Hard-coded basename to constant name user maps that override the default
39
39
  # inflection logic.
40
40
  #
41
- # @sig () -> Hash[String, String]
41
+ #: () -> Hash[String, String]
42
42
  def overrides
43
43
  @overrides ||= {}
44
44
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # This is a private module.
4
4
  module Zeitwerk::Internal
5
+ #: (Symbol) -> void
5
6
  def internal(method_name)
6
7
  private method_name
7
8
 
@@ -1,23 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Zeitwerk::Loader::Callbacks
4
- include Zeitwerk::RealModName
3
+ module Zeitwerk::Loader::Callbacks # :nodoc: all
5
4
  extend Zeitwerk::Internal
6
5
 
7
6
  # Invoked from our decorated Kernel#require when a managed file is autoloaded.
8
7
  #
9
- # @sig (String) -> void
8
+ #: (String) -> void ! Zeitwerk::NameError
10
9
  internal def on_file_autoloaded(file)
11
10
  cref = autoloads.delete(file)
12
11
 
13
- Zeitwerk::Registry.unregister_autoload(file)
12
+ Zeitwerk::Registry.autoloads.unregister(file)
14
13
 
15
14
  if cref.defined?
16
- log("constant #{cref.path} loaded from file #{file}") if logger
17
- to_unload[cref.path] = [file, cref] if reloading_enabled?
15
+ log("constant #{cref} loaded from file #{file}") if logger
16
+ to_unload[file] = cref if reloading_enabled?
18
17
  run_on_load_callbacks(cref.path, cref.get, file) unless on_load_callbacks.empty?
19
18
  else
20
- msg = "expected file #{file} to define constant #{cref.path}, but didn't"
19
+ msg = "expected file #{file} to define constant #{cref}, but didn't"
21
20
  log(msg) if logger
22
21
 
23
22
  # Ruby still keeps the autoload defined, but we remove it because the
@@ -27,7 +26,7 @@ module Zeitwerk::Loader::Callbacks
27
26
  # Since the expected constant was not defined, there is nothing to unload.
28
27
  # However, if the exception is rescued and reloading is enabled, we still
29
28
  # need to deleted the file from $LOADED_FEATURES.
30
- to_unload[cref.path] = [file, cref] if reloading_enabled?
29
+ to_unload[file] = cref if reloading_enabled?
31
30
 
32
31
  raise Zeitwerk::NameError.new(msg, cref.cname)
33
32
  end
@@ -36,7 +35,7 @@ module Zeitwerk::Loader::Callbacks
36
35
  # Invoked from our decorated Kernel#require when a managed directory is
37
36
  # autoloaded.
38
37
  #
39
- # @sig (String) -> void
38
+ #: (String) -> void
40
39
  internal def on_dir_autoloaded(dir)
41
40
  # Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
42
41
  # we handle directories ourselves without going through Kernel#require, so
@@ -53,10 +52,9 @@ module Zeitwerk::Loader::Callbacks
53
52
  dirs_autoload_monitor.synchronize do
54
53
  if cref = autoloads.delete(dir)
55
54
  implicit_namespace = cref.set(Module.new)
56
- cpath = implicit_namespace.name
57
- log("module #{cpath} autovivified from directory #{dir}") if logger
55
+ log("module #{cref} autovivified from directory #{dir}") if logger
58
56
 
59
- to_unload[cpath] = [dir, cref] if reloading_enabled?
57
+ to_unload[dir] = cref if reloading_enabled?
60
58
 
61
59
  # We don't unregister `dir` in the registry because concurrent threads
62
60
  # wouldn't find a loader associated to it in Kernel#require and would
@@ -64,21 +62,20 @@ module Zeitwerk::Loader::Callbacks
64
62
  # these to be able to unregister later if eager loading.
65
63
  autoloaded_dirs << dir
66
64
 
67
- on_namespace_loaded(implicit_namespace)
65
+ on_namespace_loaded(cref, implicit_namespace)
68
66
 
69
- run_on_load_callbacks(cpath, implicit_namespace, dir) unless on_load_callbacks.empty?
67
+ run_on_load_callbacks(cref.path, implicit_namespace, dir) unless on_load_callbacks.empty?
70
68
  end
71
69
  end
72
70
  end
73
71
 
74
- # Invoked when a class or module is created or reopened, either from the
75
- # tracer or from module autovivification. If the namespace has matching
76
- # subdirectories, we descend into them now.
72
+ # Invoked when a namespace is created, either from const_added or from module
73
+ # autovivification. If the namespace has matching subdirectories, we descend
74
+ # into them now.
77
75
  #
78
- # @private
79
- # @sig (Module) -> void
80
- def on_namespace_loaded(namespace)
81
- if dirs = namespace_dirs.delete(real_mod_name(namespace))
76
+ #: (Zeitwerk::Cref, Module) -> void
77
+ internal def on_namespace_loaded(cref, namespace)
78
+ if dirs = namespace_dirs.delete(cref)
82
79
  dirs.each do |dir|
83
80
  define_autoloads_for_dir(dir, namespace)
84
81
  end
@@ -87,7 +84,7 @@ module Zeitwerk::Loader::Callbacks
87
84
 
88
85
  private
89
86
 
90
- # @sig (String, Object) -> void
87
+ #: (String, top, String) -> void
91
88
  def run_on_load_callbacks(cpath, value, abspath)
92
89
  # Order matters. If present, run the most specific one.
93
90
  callbacks = reloading_enabled? ? on_load_callbacks[cpath] : on_load_callbacks.delete(cpath)