zeitwerk 2.6.0 → 2.6.15

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.
@@ -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
@@ -7,11 +8,15 @@ module Zeitwerk
7
8
  require_relative "loader/helpers"
8
9
  require_relative "loader/callbacks"
9
10
  require_relative "loader/config"
11
+ require_relative "loader/eager_load"
12
+
13
+ extend Internal
10
14
 
11
15
  include RealModName
12
16
  include Callbacks
13
17
  include Helpers
14
18
  include Config
19
+ include EagerLoad
15
20
 
16
21
  MUTEX = Mutex.new
17
22
  private_constant :MUTEX
@@ -24,9 +29,9 @@ module Zeitwerk
24
29
  # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
25
30
  # ...
26
31
  #
27
- # @private
28
32
  # @sig Hash[String, [Module, Symbol]]
29
33
  attr_reader :autoloads
34
+ internal :autoloads
30
35
 
31
36
  # We keep track of autoloaded directories to remove them from the registry
32
37
  # at the end of eager loading.
@@ -34,9 +39,9 @@ module Zeitwerk
34
39
  # Files are removed as they are autoloaded, but directories need to wait due
35
40
  # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
36
41
  #
37
- # @private
38
42
  # @sig Array[String]
39
43
  attr_reader :autoloaded_dirs
44
+ internal :autoloaded_dirs
40
45
 
41
46
  # Stores metadata needed for unloading. Its entries look like this:
42
47
  #
@@ -50,11 +55,11 @@ module Zeitwerk
50
55
  # If reloading is enabled, this hash is filled as constants are autoloaded
51
56
  # or eager loaded. Otherwise, the collection remains empty.
52
57
  #
53
- # @private
54
58
  # @sig Hash[String, [String, [Module, Symbol]]]
55
59
  attr_reader :to_unload
60
+ internal :to_unload
56
61
 
57
- # Maps constant paths of namespaces to arrays of corresponding directories.
62
+ # Maps namespace constant paths to their respective directories.
58
63
  #
59
64
  # For example, given this mapping:
60
65
  #
@@ -64,21 +69,32 @@ module Zeitwerk
64
69
  # ...
65
70
  # ]
66
71
  #
67
- # when `Admin` gets defined we know that it plays the role of a namespace and
68
- # that its children are spread over those directories. We'll visit them to set
69
- # up the corresponding autoloads.
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.
70
75
  #
71
- # @private
72
76
  # @sig Hash[String, Array[String]]
73
- attr_reader :lazy_subdirs
77
+ attr_reader :namespace_dirs
78
+ internal :namespace_dirs
79
+
80
+ # A shadowed file is a file managed by this loader that is ignored when
81
+ # setting autoloads because its matching constant is already taken.
82
+ #
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.
86
+ #
87
+ # @sig Set[String]
88
+ attr_reader :shadowed_files
89
+ internal :shadowed_files
74
90
 
75
- # @private
76
91
  # @sig Mutex
77
92
  attr_reader :mutex
93
+ private :mutex
78
94
 
79
- # @private
80
- # @sig Mutex
81
- attr_reader :mutex2
95
+ # @sig Monitor
96
+ attr_reader :dirs_autoload_monitor
97
+ private :dirs_autoload_monitor
82
98
 
83
99
  def initialize
84
100
  super
@@ -86,24 +102,26 @@ module Zeitwerk
86
102
  @autoloads = {}
87
103
  @autoloaded_dirs = []
88
104
  @to_unload = {}
89
- @lazy_subdirs = Hash.new { |h, cpath| h[cpath] = [] }
90
- @mutex = Mutex.new
91
- @mutex2 = Mutex.new
105
+ @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
106
+ @shadowed_files = Set.new
92
107
  @setup = false
93
108
  @eager_loaded = false
94
109
 
110
+ @mutex = Mutex.new
111
+ @dirs_autoload_monitor = Monitor.new
112
+
95
113
  Registry.register_loader(self)
96
114
  end
97
115
 
98
- # Sets autoloads in the root namespace.
116
+ # Sets autoloads in the root namespaces.
99
117
  #
100
118
  # @sig () -> void
101
119
  def setup
102
120
  mutex.synchronize do
103
121
  break if @setup
104
122
 
105
- actual_root_dirs.each do |root_dir, namespace|
106
- set_autoloads_in_dir(root_dir, namespace)
123
+ actual_roots.each do |root_dir, root_namespace|
124
+ define_autoloads_for_dir(root_dir, root_namespace)
107
125
  end
108
126
 
109
127
  on_setup_callbacks.each(&:call)
@@ -120,12 +138,14 @@ module Zeitwerk
120
138
  # unload them.
121
139
  #
122
140
  # This method is public but undocumented. Main interface is `reload`, which
123
- # means `unload` + `setup`. This one is avaiable to be used together with
141
+ # means `unload` + `setup`. This one is available to be used together with
124
142
  # `unregister`, which is undocumented too.
125
143
  #
126
144
  # @sig () -> void
127
145
  def unload
128
146
  mutex.synchronize do
147
+ raise SetupRequired unless @setup
148
+
129
149
  # We are going to keep track of the files that were required by our
130
150
  # autoloads to later remove them from $LOADED_FEATURES, thus making them
131
151
  # loadable by Kernel#require again.
@@ -181,10 +201,11 @@ module Zeitwerk
181
201
  autoloads.clear
182
202
  autoloaded_dirs.clear
183
203
  to_unload.clear
184
- lazy_subdirs.clear
204
+ namespace_dirs.clear
205
+ shadowed_files.clear
185
206
 
186
207
  Registry.on_unload(self)
187
- ExplicitNamespace.unregister_loader(self)
208
+ ExplicitNamespace.__unregister_loader(self)
188
209
 
189
210
  @setup = false
190
211
  @eager_loaded = false
@@ -201,6 +222,7 @@ module Zeitwerk
201
222
  # @sig () -> void
202
223
  def reload
203
224
  raise ReloadingDisabledError unless reloading_enabled?
225
+ raise SetupRequired unless @setup
204
226
 
205
227
  unload
206
228
  recompute_ignored_paths
@@ -208,55 +230,85 @@ module Zeitwerk
208
230
  setup
209
231
  end
210
232
 
211
- # Eager loads all files in the root directories, recursively. Files do not
212
- # need to be in `$LOAD_PATH`, absolute file names are used. Ignored files
213
- # are not eager loaded. You can opt-out specifically in specific files and
214
- # directories with `do_not_eager_load`, and that can be overridden passing
215
- # `force: true`.
233
+ # Returns a hash that maps the absolute paths of the managed files and
234
+ # directories to their respective expected constant paths.
216
235
  #
217
- # @sig (true | false) -> void
218
- def eager_load(force: false)
219
- mutex.synchronize do
220
- break if @eager_loaded
221
-
222
- log("eager load start") if logger
236
+ # @sig () -> Hash[String, String]
237
+ def all_expected_cpaths
238
+ result = {}
223
239
 
224
- honour_exclusions = !force
225
-
226
- queue = []
227
- actual_root_dirs.each do |root_dir, namespace|
228
- queue << [namespace, root_dir] unless honour_exclusions && excluded_from_eager_load?(root_dir)
229
- end
240
+ actual_roots.each do |root_dir, root_namespace|
241
+ queue = [[root_dir, real_mod_name(root_namespace)]]
230
242
 
231
- while to_eager_load = queue.shift
232
- namespace, dir = to_eager_load
243
+ until queue.empty?
244
+ dir, cpath = queue.shift
245
+ result[dir] = cpath
233
246
 
234
- ls(dir) do |basename, abspath|
235
- next if honour_exclusions && excluded_from_eager_load?(abspath)
247
+ prefix = cpath == "Object" ? "" : cpath + "::"
236
248
 
237
- if ruby?(abspath)
238
- if cref = autoloads[abspath]
239
- cget(*cref)
240
- end
241
- elsif !root_dirs.key?(abspath)
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
242
254
  if collapse?(abspath)
243
- queue << [namespace, abspath]
255
+ queue << [abspath, cpath]
244
256
  else
245
- cname = inflector.camelize(basename, abspath)
246
- queue << [cget(namespace, cname), abspath]
257
+ queue << [abspath, prefix + inflector.camelize(basename, abspath)]
247
258
  end
248
259
  end
249
260
  end
250
261
  end
262
+ end
251
263
 
252
- autoloaded_dirs.each do |autoloaded_dir|
253
- Registry.unregister_autoload(autoloaded_dir)
254
- end
255
- autoloaded_dirs.clear
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 = []
256
277
 
257
- @eager_loaded = true
278
+ if ruby?(abspath)
279
+ basename = File.basename(abspath, ".rb")
280
+ return if hidden?(basename)
258
281
 
259
- log("eager load end") if logger
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
260
312
  end
261
313
  end
262
314
 
@@ -282,18 +334,29 @@ module Zeitwerk
282
334
  # @sig () -> void
283
335
  def unregister
284
336
  Registry.unregister_loader(self)
285
- ExplicitNamespace.unregister_loader(self)
337
+ ExplicitNamespace.__unregister_loader(self)
338
+ end
339
+
340
+ # The return value of this predicate is only meaningful if the loader has
341
+ # scanned the file. This is the case in the spots where we use it.
342
+ #
343
+ # @sig (String) -> Boolean
344
+ internal def shadowed_file?(file)
345
+ shadowed_files.member?(file)
286
346
  end
287
347
 
288
348
  # --- Class methods ---------------------------------------------------------------------------
289
349
 
290
350
  class << self
351
+ include RealModName
352
+
291
353
  # @sig #call | #debug | nil
292
354
  attr_accessor :default_logger
293
355
 
294
356
  # This is a shortcut for
295
357
  #
296
358
  # require "zeitwerk"
359
+ #
297
360
  # loader = Zeitwerk::Loader.new
298
361
  # loader.tag = File.basename(__FILE__, ".rb")
299
362
  # loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
@@ -308,14 +371,64 @@ module Zeitwerk
308
371
  # @sig (bool) -> Zeitwerk::GemLoader
309
372
  def for_gem(warn_on_extra_files: true)
310
373
  called_from = caller_locations(1, 1).first.path
311
- Registry.loader_for_gem(called_from, warn_on_extra_files: warn_on_extra_files)
374
+ Registry.loader_for_gem(called_from, namespace: Object, warn_on_extra_files: warn_on_extra_files)
312
375
  end
313
376
 
314
- # Broadcasts `eager_load` to all loaders.
377
+ # This is a shortcut for
378
+ #
379
+ # require "zeitwerk"
380
+ #
381
+ # loader = Zeitwerk::Loader.new
382
+ # loader.tag = namespace.name + "-" + File.basename(__FILE__, ".rb")
383
+ # loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
384
+ # loader.push_dir(__dir__, namespace: namespace)
385
+ #
386
+ # except that this method returns the same object in subsequent calls from
387
+ # the same file, in the unlikely case the gem wants to be able to reload.
388
+ #
389
+ # This method returns a subclass of Zeitwerk::Loader, but the exact type
390
+ # is private, client code can only rely on the interface.
391
+ #
392
+ # @sig (bool) -> Zeitwerk::GemLoader
393
+ def for_gem_extension(namespace)
394
+ unless namespace.is_a?(Module) # Note that Class < Module.
395
+ raise Zeitwerk::Error, "#{namespace.inspect} is not a class or module object, should be"
396
+ end
397
+
398
+ unless real_mod_name(namespace)
399
+ raise Zeitwerk::Error, "extending anonymous namespaces is unsupported"
400
+ end
401
+
402
+ called_from = caller_locations(1, 1).first.path
403
+ Registry.loader_for_gem(called_from, namespace: namespace, warn_on_extra_files: false)
404
+ end
405
+
406
+ # Broadcasts `eager_load` to all loaders. Those that have not been setup
407
+ # are skipped.
315
408
  #
316
409
  # @sig () -> void
317
410
  def eager_load_all
318
- Registry.loaders.each(&:eager_load)
411
+ Registry.loaders.each do |loader|
412
+ begin
413
+ loader.eager_load
414
+ rescue SetupRequired
415
+ # This is fine, we eager load what can be eager loaded.
416
+ end
417
+ end
418
+ end
419
+
420
+ # Broadcasts `eager_load_namespace` to all loaders. Those that have not
421
+ # been setup are skipped.
422
+ #
423
+ # @sig (Module) -> void
424
+ def eager_load_namespace(mod)
425
+ Registry.loaders.each do |loader|
426
+ begin
427
+ loader.eager_load_namespace(mod)
428
+ rescue SetupRequired
429
+ # This is fine, we eager load what can be eager loaded.
430
+ end
431
+ end
319
432
  end
320
433
 
321
434
  # Returns an array with the absolute paths of the root directories of all
@@ -327,77 +440,58 @@ module Zeitwerk
327
440
  end
328
441
  end
329
442
 
330
- private # -------------------------------------------------------------------------------------
331
-
332
443
  # @sig (String, Module) -> void
333
- def set_autoloads_in_dir(dir, parent)
334
- ls(dir) do |basename, abspath|
335
- begin
336
- if ruby?(basename)
337
- basename.delete_suffix!(".rb")
338
- cname = inflector.camelize(basename, abspath).to_sym
339
- 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
+ autoload_file(parent, cname_for(basename, abspath), abspath)
449
+ else
450
+ if collapse?(abspath)
451
+ define_autoloads_for_dir(abspath, parent)
340
452
  else
341
- # In a Rails application, `app/models/concerns` is a subdirectory of
342
- # `app/models`, but both of them are root directories.
343
- #
344
- # To resolve the ambiguity file name -> constant path this introduces,
345
- # the `app/models/concerns` directory is totally ignored as a namespace,
346
- # it counts only as root. The guard checks that.
347
- unless root_dir?(abspath)
348
- cname = inflector.camelize(basename, abspath).to_sym
349
- if collapse?(abspath)
350
- set_autoloads_in_dir(abspath, parent)
351
- else
352
- autoload_subdir(parent, cname, abspath)
353
- end
354
- end
453
+ autoload_subdir(parent, cname_for(basename, abspath), abspath)
355
454
  end
356
- rescue ::NameError => error
357
- path_type = ruby?(abspath) ? "file" : "directory"
358
-
359
- raise NameError.new(<<~MESSAGE, error.name)
360
- #{error.message} inferred by #{inflector.class} from #{path_type}
361
-
362
- #{abspath}
363
-
364
- Possible ways to address this:
365
-
366
- * Tell Zeitwerk to ignore this particular #{path_type}.
367
- * Tell Zeitwerk to ignore one of its parent directories.
368
- * Rename the #{path_type} to comply with the naming conventions.
369
- * Modify the inflector to handle this case.
370
- MESSAGE
371
455
  end
372
456
  end
373
457
  end
374
458
 
375
459
  # @sig (Module, Symbol, String) -> void
376
- def autoload_subdir(parent, cname, subdir)
460
+ private def autoload_subdir(parent, cname, subdir)
377
461
  if autoload_path = autoload_path_set_by_me_for?(parent, cname)
378
462
  cpath = cpath(parent, cname)
379
- register_explicit_namespace(cpath) if ruby?(autoload_path)
380
- # We do not need to issue another autoload, the existing one is enough
381
- # no matter if it is for a file or a directory. Just remember the
382
- # subdirectory has to be visited if the namespace is used.
383
- lazy_subdirs[cpath] << subdir
463
+ if ruby?(autoload_path)
464
+ # Scanning visited a Ruby file first, and now a directory for the same
465
+ # constant has been found. This means we are dealing with an explicit
466
+ # namespace whose definition was seen first.
467
+ #
468
+ # Registering is idempotent, and we have to keep the autoload pointing
469
+ # to the file. This may run again if more directories are found later
470
+ # on, no big deal.
471
+ register_explicit_namespace(cpath)
472
+ end
473
+ # If the existing autoload points to a file, it has to be preserved, if
474
+ # not, it is fine as it is. In either case, we do not need to override.
475
+ # Just remember the subdirectory conforms this namespace.
476
+ namespace_dirs[cpath] << subdir
384
477
  elsif !cdef?(parent, cname)
385
478
  # First time we find this namespace, set an autoload for it.
386
- lazy_subdirs[cpath(parent, cname)] << subdir
387
- set_autoload(parent, cname, subdir)
479
+ namespace_dirs[cpath(parent, cname)] << subdir
480
+ define_autoload(parent, cname, subdir)
388
481
  else
389
482
  # For whatever reason the constant that corresponds to this namespace has
390
483
  # already been defined, we have to recurse.
391
484
  log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
392
- set_autoloads_in_dir(subdir, cget(parent, cname))
485
+ define_autoloads_for_dir(subdir, cget(parent, cname))
393
486
  end
394
487
  end
395
488
 
396
489
  # @sig (Module, Symbol, String) -> void
397
- def autoload_file(parent, cname, file)
490
+ private def autoload_file(parent, cname, file)
398
491
  if autoload_path = strict_autoload_path(parent, cname) || Registry.inception?(cpath(parent, cname))
399
492
  # First autoload for a Ruby file wins, just ignore subsequent ones.
400
493
  if ruby?(autoload_path)
494
+ shadowed_files << file
401
495
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
402
496
  else
403
497
  promote_namespace_from_implicit_to_explicit(
@@ -408,9 +502,10 @@ module Zeitwerk
408
502
  )
409
503
  end
410
504
  elsif cdef?(parent, cname)
505
+ shadowed_files << file
411
506
  log("file #{file} is ignored because #{cpath(parent, cname)} is already defined") if logger
412
507
  else
413
- set_autoload(parent, cname, file)
508
+ define_autoload(parent, cname, file)
414
509
  end
415
510
  end
416
511
 
@@ -418,18 +513,18 @@ module Zeitwerk
418
513
  # the file where we've found the namespace is explicitly defined.
419
514
  #
420
515
  # @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
421
- def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
516
+ private def promote_namespace_from_implicit_to_explicit(dir:, file:, parent:, cname:)
422
517
  autoloads.delete(dir)
423
518
  Registry.unregister_autoload(dir)
424
519
 
425
520
  log("earlier autoload for #{cpath(parent, cname)} discarded, it is actually an explicit namespace defined in #{file}") if logger
426
521
 
427
- set_autoload(parent, cname, file)
522
+ define_autoload(parent, cname, file)
428
523
  register_explicit_namespace(cpath(parent, cname))
429
524
  end
430
525
 
431
526
  # @sig (Module, Symbol, String) -> void
432
- def set_autoload(parent, cname, abspath)
527
+ private def define_autoload(parent, cname, abspath)
433
528
  parent.autoload(cname, abspath)
434
529
 
435
530
  if logger
@@ -450,7 +545,7 @@ module Zeitwerk
450
545
  end
451
546
 
452
547
  # @sig (Module, Symbol) -> String?
453
- def autoload_path_set_by_me_for?(parent, cname)
548
+ private def autoload_path_set_by_me_for?(parent, cname)
454
549
  if autoload_path = strict_autoload_path(parent, cname)
455
550
  autoload_path if autoloads.key?(autoload_path)
456
551
  else
@@ -459,28 +554,28 @@ module Zeitwerk
459
554
  end
460
555
 
461
556
  # @sig (String) -> void
462
- def register_explicit_namespace(cpath)
463
- ExplicitNamespace.register(cpath, self)
557
+ private def register_explicit_namespace(cpath)
558
+ ExplicitNamespace.__register(cpath, self)
464
559
  end
465
560
 
466
561
  # @sig (String) -> void
467
- def raise_if_conflicting_directory(dir)
562
+ private def raise_if_conflicting_directory(dir)
468
563
  MUTEX.synchronize do
564
+ dir_slash = dir + "/"
565
+
469
566
  Registry.loaders.each do |loader|
470
567
  next if loader == self
471
- next if loader.ignores?(dir)
568
+ next if loader.__ignores?(dir)
472
569
 
473
- dir = dir + "/"
474
- loader.root_dirs.each do |root_dir, _namespace|
570
+ loader.__roots.each_key do |root_dir|
475
571
  next if ignores?(root_dir)
476
572
 
477
- root_dir = root_dir + "/"
478
- if dir.start_with?(root_dir) || root_dir.start_with?(dir)
573
+ root_dir_slash = root_dir + "/"
574
+ if dir_slash.start_with?(root_dir_slash) || root_dir_slash.start_with?(dir_slash)
479
575
  require "pp" # Needed for pretty_inspect, even in Ruby 2.5.
480
576
  raise Error,
481
- "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir.chop}," \
577
+ "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
482
578
  " which is already managed by\n\n#{loader.pretty_inspect}\n"
483
- EOS
484
579
  end
485
580
  end
486
581
  end
@@ -488,23 +583,23 @@ module Zeitwerk
488
583
  end
489
584
 
490
585
  # @sig (String, Object, String) -> void
491
- def run_on_unload_callbacks(cpath, value, abspath)
586
+ private def run_on_unload_callbacks(cpath, value, abspath)
492
587
  # Order matters. If present, run the most specific one.
493
588
  on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
494
589
  on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
495
590
  end
496
591
 
497
592
  # @sig (Module, Symbol) -> void
498
- def unload_autoload(parent, cname)
499
- parent.__send__(:remove_const, cname)
593
+ private def unload_autoload(parent, cname)
594
+ crem(parent, cname)
500
595
  log("autoload for #{cpath(parent, cname)} removed") if logger
501
596
  end
502
597
 
503
598
  # @sig (Module, Symbol) -> void
504
- def unload_cref(parent, cname)
599
+ private def unload_cref(parent, cname)
505
600
  # Let's optimistically remove_const. The way we use it, this is going to
506
601
  # succeed always if all is good.
507
- parent.__send__(:remove_const, cname)
602
+ crem(parent, cname)
508
603
  rescue ::NameError
509
604
  # There are a few edge scenarios in which this may happen. If the constant
510
605
  # is gone, that is OK, anyway.
@@ -0,0 +1,5 @@
1
+ class Zeitwerk::NullInflector
2
+ def camelize(basename, _abspath)
3
+ basename
4
+ end
5
+ end
@@ -86,8 +86,8 @@ module Zeitwerk
86
86
  #
87
87
  # @private
88
88
  # @sig (String) -> Zeitwerk::Loader
89
- def loader_for_gem(root_file, warn_on_extra_files:)
90
- gem_loaders_by_root_file[root_file] ||= GemLoader._new(root_file, warn_on_extra_files: warn_on_extra_files)
89
+ def loader_for_gem(root_file, namespace:, warn_on_extra_files:)
90
+ gem_loaders_by_root_file[root_file] ||= GemLoader.__new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
91
91
  end
92
92
 
93
93
  # @private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.0"
4
+ VERSION = "2.6.15"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
+ require_relative "zeitwerk/internal"
5
6
  require_relative "zeitwerk/loader"
6
7
  require_relative "zeitwerk/gem_loader"
7
8
  require_relative "zeitwerk/registry"
8
9
  require_relative "zeitwerk/explicit_namespace"
9
10
  require_relative "zeitwerk/inflector"
10
11
  require_relative "zeitwerk/gem_inflector"
12
+ require_relative "zeitwerk/null_inflector"
11
13
  require_relative "zeitwerk/kernel"
12
14
  require_relative "zeitwerk/error"
13
15
  require_relative "zeitwerk/version"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeitwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.6.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-13 00:00:00.000000000 Z
11
+ date: 2024-05-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -28,11 +28,14 @@ files:
28
28
  - lib/zeitwerk/gem_inflector.rb
29
29
  - lib/zeitwerk/gem_loader.rb
30
30
  - lib/zeitwerk/inflector.rb
31
+ - lib/zeitwerk/internal.rb
31
32
  - lib/zeitwerk/kernel.rb
32
33
  - lib/zeitwerk/loader.rb
33
34
  - lib/zeitwerk/loader/callbacks.rb
34
35
  - lib/zeitwerk/loader/config.rb
36
+ - lib/zeitwerk/loader/eager_load.rb
35
37
  - lib/zeitwerk/loader/helpers.rb
38
+ - lib/zeitwerk/null_inflector.rb
36
39
  - lib/zeitwerk/real_mod_name.rb
37
40
  - lib/zeitwerk/registry.rb
38
41
  - lib/zeitwerk/version.rb
@@ -59,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
62
  - !ruby/object:Gem::Version
60
63
  version: '0'
61
64
  requirements: []
62
- rubygems_version: 3.3.3
65
+ rubygems_version: 3.5.7
63
66
  signing_key:
64
67
  specification_version: 4
65
68
  summary: Efficient and thread-safe constant autoloader