zeitwerk 2.6.13 → 2.7.2

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.
@@ -30,27 +30,43 @@ module Zeitwerk::Loader::Helpers
30
30
 
31
31
  if dir?(abspath)
32
32
  next if roots.key?(abspath)
33
- next if !has_at_least_one_ruby_file?(abspath)
33
+
34
+ if !has_at_least_one_ruby_file?(abspath)
35
+ log("directory #{abspath} is ignored because it has no Ruby files") if logger
36
+ next
37
+ end
38
+
39
+ ftype = :directory
34
40
  else
35
41
  next unless ruby?(abspath)
42
+ ftype = :file
36
43
  end
37
44
 
38
45
  # We freeze abspath because that saves allocations when passed later to
39
46
  # File methods. See #125.
40
- yield basename, abspath.freeze
47
+ yield basename, abspath.freeze, ftype
41
48
  end
42
49
  end
43
50
 
51
+ # Looks for a Ruby file using breadth-first search. This type of search is
52
+ # important to list as less directories as possible and return fast in the
53
+ # common case in which there are Ruby files.
54
+ #
44
55
  # @sig (String) -> bool
45
56
  private def has_at_least_one_ruby_file?(dir)
46
57
  to_visit = [dir]
47
58
 
48
- while dir = to_visit.shift
49
- ls(dir) do |_basename, abspath|
59
+ while (dir = to_visit.shift)
60
+ Dir.each_child(dir) do |basename|
61
+ next if hidden?(basename)
62
+
63
+ abspath = File.join(dir, basename)
64
+ next if ignored_path?(abspath)
65
+
50
66
  if dir?(abspath)
51
- to_visit << abspath
67
+ to_visit << abspath unless roots.key?(abspath)
52
68
  else
53
- return true
69
+ return true if ruby?(abspath)
54
70
  end
55
71
  end
56
72
  end
@@ -82,64 +98,7 @@ module Zeitwerk::Loader::Helpers
82
98
  end
83
99
  end
84
100
 
85
- # --- Constants ---------------------------------------------------------------------------------
86
-
87
- # The autoload? predicate takes into account the ancestor chain of the
88
- # receiver, like const_defined? and other methods in the constants API do.
89
- #
90
- # For example, given
91
- #
92
- # class A
93
- # autoload :X, "x.rb"
94
- # end
95
- #
96
- # class B < A
97
- # end
98
- #
99
- # B.autoload?(:X) returns "x.rb".
100
- #
101
- # We need a way to strictly check in parent ignoring ancestors.
102
- #
103
- # @sig (Module, Symbol) -> String?
104
- if method(:autoload?).arity == 1
105
- private def strict_autoload_path(parent, cname)
106
- parent.autoload?(cname) if cdef?(parent, cname)
107
- end
108
- else
109
- private def strict_autoload_path(parent, cname)
110
- parent.autoload?(cname, false)
111
- end
112
- end
113
-
114
- # @sig (Module, Symbol) -> String
115
- if Symbol.method_defined?(:name)
116
- # Symbol#name was introduced in Ruby 3.0. It returns always the same
117
- # frozen object, so we may save a few string allocations.
118
- private def cpath(parent, cname)
119
- Object == parent ? cname.name : "#{real_mod_name(parent)}::#{cname.name}"
120
- end
121
- else
122
- private def cpath(parent, cname)
123
- Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
124
- end
125
- end
126
-
127
- # @sig (Module, Symbol) -> bool
128
- private def cdef?(parent, cname)
129
- parent.const_defined?(cname, false)
130
- end
131
-
132
- # @raise [NameError]
133
- # @sig (Module, Symbol) -> Object
134
- private def cget(parent, cname)
135
- parent.const_get(cname, false)
136
- end
137
-
138
- # @raise [NameError]
139
- # @sig (Module, Symbol) -> Object
140
- private def crem(parent, cname)
141
- parent.__send__(:remove_const, cname)
142
- end
101
+ # --- Inflection --------------------------------------------------------------------------------
143
102
 
144
103
  CNAME_VALIDATOR = Module.new
145
104
  private_constant :CNAME_VALIDATOR
@@ -22,17 +22,40 @@ module Zeitwerk
22
22
  private_constant :MUTEX
23
23
 
24
24
  # Maps absolute paths for which an autoload has been set ---and not
25
- # executed--- to their corresponding parent class or module and constant
26
- # name.
25
+ # executed--- to their corresponding Zeitwerk::Cref object.
27
26
  #
28
- # "/Users/fxn/blog/app/models/user.rb" => [Object, :User],
29
- # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
27
+ # "/Users/fxn/blog/app/models/user.rb" => #<Zeitwerk::Cref:... @mod=Object, @cname=:User, ...>,
28
+ # "/Users/fxn/blog/app/models/hotel/pricing.rb" => #<Zeitwerk::Cref:... @mod=Hotel, @cname=:Pricing, ...>,
30
29
  # ...
31
30
  #
32
- # @sig Hash[String, [Module, Symbol]]
31
+ # @sig Hash[String, Zeitwerk::Cref]
33
32
  attr_reader :autoloads
34
33
  internal :autoloads
35
34
 
35
+ # When the path passed to Module#autoload is in the stack of features being
36
+ # loaded at the moment, Ruby passes. For example, Module#autoload? returns
37
+ # `nil` even if the autoload has not been attempted. See
38
+ #
39
+ # https://bugs.ruby-lang.org/issues/21035
40
+ #
41
+ # We call these "inceptions".
42
+ #
43
+ # A common case is the entry point of gems managed by Zeitwerk. Their main
44
+ # file is normally required and, while doing so, the loader sets an autoload
45
+ # on the gem namespace. That autoload hits this edge case.
46
+ #
47
+ # There is some logic that neeeds to know if an autoload for a given
48
+ # constant already exists. We check Module#autoload? first, and fallback to
49
+ # the inceptions just in case.
50
+ #
51
+ # This map keeps track of pairs (cref, autoload_path) found by the loader.
52
+ # The module Zeitwerk::Registry::Inceptions, on the other hand, acts as a
53
+ # global registry for them.
54
+ #
55
+ # @sig Zeitwerk::Cref::Map[String]
56
+ attr_reader :inceptions
57
+ internal :inceptions
58
+
36
59
  # We keep track of autoloaded directories to remove them from the registry
37
60
  # at the end of eager loading.
38
61
  #
@@ -43,46 +66,31 @@ module Zeitwerk
43
66
  attr_reader :autoloaded_dirs
44
67
  internal :autoloaded_dirs
45
68
 
46
- # Stores metadata needed for unloading. Its entries look like this:
47
- #
48
- # "Admin::Role" => [".../admin/role.rb", [Admin, :Role]]
49
- #
50
- # The cpath as key helps implementing unloadable_cpath? The file name is
51
- # stored in order to be able to delete it from $LOADED_FEATURES, and the
52
- # pair [Module, Symbol] is used to remove_const the constant from the class
53
- # or module object.
69
+ # If reloading is enabled, this collection maps autoload paths to their
70
+ # autoloaded crefs.
54
71
  #
55
- # If reloading is enabled, this hash is filled as constants are autoloaded
56
- # or eager loaded. Otherwise, the collection remains empty.
72
+ # On unload, the autoload paths are passed to callbacks, files deleted from
73
+ # $LOADED_FEATURES, and the crefs are deleted.
57
74
  #
58
- # @sig Hash[String, [String, [Module, Symbol]]]
75
+ # @sig Hash[String, Zeitwerk::Cref]
59
76
  attr_reader :to_unload
60
77
  internal :to_unload
61
78
 
62
- # Maps namespace constant paths to their respective directories.
79
+ # Maps namespace crefs to the directories that conform the namespace.
63
80
  #
64
- # For example, given this mapping:
81
+ # When these crefs get defined we know their children are spread over those
82
+ # directories. We'll visit them to set up the corresponding autoloads.
65
83
  #
66
- # "Admin" => [
67
- # "/Users/fxn/blog/app/controllers/admin",
68
- # "/Users/fxn/blog/app/models/admin",
69
- # ...
70
- # ]
71
- #
72
- # when `Admin` gets defined we know that it plays the role of a namespace
73
- # and that its children are spread over those directories. We'll visit them
74
- # to set up the corresponding autoloads.
75
- #
76
- # @sig Hash[String, Array[String]]
84
+ # @sig Zeitwerk::Cref::Map[String]
77
85
  attr_reader :namespace_dirs
78
86
  internal :namespace_dirs
79
87
 
80
88
  # A shadowed file is a file managed by this loader that is ignored when
81
89
  # setting autoloads because its matching constant is already taken.
82
90
  #
83
- # This private set is populated as we descend. For example, if the loader
84
- # has only scanned the top-level, `shadowed_files` does not have shadowed
85
- # files that may exist deep in the project tree yet.
91
+ # This private set is populated lazily, as we descend. For example, if the
92
+ # loader has only scanned the top-level, `shadowed_files` does not have the
93
+ # shadowed files that may exist deep in the project tree.
86
94
  #
87
95
  # @sig Set[String]
88
96
  attr_reader :shadowed_files
@@ -100,9 +108,10 @@ module Zeitwerk
100
108
  super
101
109
 
102
110
  @autoloads = {}
111
+ @inceptions = Zeitwerk::Cref::Map.new
103
112
  @autoloaded_dirs = []
104
113
  @to_unload = {}
105
- @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
114
+ @namespace_dirs = Zeitwerk::Cref::Map.new
106
115
  @shadowed_files = Set.new
107
116
  @setup = false
108
117
  @eager_loaded = false
@@ -154,32 +163,32 @@ module Zeitwerk
154
163
  # is enough.
155
164
  unloaded_files = Set.new
156
165
 
157
- autoloads.each do |abspath, (parent, cname)|
158
- if parent.autoload?(cname)
159
- unload_autoload(parent, cname)
166
+ autoloads.each do |abspath, cref|
167
+ if cref.autoload?
168
+ unload_autoload(cref)
160
169
  else
161
170
  # Could happen if loaded with require_relative. That is unsupported,
162
171
  # and the constant path would escape unloadable_cpath? This is just
163
172
  # defensive code to clean things up as much as we are able to.
164
- unload_cref(parent, cname)
173
+ unload_cref(cref)
165
174
  unloaded_files.add(abspath) if ruby?(abspath)
166
175
  end
167
176
  end
168
177
 
169
- to_unload.each do |cpath, (abspath, (parent, cname))|
178
+ to_unload.each do |abspath, cref|
170
179
  unless on_unload_callbacks.empty?
171
180
  begin
172
- value = cget(parent, cname)
181
+ value = cref.get
173
182
  rescue ::NameError
174
183
  # Perhaps the user deleted the constant by hand, or perhaps an
175
184
  # autoload failed to define the expected constant but the user
176
185
  # rescued the exception.
177
186
  else
178
- run_on_unload_callbacks(cpath, value, abspath)
187
+ run_on_unload_callbacks(cref, value, abspath)
179
188
  end
180
189
  end
181
190
 
182
- unload_cref(parent, cname)
191
+ unload_cref(cref)
183
192
  unloaded_files.add(abspath) if ruby?(abspath)
184
193
  end
185
194
 
@@ -204,8 +213,10 @@ module Zeitwerk
204
213
  namespace_dirs.clear
205
214
  shadowed_files.clear
206
215
 
216
+ unregister_inceptions
217
+ unregister_explicit_namespaces
218
+
207
219
  Registry.on_unload(self)
208
- ExplicitNamespace.__unregister_loader(self)
209
220
 
210
221
  @setup = false
211
222
  @eager_loaded = false
@@ -230,68 +241,107 @@ module Zeitwerk
230
241
  setup
231
242
  end
232
243
 
233
- # @sig (String | Pathname) -> String?
234
- def cpath_expected_at(path)
235
- abspath = File.expand_path(path)
244
+ # Returns a hash that maps the absolute paths of the managed files and
245
+ # directories to their respective expected constant paths.
246
+ #
247
+ # @sig () -> Hash[String, String]
248
+ def all_expected_cpaths
249
+ result = {}
236
250
 
237
- raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
251
+ actual_roots.each do |root_dir, root_namespace|
252
+ queue = [[root_dir, real_mod_name(root_namespace)]]
238
253
 
239
- return unless dir?(abspath) || ruby?(abspath)
240
- return if ignored_path?(abspath)
254
+ while (dir, cpath = queue.shift)
255
+ result[dir] = cpath
241
256
 
242
- paths = []
257
+ prefix = cpath == "Object" ? "" : cpath + "::"
243
258
 
244
- if ruby?(abspath)
245
- basename = File.basename(abspath, ".rb")
246
- return if hidden?(basename)
259
+ ls(dir) do |basename, abspath, ftype|
260
+ if ftype == :file
261
+ basename.delete_suffix!(".rb")
262
+ result[abspath] = prefix + inflector.camelize(basename, abspath)
263
+ else
264
+ if collapse?(abspath)
265
+ queue << [abspath, cpath]
266
+ else
267
+ queue << [abspath, prefix + inflector.camelize(basename, abspath)]
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
247
273
 
248
- paths << [basename, abspath]
249
- walk_up_from = File.dirname(abspath)
250
- else
251
- walk_up_from = abspath
274
+ result
252
275
  end
253
276
 
254
- root_namespace = nil
277
+ # @sig (String | Pathname) -> String?
278
+ def cpath_expected_at(path)
279
+ abspath = File.expand_path(path)
255
280
 
256
- walk_up(walk_up_from) do |dir|
257
- break if root_namespace = roots[dir]
258
- return if ignored_path?(dir)
281
+ raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
259
282
 
260
- basename = File.basename(dir)
261
- return if hidden?(basename)
283
+ return unless dir?(abspath) || ruby?(abspath)
284
+ return if ignored_path?(abspath)
262
285
 
263
- paths << [basename, abspath] unless collapse?(dir)
264
- end
286
+ paths = []
265
287
 
266
- return unless root_namespace
288
+ if ruby?(abspath)
289
+ basename = File.basename(abspath, ".rb")
290
+ return if hidden?(basename)
267
291
 
268
- if paths.empty?
269
- real_mod_name(root_namespace)
270
- else
271
- cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
292
+ paths << [basename, abspath]
293
+ walk_up_from = File.dirname(abspath)
294
+ else
295
+ walk_up_from = abspath
296
+ end
272
297
 
273
- if root_namespace == Object
274
- cnames.join("::")
298
+ root_namespace = nil
299
+
300
+ walk_up(walk_up_from) do |dir|
301
+ break if root_namespace = roots[dir]
302
+ return if ignored_path?(dir)
303
+
304
+ basename = File.basename(dir)
305
+ return if hidden?(basename)
306
+
307
+ paths << [basename, abspath] unless collapse?(dir)
308
+ end
309
+
310
+ return unless root_namespace
311
+
312
+ if paths.empty?
313
+ real_mod_name(root_namespace)
275
314
  else
276
- "#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
315
+ cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
316
+
317
+ if root_namespace == Object
318
+ cnames.join("::")
319
+ else
320
+ "#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
321
+ end
277
322
  end
278
323
  end
279
- end
280
324
 
281
325
  # Says if the given constant path would be unloaded on reload. This
282
326
  # predicate returns `false` if reloading is disabled.
283
327
  #
328
+ # This is an undocumented method that I wrote to help transition from the
329
+ # classic autoloader in Rails. Its usage was removed from Rails in 7.0.
330
+ #
284
331
  # @sig (String) -> bool
285
332
  def unloadable_cpath?(cpath)
286
- to_unload.key?(cpath)
333
+ unloadable_cpaths.include?(cpath)
287
334
  end
288
335
 
289
336
  # Returns an array with the constant paths that would be unloaded on reload.
290
337
  # This predicate returns an empty array if reloading is disabled.
291
338
  #
339
+ # This is an undocumented method that I wrote to help transition from the
340
+ # classic autoloader in Rails. Its usage was removed from Rails in 7.0.
341
+ #
292
342
  # @sig () -> Array[String]
293
343
  def unloadable_cpaths
294
- to_unload.keys.freeze
344
+ to_unload.values.map(&:path)
295
345
  end
296
346
 
297
347
  # This is a dangerous method.
@@ -299,8 +349,9 @@ module Zeitwerk
299
349
  # @experimental
300
350
  # @sig () -> void
301
351
  def unregister
352
+ unregister_inceptions
353
+ unregister_explicit_namespaces
302
354
  Registry.unregister_loader(self)
303
- ExplicitNamespace.__unregister_loader(self)
304
355
  end
305
356
 
306
357
  # The return value of this predicate is only meaningful if the loader has
@@ -408,24 +459,25 @@ module Zeitwerk
408
459
 
409
460
  # @sig (String, Module) -> void
410
461
  private def define_autoloads_for_dir(dir, parent)
411
- ls(dir) do |basename, abspath|
412
- if ruby?(basename)
462
+ ls(dir) do |basename, abspath, ftype|
463
+ if ftype == :file
413
464
  basename.delete_suffix!(".rb")
414
- autoload_file(parent, cname_for(basename, abspath), abspath)
465
+ cref = Cref.new(parent, cname_for(basename, abspath))
466
+ autoload_file(cref, abspath)
415
467
  else
416
468
  if collapse?(abspath)
417
469
  define_autoloads_for_dir(abspath, parent)
418
470
  else
419
- autoload_subdir(parent, cname_for(basename, abspath), abspath)
471
+ cref = Cref.new(parent, cname_for(basename, abspath))
472
+ autoload_subdir(cref, abspath)
420
473
  end
421
474
  end
422
475
  end
423
476
  end
424
477
 
425
478
  # @sig (Module, Symbol, String) -> void
426
- private def autoload_subdir(parent, cname, subdir)
427
- if autoload_path = autoload_path_set_by_me_for?(parent, cname)
428
- cpath = cpath(parent, cname)
479
+ private def autoload_subdir(cref, subdir)
480
+ if autoload_path = autoload_path_set_by_me_for?(cref)
429
481
  if ruby?(autoload_path)
430
482
  # Scanning visited a Ruby file first, and now a directory for the same
431
483
  # constant has been found. This means we are dealing with an explicit
@@ -434,94 +486,107 @@ module Zeitwerk
434
486
  # Registering is idempotent, and we have to keep the autoload pointing
435
487
  # to the file. This may run again if more directories are found later
436
488
  # on, no big deal.
437
- register_explicit_namespace(cpath)
489
+ register_explicit_namespace(cref)
438
490
  end
439
491
  # If the existing autoload points to a file, it has to be preserved, if
440
492
  # not, it is fine as it is. In either case, we do not need to override.
441
493
  # Just remember the subdirectory conforms this namespace.
442
- namespace_dirs[cpath] << subdir
443
- elsif !cdef?(parent, cname)
494
+ namespace_dirs.get_or_set(cref) { [] } << subdir
495
+ elsif !cref.defined?
444
496
  # First time we find this namespace, set an autoload for it.
445
- namespace_dirs[cpath(parent, cname)] << subdir
446
- define_autoload(parent, cname, subdir)
497
+ namespace_dirs.get_or_set(cref) { [] } << subdir
498
+ define_autoload(cref, subdir)
447
499
  else
448
500
  # For whatever reason the constant that corresponds to this namespace has
449
501
  # already been defined, we have to recurse.
450
- log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
451
- define_autoloads_for_dir(subdir, cget(parent, cname))
502
+ log("the namespace #{cref} already exists, descending into #{subdir}") if logger
503
+ define_autoloads_for_dir(subdir, cref.get)
452
504
  end
453
505
  end
454
506
 
455
507
  # @sig (Module, Symbol, String) -> void
456
- private def autoload_file(parent, cname, file)
457
- if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
508
+ private def autoload_file(cref, file)
509
+ if autoload_path = cref.autoload? || Registry::Inceptions.registered?(cref)
458
510
  # First autoload for a Ruby file wins, just ignore subsequent ones.
459
511
  if ruby?(autoload_path)
460
512
  shadowed_files << file
461
513
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
462
514
  else
463
- promote_namespace_from_implicit_to_explicit(
464
- dir: autoload_path,
465
- file: file,
466
- parent: parent,
467
- cname: cname
468
- )
515
+ promote_namespace_from_implicit_to_explicit(dir: autoload_path, file: file, cref: cref)
469
516
  end
470
- elsif cdef?(parent, cname)
517
+ elsif cref.defined?
471
518
  shadowed_files << file
472
- log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
519
+ log("file #{file} is ignored because #{cref} is already defined") if logger
473
520
  else
474
- define_autoload(parent, cname, file)
521
+ define_autoload(cref, file)
475
522
  end
476
523
  end
477
524
 
478
525
  # `dir` is the directory that would have autovivified a namespace. `file` is
479
526
  # the file where we've found the namespace is explicitly defined.
480
527
  #
481
- # @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
482
- private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
528
+ # @sig (dir: String, file: String, cref: Zeitwerk::Cref) -> void
529
+ private def promote_namespace_from_implicit_to_explicit(dir:, file:, cref:)
483
530
  autoloads.delete(dir)
484
531
  Registry.unregister_autoload(dir)
485
532
 
486
- log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
533
+ log("earlier autoload for #{cref} discarded, it is actually an explicit namespace defined in #{file}") if logger
487
534
 
488
- define_autoload(parent, cname, file)
489
- register_explicit_namespace(cpath(parent, cname))
535
+ # Order matters: When Module#const_added is triggered by the autoload, we
536
+ # don't want the namespace to be registered yet.
537
+ define_autoload(cref, file)
538
+ register_explicit_namespace(cref)
490
539
  end
491
540
 
492
541
  # @sig (Module, Symbol, String) -> void
493
- private def define_autoload(parent, cname, abspath)
494
- parent.autoload(cname, abspath)
542
+ private def define_autoload(cref, abspath)
543
+ cref.autoload(abspath)
495
544
 
496
545
  if logger
497
546
  if ruby?(abspath)
498
- log("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")
547
+ log("autoload set for #{cref}, to be loaded from #{abspath}")
499
548
  else
500
- log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
549
+ log("autoload set for #{cref}, to be autovivified from #{abspath}")
501
550
  end
502
551
  end
503
552
 
504
- autoloads[abspath] = [parent, cname]
553
+ autoloads[abspath] = cref
505
554
  Registry.register_autoload(self, abspath)
506
555
 
507
- # See why in the documentation of Zeitwerk::Registry.inceptions.
508
- unless parent.autoload?(cname)
509
- Registry.register_inception(cpath(parent, cname), abspath, self)
510
- end
556
+ register_inception(cref, abspath) unless cref.autoload?
511
557
  end
512
558
 
513
559
  # @sig (Module, Symbol) -> String?
514
- private def autoload_path_set_by_me_for?(parent, cname)
515
- if autoload_path = strict_autoload_path(parent, cname)
560
+ private def autoload_path_set_by_me_for?(cref)
561
+ if autoload_path = cref.autoload?
516
562
  autoload_path if autoloads.key?(autoload_path)
517
563
  else
518
- Registry.inception?(cpath(parent, cname))
564
+ inceptions[cref]
519
565
  end
520
566
  end
521
567
 
522
- # @sig (String) -> void
523
- private def register_explicit_namespace(cpath)
524
- ExplicitNamespace.__register(cpath, self)
568
+ # @sig (Zeitwerk::Cref) -> void
569
+ private def register_explicit_namespace(cref)
570
+ Registry::ExplicitNamespaces.__register(cref, self)
571
+ end
572
+
573
+ # @sig () -> void
574
+ private def unregister_explicit_namespaces
575
+ Registry::ExplicitNamespaces.__unregister_loader(self)
576
+ end
577
+
578
+ # @sig (Zeitwerk::Cref, String) -> void
579
+ private def register_inception(cref, abspath)
580
+ inceptions[cref] = abspath
581
+ Registry::Inceptions.register(cref, abspath)
582
+ end
583
+
584
+ # @sig () -> void
585
+ private def unregister_inceptions
586
+ inceptions.each_key do |cref|
587
+ Registry::Inceptions.unregister(cref)
588
+ end
589
+ inceptions.clear
525
590
  end
526
591
 
527
592
  # @sig (String) -> void
@@ -548,29 +613,29 @@ module Zeitwerk
548
613
  end
549
614
  end
550
615
 
551
- # @sig (String, Object, String) -> void
552
- private def run_on_unload_callbacks(cpath, value, abspath)
616
+ # @sig (String, top, String) -> void
617
+ private def run_on_unload_callbacks(cref, value, abspath)
553
618
  # Order matters. If present, run the most specific one.
554
- on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
555
- on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
619
+ on_unload_callbacks[cref.path]&.each { |c| c.call(value, abspath) }
620
+ on_unload_callbacks[:ANY]&.each { |c| c.call(cref.path, value, abspath) }
556
621
  end
557
622
 
558
623
  # @sig (Module, Symbol) -> void
559
- private def unload_autoload(parent, cname)
560
- crem(parent, cname)
561
- log("autoload for #{cpath(parent, cname)} removed") if logger
624
+ private def unload_autoload(cref)
625
+ cref.remove
626
+ log("autoload for #{cref} removed") if logger
562
627
  end
563
628
 
564
629
  # @sig (Module, Symbol) -> void
565
- private def unload_cref(parent, cname)
630
+ private def unload_cref(cref)
566
631
  # Let's optimistically remove_const. The way we use it, this is going to
567
632
  # succeed always if all is good.
568
- crem(parent, cname)
633
+ cref.remove
569
634
  rescue ::NameError
570
635
  # There are a few edge scenarios in which this may happen. If the constant
571
636
  # is gone, that is OK, anyway.
572
637
  else
573
- log("#{cpath(parent, cname)} unloaded") if logger
638
+ log("#{cref} unloaded") if logger
574
639
  end
575
640
  end
576
641
  end
@@ -1,4 +1,5 @@
1
1
  class Zeitwerk::NullInflector
2
+ # @sig (String, String) -> String
2
3
  def camelize(basename, _abspath)
3
4
  basename
4
5
  end
@@ -10,13 +10,7 @@ module Zeitwerk::RealModName
10
10
  # The name method can be overridden, hence the indirection in this method.
11
11
  #
12
12
  # @sig (Module) -> String?
13
- if UnboundMethod.method_defined?(:bind_call)
14
- def real_mod_name(mod)
15
- UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
16
- end
17
- else
18
- def real_mod_name(mod)
19
- UNBOUND_METHOD_MODULE_NAME.bind(mod).call
20
- end
13
+ def real_mod_name(mod)
14
+ UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
21
15
  end
22
16
  end