zeitwerk 2.6.8 → 2.6.18

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.
@@ -45,8 +45,10 @@ module Zeitwerk::Loader::EagerLoad
45
45
 
46
46
  break if root_namespace = roots[dir]
47
47
 
48
+ basename = File.basename(dir)
49
+ return if hidden?(basename)
50
+
48
51
  unless collapse?(dir)
49
- basename = File.basename(dir)
50
52
  cnames << inflector.camelize(basename, dir).to_sym
51
53
  end
52
54
  end
@@ -59,8 +61,8 @@ module Zeitwerk::Loader::EagerLoad
59
61
  cnames.reverse_each do |cname|
60
62
  # Can happen if there are no Ruby files. This is not an error condition,
61
63
  # the directory is actually managed. Could have Ruby files later.
62
- return unless cdef?(namespace, cname)
63
- namespace = cget(namespace, cname)
64
+ return unless namespace.const_defined?(cname, false)
65
+ namespace = namespace.const_get(cname, false)
64
66
  end
65
67
 
66
68
  # A shortcircuiting test depends on the invocation of this method. Please
@@ -119,6 +121,8 @@ module Zeitwerk::Loader::EagerLoad
119
121
  raise Zeitwerk::Error.new("#{abspath} is ignored") if ignored_path?(abspath)
120
122
 
121
123
  basename = File.basename(abspath, ".rb")
124
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
125
+
122
126
  base_cname = inflector.camelize(basename, abspath).to_sym
123
127
 
124
128
  root_namespace = nil
@@ -129,8 +133,10 @@ module Zeitwerk::Loader::EagerLoad
129
133
 
130
134
  break if root_namespace = roots[dir]
131
135
 
136
+ basename = File.basename(dir)
137
+ raise Zeitwerk::Error.new("#{abspath} is ignored") if hidden?(basename)
138
+
132
139
  unless collapse?(dir)
133
- basename = File.basename(dir)
134
140
  cnames << inflector.camelize(basename, dir).to_sym
135
141
  end
136
142
  end
@@ -139,12 +145,12 @@ module Zeitwerk::Loader::EagerLoad
139
145
 
140
146
  namespace = root_namespace
141
147
  cnames.reverse_each do |cname|
142
- namespace = cget(namespace, cname)
148
+ namespace = namespace.const_get(cname, false)
143
149
  end
144
150
 
145
151
  raise Zeitwerk::Error.new("#{abspath} is shadowed") if shadowed_file?(abspath)
146
152
 
147
- cget(namespace, base_cname)
153
+ namespace.const_get(base_cname, false)
148
154
  end
149
155
 
150
156
  # The caller is responsible for making sure `namespace` is the namespace that
@@ -158,22 +164,20 @@ module Zeitwerk::Loader::EagerLoad
158
164
  log("eager load directory #{dir} start") if logger
159
165
 
160
166
  queue = [[dir, namespace]]
161
- while to_eager_load = queue.shift
162
- dir, namespace = to_eager_load
163
-
164
- ls(dir) do |basename, abspath|
167
+ while (current_dir, namespace = queue.shift)
168
+ ls(current_dir) do |basename, abspath, ftype|
165
169
  next if honour_exclusions && eager_load_exclusions.member?(abspath)
166
170
 
167
- if ruby?(abspath)
168
- if (cref = autoloads[abspath]) && !shadowed_file?(abspath)
169
- cget(*cref)
171
+ if ftype == :file
172
+ if (cref = autoloads[abspath])
173
+ cref.get
170
174
  end
171
175
  else
172
176
  if collapse?(abspath)
173
177
  queue << [abspath, namespace]
174
178
  else
175
179
  cname = inflector.camelize(basename, abspath).to_sym
176
- queue << [abspath, cget(namespace, cname)]
180
+ queue << [abspath, namespace.const_get(cname, false)]
177
181
  end
178
182
  end
179
183
  end
@@ -203,9 +207,9 @@ module Zeitwerk::Loader::EagerLoad
203
207
  next_dirs = []
204
208
 
205
209
  suffix.split("::").each do |segment|
206
- while dir = dirs.shift
207
- ls(dir) do |basename, abspath|
208
- next unless dir?(abspath)
210
+ while (dir = dirs.shift)
211
+ ls(dir) do |basename, abspath, ftype|
212
+ next unless ftype == :directory
209
213
 
210
214
  if collapse?(abspath)
211
215
  dirs << abspath
@@ -30,27 +30,45 @@ 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
+ children = Dir.children(dir)
61
+
62
+ children.each do |basename|
63
+ next if hidden?(basename)
64
+
65
+ abspath = File.join(dir, basename)
66
+ next if ignored_path?(abspath)
67
+
50
68
  if dir?(abspath)
51
- to_visit << abspath
69
+ to_visit << abspath unless roots.key?(abspath)
52
70
  else
53
- return true
71
+ return true if ruby?(abspath)
54
72
  end
55
73
  end
56
74
  end
@@ -82,62 +100,49 @@ module Zeitwerk::Loader::Helpers
82
100
  end
83
101
  end
84
102
 
85
- # --- Constants ---------------------------------------------------------------------------------
103
+ # --- Inflection --------------------------------------------------------------------------------
86
104
 
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
105
+ CNAME_VALIDATOR = Module.new
106
+ private_constant :CNAME_VALIDATOR
113
107
 
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}"
108
+ # @raise [Zeitwerk::NameError]
109
+ # @sig (String, String) -> Symbol
110
+ private def cname_for(basename, abspath)
111
+ cname = inflector.camelize(basename, abspath)
112
+
113
+ unless cname.is_a?(String)
114
+ raise TypeError, "#{inflector.class}#camelize must return a String, received #{cname.inspect}"
120
115
  end
121
- else
122
- private def cpath(parent, cname)
123
- Object == parent ? cname.to_s : "#{real_mod_name(parent)}::#{cname}"
116
+
117
+ if cname.include?("::")
118
+ raise Zeitwerk::NameError.new(<<~MESSAGE, cname)
119
+ wrong constant name #{cname} inferred by #{inflector.class} from
120
+
121
+ #{abspath}
122
+
123
+ #{inflector.class}#camelize should return a simple constant name without "::"
124
+ MESSAGE
124
125
  end
125
- end
126
126
 
127
- # @sig (Module, Symbol) -> bool
128
- private def cdef?(parent, cname)
129
- parent.const_defined?(cname, false)
130
- end
127
+ begin
128
+ CNAME_VALIDATOR.const_defined?(cname, false)
129
+ rescue ::NameError => error
130
+ path_type = ruby?(abspath) ? "file" : "directory"
131
131
 
132
- # @raise [NameError]
133
- # @sig (Module, Symbol) -> Object
134
- private def cget(parent, cname)
135
- parent.const_get(cname, false)
136
- end
132
+ raise Zeitwerk::NameError.new(<<~MESSAGE, error.name)
133
+ #{error.message} inferred by #{inflector.class} from #{path_type}
134
+
135
+ #{abspath}
136
+
137
+ Possible ways to address this:
138
+
139
+ * Tell Zeitwerk to ignore this particular #{path_type}.
140
+ * Tell Zeitwerk to ignore one of its parent directories.
141
+ * Rename the #{path_type} to comply with the naming conventions.
142
+ * Modify the inflector to handle this case.
143
+ MESSAGE
144
+ end
137
145
 
138
- # @raise [NameError]
139
- # @sig (Module, Symbol) -> Object
140
- private def crem(parent, cname)
141
- parent.__send__(:remove_const, cname)
146
+ cname.to_sym
142
147
  end
143
148
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "monitor"
3
4
  require "set"
4
5
 
5
6
  module Zeitwerk
@@ -21,14 +22,13 @@ module Zeitwerk
21
22
  private_constant :MUTEX
22
23
 
23
24
  # Maps absolute paths for which an autoload has been set ---and not
24
- # executed--- to their corresponding parent class or module and constant
25
- # name.
25
+ # executed--- to their corresponding Zeitwerk::Cref object.
26
26
  #
27
- # "/Users/fxn/blog/app/models/user.rb" => [Object, :User],
28
- # "/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, ...>,
29
29
  # ...
30
30
  #
31
- # @sig Hash[String, [Module, Symbol]]
31
+ # @sig Hash[String, Zeitwerk::Cref]
32
32
  attr_reader :autoloads
33
33
  internal :autoloads
34
34
 
@@ -44,17 +44,19 @@ module Zeitwerk
44
44
 
45
45
  # Stores metadata needed for unloading. Its entries look like this:
46
46
  #
47
- # "Admin::Role" => [".../admin/role.rb", [Admin, :Role]]
47
+ # "Admin::Role" => [
48
+ # ".../admin/role.rb",
49
+ # #<Zeitwerk::Cref:... @mod=Admin, @cname=:Role, ...>
50
+ # ]
48
51
  #
49
52
  # The cpath as key helps implementing unloadable_cpath? The file name is
50
53
  # stored in order to be able to delete it from $LOADED_FEATURES, and the
51
- # pair [Module, Symbol] is used to remove_const the constant from the class
52
- # or module object.
54
+ # cref is used to remove the constant from the parent class or module.
53
55
  #
54
56
  # If reloading is enabled, this hash is filled as constants are autoloaded
55
57
  # or eager loaded. Otherwise, the collection remains empty.
56
58
  #
57
- # @sig Hash[String, [String, [Module, Symbol]]]
59
+ # @sig Hash[String, [String, Zeitwerk::Cref]]
58
60
  attr_reader :to_unload
59
61
  internal :to_unload
60
62
 
@@ -91,9 +93,9 @@ module Zeitwerk
91
93
  attr_reader :mutex
92
94
  private :mutex
93
95
 
94
- # @sig Mutex
95
- attr_reader :mutex2
96
- private :mutex2
96
+ # @sig Monitor
97
+ attr_reader :dirs_autoload_monitor
98
+ private :dirs_autoload_monitor
97
99
 
98
100
  def initialize
99
101
  super
@@ -103,11 +105,12 @@ module Zeitwerk
103
105
  @to_unload = {}
104
106
  @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
105
107
  @shadowed_files = Set.new
106
- @mutex = Mutex.new
107
- @mutex2 = Mutex.new
108
108
  @setup = false
109
109
  @eager_loaded = false
110
110
 
111
+ @mutex = Mutex.new
112
+ @dirs_autoload_monitor = Monitor.new
113
+
111
114
  Registry.register_loader(self)
112
115
  end
113
116
 
@@ -119,7 +122,7 @@ module Zeitwerk
119
122
  break if @setup
120
123
 
121
124
  actual_roots.each do |root_dir, root_namespace|
122
- set_autoloads_in_dir(root_dir, root_namespace)
125
+ define_autoloads_for_dir(root_dir, root_namespace)
123
126
  end
124
127
 
125
128
  on_setup_callbacks.each(&:call)
@@ -152,22 +155,22 @@ module Zeitwerk
152
155
  # is enough.
153
156
  unloaded_files = Set.new
154
157
 
155
- autoloads.each do |abspath, (parent, cname)|
156
- if parent.autoload?(cname)
157
- unload_autoload(parent, cname)
158
+ autoloads.each do |abspath, cref|
159
+ if cref.autoload?
160
+ unload_autoload(cref)
158
161
  else
159
162
  # Could happen if loaded with require_relative. That is unsupported,
160
163
  # and the constant path would escape unloadable_cpath? This is just
161
164
  # defensive code to clean things up as much as we are able to.
162
- unload_cref(parent, cname)
165
+ unload_cref(cref)
163
166
  unloaded_files.add(abspath) if ruby?(abspath)
164
167
  end
165
168
  end
166
169
 
167
- to_unload.each do |cpath, (abspath, (parent, cname))|
170
+ to_unload.each do |cpath, (abspath, cref)|
168
171
  unless on_unload_callbacks.empty?
169
172
  begin
170
- value = cget(parent, cname)
173
+ value = cref.get
171
174
  rescue ::NameError
172
175
  # Perhaps the user deleted the constant by hand, or perhaps an
173
176
  # autoload failed to define the expected constant but the user
@@ -177,7 +180,7 @@ module Zeitwerk
177
180
  end
178
181
  end
179
182
 
180
- unload_cref(parent, cname)
183
+ unload_cref(cref)
181
184
  unloaded_files.add(abspath) if ruby?(abspath)
182
185
  end
183
186
 
@@ -228,6 +231,87 @@ module Zeitwerk
228
231
  setup
229
232
  end
230
233
 
234
+ # Returns a hash that maps the absolute paths of the managed files and
235
+ # directories to their respective expected constant paths.
236
+ #
237
+ # @sig () -> Hash[String, String]
238
+ def all_expected_cpaths
239
+ result = {}
240
+
241
+ actual_roots.each do |root_dir, root_namespace|
242
+ queue = [[root_dir, real_mod_name(root_namespace)]]
243
+
244
+ while (dir, cpath = queue.shift)
245
+ result[dir] = cpath
246
+
247
+ prefix = cpath == "Object" ? "" : cpath + "::"
248
+
249
+ ls(dir) do |basename, abspath, ftype|
250
+ if ftype == :file
251
+ basename.delete_suffix!(".rb")
252
+ result[abspath] = prefix + inflector.camelize(basename, abspath)
253
+ else
254
+ if collapse?(abspath)
255
+ queue << [abspath, cpath]
256
+ else
257
+ queue << [abspath, prefix + inflector.camelize(basename, abspath)]
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ result
265
+ end
266
+
267
+ # @sig (String | Pathname) -> String?
268
+ def cpath_expected_at(path)
269
+ abspath = File.expand_path(path)
270
+
271
+ raise Zeitwerk::Error.new("#{abspath} does not exist") unless File.exist?(abspath)
272
+
273
+ return unless dir?(abspath) || ruby?(abspath)
274
+ return if ignored_path?(abspath)
275
+
276
+ paths = []
277
+
278
+ if ruby?(abspath)
279
+ basename = File.basename(abspath, ".rb")
280
+ return if hidden?(basename)
281
+
282
+ paths << [basename, abspath]
283
+ walk_up_from = File.dirname(abspath)
284
+ else
285
+ walk_up_from = abspath
286
+ end
287
+
288
+ root_namespace = nil
289
+
290
+ walk_up(walk_up_from) do |dir|
291
+ break if root_namespace = roots[dir]
292
+ return if ignored_path?(dir)
293
+
294
+ basename = File.basename(dir)
295
+ return if hidden?(basename)
296
+
297
+ paths << [basename, abspath] unless collapse?(dir)
298
+ end
299
+
300
+ return unless root_namespace
301
+
302
+ if paths.empty?
303
+ real_mod_name(root_namespace)
304
+ else
305
+ cnames = paths.reverse_each.map { |b, a| cname_for(b, a) }
306
+
307
+ if root_namespace == Object
308
+ cnames.join("::")
309
+ else
310
+ "#{real_mod_name(root_namespace)}::#{cnames.join("::")}"
311
+ end
312
+ end
313
+ end
314
+
231
315
  # Says if the given constant path would be unloaded on reload. This
232
316
  # predicate returns `false` if reloading is disabled.
233
317
  #
@@ -357,44 +441,26 @@ module Zeitwerk
357
441
  end
358
442
 
359
443
  # @sig (String, Module) -> void
360
- private def set_autoloads_in_dir(dir, parent)
361
- ls(dir) do |basename, abspath|
362
- begin
363
- if ruby?(basename)
364
- basename.delete_suffix!(".rb")
365
- cname = inflector.camelize(basename, abspath).to_sym
366
- autoload_file(parent, cname, abspath)
444
+ private def define_autoloads_for_dir(dir, parent)
445
+ ls(dir) do |basename, abspath, ftype|
446
+ if ftype == :file
447
+ basename.delete_suffix!(".rb")
448
+ cref = Cref.new(parent, cname_for(basename, abspath))
449
+ autoload_file(cref, abspath)
450
+ else
451
+ if collapse?(abspath)
452
+ define_autoloads_for_dir(abspath, parent)
367
453
  else
368
- if collapse?(abspath)
369
- set_autoloads_in_dir(abspath, parent)
370
- else
371
- cname = inflector.camelize(basename, abspath).to_sym
372
- autoload_subdir(parent, cname, abspath)
373
- end
454
+ cref = Cref.new(parent, cname_for(basename, abspath))
455
+ autoload_subdir(cref, abspath)
374
456
  end
375
- rescue ::NameError => error
376
- path_type = ruby?(abspath) ? "file" : "directory"
377
-
378
- raise NameError.new(<<~MESSAGE, error.name)
379
- #{error.message} inferred by #{inflector.class} from #{path_type}
380
-
381
- #{abspath}
382
-
383
- Possible ways to address this:
384
-
385
- * Tell Zeitwerk to ignore this particular #{path_type}.
386
- * Tell Zeitwerk to ignore one of its parent directories.
387
- * Rename the #{path_type} to comply with the naming conventions.
388
- * Modify the inflector to handle this case.
389
- MESSAGE
390
457
  end
391
458
  end
392
459
  end
393
460
 
394
461
  # @sig (Module, Symbol, String) -> void
395
- private def autoload_subdir(parent, cname, subdir)
396
- if autoload_path = autoload_path_set_by_me_for?(parent, cname)
397
- cpath = cpath(parent, cname)
462
+ private def autoload_subdir(cref, subdir)
463
+ if autoload_path = autoload_path_set_by_me_for?(cref)
398
464
  if ruby?(autoload_path)
399
465
  # Scanning visited a Ruby file first, and now a directory for the same
400
466
  # constant has been found. This means we are dealing with an explicit
@@ -403,88 +469,83 @@ module Zeitwerk
403
469
  # Registering is idempotent, and we have to keep the autoload pointing
404
470
  # to the file. This may run again if more directories are found later
405
471
  # on, no big deal.
406
- register_explicit_namespace(cpath)
472
+ register_explicit_namespace(cref.path)
407
473
  end
408
474
  # If the existing autoload points to a file, it has to be preserved, if
409
475
  # not, it is fine as it is. In either case, we do not need to override.
410
476
  # Just remember the subdirectory conforms this namespace.
411
- namespace_dirs[cpath] << subdir
412
- elsif !cdef?(parent, cname)
477
+ namespace_dirs[cref.path] << subdir
478
+ elsif !cref.defined?
413
479
  # First time we find this namespace, set an autoload for it.
414
- namespace_dirs[cpath(parent, cname)] << subdir
415
- set_autoload(parent, cname, subdir)
480
+ namespace_dirs[cref.path] << subdir
481
+ define_autoload(cref, subdir)
416
482
  else
417
483
  # For whatever reason the constant that corresponds to this namespace has
418
484
  # already been defined, we have to recurse.
419
- log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
420
- set_autoloads_in_dir(subdir, cget(parent, cname))
485
+ log("the namespace #{cref.path} already exists, descending into #{subdir}") if logger
486
+ define_autoloads_for_dir(subdir, cref.get)
421
487
  end
422
488
  end
423
489
 
424
490
  # @sig (Module, Symbol, String) -> void
425
- private def autoload_file(parent, cname, file)
426
- if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
491
+ private def autoload_file(cref, file)
492
+ if autoload_path = cref.autoload? || Registry.inception?(cref.path)
427
493
  # First autoload for a Ruby file wins, just ignore subsequent ones.
428
494
  if ruby?(autoload_path)
429
495
  shadowed_files << file
430
496
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
431
497
  else
432
- promote_namespace_from_implicit_to_explicit(
433
- dir: autoload_path,
434
- file: file,
435
- parent: parent,
436
- cname: cname
437
- )
498
+ promote_namespace_from_implicit_to_explicit(dir: autoload_path, file: file, cref: cref)
438
499
  end
439
- elsif cdef?(parent, cname)
500
+ elsif cref.defined?
440
501
  shadowed_files << file
441
- log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
502
+ log("file #{file} is ignored because #{cref.path} is already defined") if logger
442
503
  else
443
- set_autoload(parent, cname, file)
504
+ define_autoload(cref, file)
444
505
  end
445
506
  end
446
507
 
447
508
  # `dir` is the directory that would have autovivified a namespace. `file` is
448
509
  # the file where we've found the namespace is explicitly defined.
449
510
  #
450
- # @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
451
- private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
511
+ # @sig (dir: String, file: String, cref: Zeitwerk::Cref) -> void
512
+ private def promote_namespace_from_implicit_to_explicit(dir:, file:, cref:)
452
513
  autoloads.delete(dir)
453
514
  Registry.unregister_autoload(dir)
454
515
 
455
- log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
516
+ log("earlier autoload for #{cref.path} discarded, it is actually an explicit namespace defined in #{file}") if logger
456
517
 
457
- set_autoload(parent, cname, file)
458
- register_explicit_namespace(cpath(parent, cname))
518
+ define_autoload(cref, file)
519
+ register_explicit_namespace(cref.path)
459
520
  end
460
521
 
461
522
  # @sig (Module, Symbol, String) -> void
462
- private def set_autoload(parent, cname, abspath)
463
- parent.autoload(cname, abspath)
523
+ private def define_autoload(cref, abspath)
524
+ cref.autoload(abspath)
464
525
 
465
526
  if logger
466
527
  if ruby?(abspath)
467
- log("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")
528
+ log("autoload set for #{cref.path}, to be loaded from #{abspath}")
468
529
  else
469
- log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
530
+ log("autoload set for #{cref.path}, to be autovivified from #{abspath}")
470
531
  end
471
532
  end
472
533
 
473
- autoloads[abspath] = [parent, cname]
534
+ autoloads[abspath] = cref
474
535
  Registry.register_autoload(self, abspath)
475
536
 
476
537
  # See why in the documentation of Zeitwerk::Registry.inceptions.
477
- unless parent.autoload?(cname)
478
- Registry.register_inception(cpath(parent, cname), abspath, self)
538
+ unless cref.autoload?
539
+ Registry.register_inception(cref.path, abspath, self)
479
540
  end
480
541
  end
481
542
 
482
543
  # @sig (Module, Symbol) -> String?
483
- private def autoload_path_set_by_me_for?(parent, cname)
484
- if autoload_path = strict_autoload_path(parent, cname)
544
+ private def autoload_path_set_by_me_for?(cref)
545
+ if autoload_path = cref.autoload?
485
546
  autoload_path if autoloads.key?(autoload_path)
486
547
  else
487
- Registry.inception?(cpath(parent, cname))
548
+ Registry.inception?(cref.path, self)
488
549
  end
489
550
  end
490
551
 
@@ -511,7 +572,6 @@ module Zeitwerk
511
572
  raise Error,
512
573
  "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
513
574
  " which is already managed by\n\n#{loader.pretty_inspect}\n"
514
- EOS
515
575
  end
516
576
  end
517
577
  end
@@ -526,21 +586,21 @@ module Zeitwerk
526
586
  end
527
587
 
528
588
  # @sig (Module, Symbol) -> void
529
- private def unload_autoload(parent, cname)
530
- crem(parent, cname)
531
- log("autoload for #{cpath(parent, cname)} removed") if logger
589
+ private def unload_autoload(cref)
590
+ cref.remove
591
+ log("autoload for #{cref.path} removed") if logger
532
592
  end
533
593
 
534
594
  # @sig (Module, Symbol) -> void
535
- private def unload_cref(parent, cname)
595
+ private def unload_cref(cref)
536
596
  # Let's optimistically remove_const. The way we use it, this is going to
537
597
  # succeed always if all is good.
538
- crem(parent, cname)
598
+ cref.remove
539
599
  rescue ::NameError
540
600
  # There are a few edge scenarios in which this may happen. If the constant
541
601
  # is gone, that is OK, anyway.
542
602
  else
543
- log("#{cpath(parent, cname)} unloaded") if logger
603
+ log("#{cref.path} unloaded") if logger
544
604
  end
545
605
  end
546
606
  end
@@ -0,0 +1,5 @@
1
+ class Zeitwerk::NullInflector
2
+ def camelize(basename, _abspath)
3
+ basename
4
+ end
5
+ end