zeitwerk 2.6.6 → 2.7.0

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
@@ -9,6 +10,8 @@ module Zeitwerk
9
10
  require_relative "loader/config"
10
11
  require_relative "loader/eager_load"
11
12
 
13
+ extend Internal
14
+
12
15
  include RealModName
13
16
  include Callbacks
14
17
  include Helpers
@@ -19,16 +22,15 @@ module Zeitwerk
19
22
  private_constant :MUTEX
20
23
 
21
24
  # Maps absolute paths for which an autoload has been set ---and not
22
- # executed--- to their corresponding parent class or module and constant
23
- # name.
25
+ # executed--- to their corresponding Zeitwerk::Cref object.
24
26
  #
25
- # "/Users/fxn/blog/app/models/user.rb" => [Object, :User],
26
- # "/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, ...>,
27
29
  # ...
28
30
  #
29
- # @private
30
- # @sig Hash[String, [Module, Symbol]]
31
+ # @sig Hash[String, Zeitwerk::Cref]
31
32
  attr_reader :autoloads
33
+ internal :autoloads
32
34
 
33
35
  # We keep track of autoloaded directories to remove them from the registry
34
36
  # at the end of eager loading.
@@ -36,25 +38,27 @@ module Zeitwerk
36
38
  # Files are removed as they are autoloaded, but directories need to wait due
37
39
  # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
38
40
  #
39
- # @private
40
41
  # @sig Array[String]
41
42
  attr_reader :autoloaded_dirs
43
+ internal :autoloaded_dirs
42
44
 
43
45
  # Stores metadata needed for unloading. Its entries look like this:
44
46
  #
45
- # "Admin::Role" => [".../admin/role.rb", [Admin, :Role]]
47
+ # "Admin::Role" => [
48
+ # ".../admin/role.rb",
49
+ # #<Zeitwerk::Cref:... @mod=Admin, @cname=:Role, ...>
50
+ # ]
46
51
  #
47
52
  # The cpath as key helps implementing unloadable_cpath? The file name is
48
53
  # stored in order to be able to delete it from $LOADED_FEATURES, and the
49
- # pair [Module, Symbol] is used to remove_const the constant from the class
50
- # or module object.
54
+ # cref is used to remove the constant from the parent class or module.
51
55
  #
52
56
  # If reloading is enabled, this hash is filled as constants are autoloaded
53
57
  # or eager loaded. Otherwise, the collection remains empty.
54
58
  #
55
- # @private
56
- # @sig Hash[String, [String, [Module, Symbol]]]
59
+ # @sig Hash[String, [String, Zeitwerk::Cref]]
57
60
  attr_reader :to_unload
61
+ internal :to_unload
58
62
 
59
63
  # Maps namespace constant paths to their respective directories.
60
64
  #
@@ -70,9 +74,9 @@ module Zeitwerk
70
74
  # and that its children are spread over those directories. We'll visit them
71
75
  # to set up the corresponding autoloads.
72
76
  #
73
- # @private
74
77
  # @sig Hash[String, Array[String]]
75
78
  attr_reader :namespace_dirs
79
+ internal :namespace_dirs
76
80
 
77
81
  # A shadowed file is a file managed by this loader that is ignored when
78
82
  # setting autoloads because its matching constant is already taken.
@@ -81,17 +85,17 @@ module Zeitwerk
81
85
  # has only scanned the top-level, `shadowed_files` does not have shadowed
82
86
  # files that may exist deep in the project tree yet.
83
87
  #
84
- # @private
85
88
  # @sig Set[String]
86
89
  attr_reader :shadowed_files
90
+ internal :shadowed_files
87
91
 
88
- # @private
89
92
  # @sig Mutex
90
93
  attr_reader :mutex
94
+ private :mutex
91
95
 
92
- # @private
93
- # @sig Mutex
94
- attr_reader :mutex2
96
+ # @sig Monitor
97
+ attr_reader :dirs_autoload_monitor
98
+ private :dirs_autoload_monitor
95
99
 
96
100
  def initialize
97
101
  super
@@ -101,11 +105,12 @@ module Zeitwerk
101
105
  @to_unload = {}
102
106
  @namespace_dirs = Hash.new { |h, cpath| h[cpath] = [] }
103
107
  @shadowed_files = Set.new
104
- @mutex = Mutex.new
105
- @mutex2 = Mutex.new
106
108
  @setup = false
107
109
  @eager_loaded = false
108
110
 
111
+ @mutex = Mutex.new
112
+ @dirs_autoload_monitor = Monitor.new
113
+
109
114
  Registry.register_loader(self)
110
115
  end
111
116
 
@@ -117,7 +122,7 @@ module Zeitwerk
117
122
  break if @setup
118
123
 
119
124
  actual_roots.each do |root_dir, root_namespace|
120
- set_autoloads_in_dir(root_dir, root_namespace)
125
+ define_autoloads_for_dir(root_dir, root_namespace)
121
126
  end
122
127
 
123
128
  on_setup_callbacks.each(&:call)
@@ -134,7 +139,7 @@ module Zeitwerk
134
139
  # unload them.
135
140
  #
136
141
  # This method is public but undocumented. Main interface is `reload`, which
137
- # means `unload` + `setup`. This one is avaiable to be used together with
142
+ # means `unload` + `setup`. This one is available to be used together with
138
143
  # `unregister`, which is undocumented too.
139
144
  #
140
145
  # @sig () -> void
@@ -150,22 +155,22 @@ module Zeitwerk
150
155
  # is enough.
151
156
  unloaded_files = Set.new
152
157
 
153
- autoloads.each do |abspath, (parent, cname)|
154
- if parent.autoload?(cname)
155
- unload_autoload(parent, cname)
158
+ autoloads.each do |abspath, cref|
159
+ if cref.autoload?
160
+ unload_autoload(cref)
156
161
  else
157
162
  # Could happen if loaded with require_relative. That is unsupported,
158
163
  # and the constant path would escape unloadable_cpath? This is just
159
164
  # defensive code to clean things up as much as we are able to.
160
- unload_cref(parent, cname)
165
+ unload_cref(cref)
161
166
  unloaded_files.add(abspath) if ruby?(abspath)
162
167
  end
163
168
  end
164
169
 
165
- to_unload.each do |cpath, (abspath, (parent, cname))|
170
+ to_unload.each do |cpath, (abspath, cref)|
166
171
  unless on_unload_callbacks.empty?
167
172
  begin
168
- value = cget(parent, cname)
173
+ value = cref.get
169
174
  rescue ::NameError
170
175
  # Perhaps the user deleted the constant by hand, or perhaps an
171
176
  # autoload failed to define the expected constant but the user
@@ -175,7 +180,7 @@ module Zeitwerk
175
180
  end
176
181
  end
177
182
 
178
- unload_cref(parent, cname)
183
+ unload_cref(cref)
179
184
  unloaded_files.add(abspath) if ruby?(abspath)
180
185
  end
181
186
 
@@ -226,6 +231,87 @@ module Zeitwerk
226
231
  setup
227
232
  end
228
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
+
229
315
  # Says if the given constant path would be unloaded on reload. This
230
316
  # predicate returns `false` if reloading is disabled.
231
317
  #
@@ -254,21 +340,23 @@ module Zeitwerk
254
340
  # The return value of this predicate is only meaningful if the loader has
255
341
  # scanned the file. This is the case in the spots where we use it.
256
342
  #
257
- # @private
258
343
  # @sig (String) -> Boolean
259
- def shadowed_file?(file)
344
+ internal def shadowed_file?(file)
260
345
  shadowed_files.member?(file)
261
346
  end
262
347
 
263
348
  # --- Class methods ---------------------------------------------------------------------------
264
349
 
265
350
  class << self
351
+ include RealModName
352
+
266
353
  # @sig #call | #debug | nil
267
354
  attr_accessor :default_logger
268
355
 
269
356
  # This is a shortcut for
270
357
  #
271
358
  # require "zeitwerk"
359
+ #
272
360
  # loader = Zeitwerk::Loader.new
273
361
  # loader.tag = File.basename(__FILE__, ".rb")
274
362
  # loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
@@ -283,7 +371,36 @@ module Zeitwerk
283
371
  # @sig (bool) -> Zeitwerk::GemLoader
284
372
  def for_gem(warn_on_extra_files: true)
285
373
  called_from = caller_locations(1, 1).first.path
286
- 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)
375
+ end
376
+
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)
287
404
  end
288
405
 
289
406
  # Broadcasts `eager_load` to all loaders. Those that have not been setup
@@ -323,138 +440,124 @@ module Zeitwerk
323
440
  end
324
441
  end
325
442
 
326
- private # -------------------------------------------------------------------------------------
327
-
328
443
  # @sig (String, Module) -> void
329
- def set_autoloads_in_dir(dir, parent)
330
- ls(dir) do |basename, abspath|
331
- begin
332
- if ruby?(basename)
333
- basename.delete_suffix!(".rb")
334
- cname = inflector.camelize(basename, abspath).to_sym
335
- 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)
336
453
  else
337
- if collapse?(abspath)
338
- set_autoloads_in_dir(abspath, parent)
339
- else
340
- cname = inflector.camelize(basename, abspath).to_sym
341
- autoload_subdir(parent, cname, abspath)
342
- end
454
+ cref = Cref.new(parent, cname_for(basename, abspath))
455
+ autoload_subdir(cref, abspath)
343
456
  end
344
- rescue ::NameError => error
345
- path_type = ruby?(abspath) ? "file" : "directory"
346
-
347
- raise NameError.new(<<~MESSAGE, error.name)
348
- #{error.message} inferred by #{inflector.class} from #{path_type}
349
-
350
- #{abspath}
351
-
352
- Possible ways to address this:
353
-
354
- * Tell Zeitwerk to ignore this particular #{path_type}.
355
- * Tell Zeitwerk to ignore one of its parent directories.
356
- * Rename the #{path_type} to comply with the naming conventions.
357
- * Modify the inflector to handle this case.
358
- MESSAGE
359
457
  end
360
458
  end
361
459
  end
362
460
 
363
461
  # @sig (Module, Symbol, String) -> void
364
- def autoload_subdir(parent, cname, subdir)
365
- if autoload_path = autoload_path_set_by_me_for?(parent, cname)
366
- cpath = cpath(parent, cname)
367
- register_explicit_namespace(cpath) if ruby?(autoload_path)
368
- # We do not need to issue another autoload, the existing one is enough
369
- # no matter if it is for a file or a directory. Just remember the
370
- # subdirectory has to be visited if the namespace is used.
371
- namespace_dirs[cpath] << subdir
372
- elsif !cdef?(parent, cname)
462
+ private def autoload_subdir(cref, subdir)
463
+ if autoload_path = autoload_path_set_by_me_for?(cref)
464
+ if ruby?(autoload_path)
465
+ # Scanning visited a Ruby file first, and now a directory for the same
466
+ # constant has been found. This means we are dealing with an explicit
467
+ # namespace whose definition was seen first.
468
+ #
469
+ # Registering is idempotent, and we have to keep the autoload pointing
470
+ # to the file. This may run again if more directories are found later
471
+ # on, no big deal.
472
+ register_explicit_namespace(cref)
473
+ end
474
+ # If the existing autoload points to a file, it has to be preserved, if
475
+ # not, it is fine as it is. In either case, we do not need to override.
476
+ # Just remember the subdirectory conforms this namespace.
477
+ namespace_dirs[cref.path] << subdir
478
+ elsif !cref.defined?
373
479
  # First time we find this namespace, set an autoload for it.
374
- namespace_dirs[cpath(parent, cname)] << subdir
375
- set_autoload(parent, cname, subdir)
480
+ namespace_dirs[cref.path] << subdir
481
+ define_autoload(cref, subdir)
376
482
  else
377
483
  # For whatever reason the constant that corresponds to this namespace has
378
484
  # already been defined, we have to recurse.
379
- log("the namespace #{cpath(parent, cname)} already exists, descending into #{subdir}") if logger
380
- 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)
381
487
  end
382
488
  end
383
489
 
384
490
  # @sig (Module, Symbol, String) -> void
385
- def autoload_file(parent, cname, file)
386
- 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)
387
493
  # First autoload for a Ruby file wins, just ignore subsequent ones.
388
494
  if ruby?(autoload_path)
389
495
  shadowed_files << file
390
496
  log("file #{file} is ignored because #{autoload_path} has precedence") if logger
391
497
  else
392
- promote_namespace_from_implicit_to_explicit(
393
- dir: autoload_path,
394
- file: file,
395
- parent: parent,
396
- cname: cname
397
- )
498
+ promote_namespace_from_implicit_to_explicit(dir: autoload_path, file: file, cref: cref)
398
499
  end
399
- elsif cdef?(parent, cname)
500
+ elsif cref.defined?
400
501
  shadowed_files << file
401
- 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
402
503
  else
403
- set_autoload(parent, cname, file)
504
+ define_autoload(cref, file)
404
505
  end
405
506
  end
406
507
 
407
508
  # `dir` is the directory that would have autovivified a namespace. `file` is
408
509
  # the file where we've found the namespace is explicitly defined.
409
510
  #
410
- # @sig (dir: String, file: String, parent: Module, cname: Symbol) -> void
411
- 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:)
412
513
  autoloads.delete(dir)
413
514
  Registry.unregister_autoload(dir)
414
515
 
415
- 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
416
517
 
417
- set_autoload(parent, cname, file)
418
- register_explicit_namespace(cpath(parent, cname))
518
+ # Order matters: When Module#const_added is triggered by the autoload, we
519
+ # don't want the namespace to be registered yet.
520
+ define_autoload(cref, file)
521
+ register_explicit_namespace(cref)
419
522
  end
420
523
 
421
524
  # @sig (Module, Symbol, String) -> void
422
- def set_autoload(parent, cname, abspath)
423
- parent.autoload(cname, abspath)
525
+ private def define_autoload(cref, abspath)
526
+ cref.autoload(abspath)
424
527
 
425
528
  if logger
426
529
  if ruby?(abspath)
427
- log("autoload set for #{cpath(parent, cname)}, to be loaded from #{abspath}")
530
+ log("autoload set for #{cref.path}, to be loaded from #{abspath}")
428
531
  else
429
- log("autoload set for #{cpath(parent, cname)}, to be autovivified from #{abspath}")
532
+ log("autoload set for #{cref.path}, to be autovivified from #{abspath}")
430
533
  end
431
534
  end
432
535
 
433
- autoloads[abspath] = [parent, cname]
536
+ autoloads[abspath] = cref
434
537
  Registry.register_autoload(self, abspath)
435
538
 
436
539
  # See why in the documentation of Zeitwerk::Registry.inceptions.
437
- unless parent.autoload?(cname)
438
- Registry.register_inception(cpath(parent, cname), abspath, self)
540
+ unless cref.autoload?
541
+ Registry.register_inception(cref.path, abspath, self)
439
542
  end
440
543
  end
441
544
 
442
545
  # @sig (Module, Symbol) -> String?
443
- def autoload_path_set_by_me_for?(parent, cname)
444
- if autoload_path = strict_autoload_path(parent, cname)
546
+ private def autoload_path_set_by_me_for?(cref)
547
+ if autoload_path = cref.autoload?
445
548
  autoload_path if autoloads.key?(autoload_path)
446
549
  else
447
- Registry.inception?(cpath(parent, cname))
550
+ Registry.inception?(cref.path, self)
448
551
  end
449
552
  end
450
553
 
451
- # @sig (String) -> void
452
- def register_explicit_namespace(cpath)
453
- ExplicitNamespace.__register(cpath, self)
554
+ # @sig (Zeitwerk::Cref) -> void
555
+ private def register_explicit_namespace(cref)
556
+ ExplicitNamespace.__register(cref.path, self)
454
557
  end
455
558
 
456
559
  # @sig (String) -> void
457
- def raise_if_conflicting_directory(dir)
560
+ private def raise_if_conflicting_directory(dir)
458
561
  MUTEX.synchronize do
459
562
  dir_slash = dir + "/"
460
563
 
@@ -471,7 +574,6 @@ module Zeitwerk
471
574
  raise Error,
472
575
  "loader\n\n#{pretty_inspect}\n\nwants to manage directory #{dir}," \
473
576
  " which is already managed by\n\n#{loader.pretty_inspect}\n"
474
- EOS
475
577
  end
476
578
  end
477
579
  end
@@ -479,28 +581,28 @@ module Zeitwerk
479
581
  end
480
582
 
481
583
  # @sig (String, Object, String) -> void
482
- def run_on_unload_callbacks(cpath, value, abspath)
584
+ private def run_on_unload_callbacks(cpath, value, abspath)
483
585
  # Order matters. If present, run the most specific one.
484
586
  on_unload_callbacks[cpath]&.each { |c| c.call(value, abspath) }
485
587
  on_unload_callbacks[:ANY]&.each { |c| c.call(cpath, value, abspath) }
486
588
  end
487
589
 
488
590
  # @sig (Module, Symbol) -> void
489
- def unload_autoload(parent, cname)
490
- parent.__send__(:remove_const, cname)
491
- log("autoload for #{cpath(parent, cname)} removed") if logger
591
+ private def unload_autoload(cref)
592
+ cref.remove
593
+ log("autoload for #{cref.path} removed") if logger
492
594
  end
493
595
 
494
596
  # @sig (Module, Symbol) -> void
495
- def unload_cref(parent, cname)
597
+ private def unload_cref(cref)
496
598
  # Let's optimistically remove_const. The way we use it, this is going to
497
599
  # succeed always if all is good.
498
- parent.__send__(:remove_const, cname)
600
+ cref.remove
499
601
  rescue ::NameError
500
602
  # There are a few edge scenarios in which this may happen. If the constant
501
603
  # is gone, that is OK, anyway.
502
604
  else
503
- log("#{cpath(parent, cname)} unloaded") if logger
605
+ log("#{cref.path} unloaded") if logger
504
606
  end
505
607
  end
506
608
  end
@@ -0,0 +1,5 @@
1
+ class Zeitwerk::NullInflector
2
+ def camelize(basename, _abspath)
3
+ basename
4
+ end
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
@@ -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
@@ -110,9 +110,12 @@ module Zeitwerk
110
110
 
111
111
  # @private
112
112
  # @sig (String) -> String?
113
- def inception?(cpath)
113
+ def inception?(cpath, registered_by_loader=nil)
114
114
  if pair = inceptions[cpath]
115
- pair.first
115
+ abspath, loader = pair
116
+ if registered_by_loader.nil? || registered_by_loader.equal?(loader)
117
+ abspath
118
+ end
116
119
  end
117
120
  end
118
121
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.6"
4
+ VERSION = "2.7.0"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -3,16 +3,20 @@
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
5
  require_relative "zeitwerk/internal"
6
+ require_relative "zeitwerk/cref"
6
7
  require_relative "zeitwerk/loader"
7
8
  require_relative "zeitwerk/gem_loader"
8
9
  require_relative "zeitwerk/registry"
9
10
  require_relative "zeitwerk/explicit_namespace"
10
11
  require_relative "zeitwerk/inflector"
11
12
  require_relative "zeitwerk/gem_inflector"
12
- require_relative "zeitwerk/kernel"
13
+ require_relative "zeitwerk/null_inflector"
13
14
  require_relative "zeitwerk/error"
14
15
  require_relative "zeitwerk/version"
15
16
 
17
+ require_relative "zeitwerk/core_ext/kernel"
18
+ require_relative "zeitwerk/core_ext/module"
19
+
16
20
  # This is a dangerous method.
17
21
  #
18
22
  # @experimental