zeitwerk 2.6.16 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ module Zeitwerk::Registry
2
+ # Loaders know their own inceptions, but there is a use case in which we need
3
+ # to know if a given cpath is an inception globally. This is what this
4
+ # registry is for.
5
+ class Inceptions # :nodoc:
6
+ #: () -> void
7
+ def initialize
8
+ @inceptions = Zeitwerk::Cref::Map.new #: Zeitwerk::Cref::Map[String]
9
+ end
10
+
11
+ #: (Zeitwerk::Cref, String) -> void
12
+ def register(cref, abspath)
13
+ @inceptions[cref] = abspath
14
+ end
15
+
16
+ #: (Zeitwerk::Cref) -> String?
17
+ def registered?(cref)
18
+ @inceptions[cref]
19
+ end
20
+
21
+ #: (Zeitwerk::Cref) -> void
22
+ def unregister(cref)
23
+ @inceptions.delete(cref)
24
+ end
25
+
26
+ #: () -> void
27
+ def clear # for tests
28
+ @inceptions.clear
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,33 @@
1
+ module Zeitwerk::Registry
2
+ class Loaders # :nodoc:
3
+ #: () -> void
4
+ def initialize
5
+ @loaders = [] #: Array[Zeitwerk::Loader]
6
+ end
7
+
8
+ #: ({ (Zeitwerk::Loader) -> void }) -> void
9
+ def each(&block)
10
+ @loaders.each(&block)
11
+ end
12
+
13
+ #: (Zeitwerk::Loader) -> void
14
+ def register(loader)
15
+ @loaders << loader
16
+ end
17
+
18
+ #: (Zeitwerk::Loader) -> Zeitwerk::Loader?
19
+ def unregister(loader)
20
+ @loaders.delete(loader)
21
+ end
22
+
23
+ #: (Zeitwerk::Loader) -> bool
24
+ def registered?(loader) # for tests
25
+ @loaders.include?(loader)
26
+ end
27
+
28
+ #: () -> void
29
+ def clear # for tests
30
+ @loaders.clear
31
+ end
32
+ end
33
+ end
@@ -2,18 +2,23 @@
2
2
 
3
3
  module Zeitwerk
4
4
  module Registry # :nodoc: all
5
+ require_relative "registry/autoloads"
6
+ require_relative "registry/explicit_namespaces"
7
+ require_relative "registry/inceptions"
8
+ require_relative "registry/loaders"
9
+
5
10
  class << self
6
11
  # Keeps track of all loaders. Useful to broadcast messages and to prevent
7
12
  # them from being garbage collected.
8
13
  #
9
14
  # @private
10
- # @sig Array[Zeitwerk::Loader]
15
+ #: Zeitwerk::Registry::Loaders
11
16
  attr_reader :loaders
12
17
 
13
18
  # Registers gem loaders to let `for_gem` be idempotent in case of reload.
14
19
  #
15
20
  # @private
16
- # @sig Hash[String, Zeitwerk::Loader]
21
+ #: Hash[String, Zeitwerk::Loader]
17
22
  attr_reader :gem_loaders_by_root_file
18
23
 
19
24
  # Maps absolute paths to the loaders responsible for them.
@@ -22,117 +27,37 @@ module Zeitwerk
22
27
  # invoke callbacks and autovivify modules.
23
28
  #
24
29
  # @private
25
- # @sig Hash[String, Zeitwerk::Loader]
30
+ #: Zeitwerk::Registry::Autoloads
26
31
  attr_reader :autoloads
27
32
 
28
- # This hash table addresses an edge case in which an autoload is ignored.
29
- #
30
- # For example, let's suppose we want to autoload in a gem like this:
31
- #
32
- # # lib/my_gem.rb
33
- # loader = Zeitwerk::Loader.new
34
- # loader.push_dir(__dir__)
35
- # loader.setup
36
- #
37
- # module MyGem
38
- # end
39
- #
40
- # if you require "my_gem", as Bundler would do, this happens while setting
41
- # up autoloads:
42
- #
43
- # 1. Object.autoload?(:MyGem) returns `nil` because the autoload for
44
- # the constant is issued by Zeitwerk while the same file is being
45
- # required.
46
- # 2. The constant `MyGem` is undefined while setup runs.
47
- #
48
- # Therefore, a directory `lib/my_gem` would autovivify a module according to
49
- # the existing information. But that would be wrong.
50
- #
51
- # To overcome this fundamental limitation, we keep track of the constant
52
- # paths that are in this situation ---in the example above, "MyGem"--- and
53
- # take this collection into account for the autovivification logic.
54
- #
55
- # Note that you cannot generally address this by moving the setup code
56
- # below the constant definition, because we want libraries to be able to
57
- # use managed constants in the module body:
58
- #
59
- # module MyGem
60
- # include MyConcern
61
- # end
62
- #
63
33
  # @private
64
- # @sig Hash[String, [String, Zeitwerk::Loader]]
65
- attr_reader :inceptions
34
+ #: Zeitwerk::Registry::ExplicitNamespaces
35
+ attr_reader :explicit_namespaces
66
36
 
67
- # Registers a loader.
68
- #
69
37
  # @private
70
- # @sig (Zeitwerk::Loader) -> void
71
- def register_loader(loader)
72
- loaders << loader
73
- end
38
+ #: Zeitwerk::Registry::Inceptions
39
+ attr_reader :inceptions
74
40
 
75
41
  # @private
76
- # @sig (Zeitwerk::Loader) -> void
42
+ #: (Zeitwerk::Loader) -> void
77
43
  def unregister_loader(loader)
78
- loaders.delete(loader)
79
44
  gem_loaders_by_root_file.delete_if { |_, l| l == loader }
80
- autoloads.delete_if { |_, l| l == loader }
81
- inceptions.delete_if { |_, (_, l)| l == loader }
82
45
  end
83
46
 
84
47
  # This method returns always a loader, the same instance for the same root
85
48
  # file. That is how Zeitwerk::Loader.for_gem is idempotent.
86
49
  #
87
50
  # @private
88
- # @sig (String) -> Zeitwerk::Loader
51
+ #: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::Loader
89
52
  def loader_for_gem(root_file, namespace:, warn_on_extra_files:)
90
53
  gem_loaders_by_root_file[root_file] ||= GemLoader.__new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
91
54
  end
92
-
93
- # @private
94
- # @sig (Zeitwerk::Loader, String) -> String
95
- def register_autoload(loader, abspath)
96
- autoloads[abspath] = loader
97
- end
98
-
99
- # @private
100
- # @sig (String) -> void
101
- def unregister_autoload(abspath)
102
- autoloads.delete(abspath)
103
- end
104
-
105
- # @private
106
- # @sig (String, String, Zeitwerk::Loader) -> void
107
- def register_inception(cpath, abspath, loader)
108
- inceptions[cpath] = [abspath, loader]
109
- end
110
-
111
- # @private
112
- # @sig (String) -> String?
113
- def inception?(cpath)
114
- if pair = inceptions[cpath]
115
- pair.first
116
- end
117
- end
118
-
119
- # @private
120
- # @sig (String) -> Zeitwerk::Loader?
121
- def loader_for(path)
122
- autoloads[path]
123
- end
124
-
125
- # @private
126
- # @sig (Zeitwerk::Loader) -> void
127
- def on_unload(loader)
128
- autoloads.delete_if { |_path, object| object == loader }
129
- inceptions.delete_if { |_cpath, (_path, object)| object == loader }
130
- end
131
55
  end
132
56
 
133
- @loaders = []
57
+ @loaders = Loaders.new
134
58
  @gem_loaders_by_root_file = {}
135
- @autoloads = {}
136
- @inceptions = {}
59
+ @autoloads = Autoloads.new
60
+ @explicit_namespaces = ExplicitNamespaces.new
61
+ @inceptions = Inceptions.new
137
62
  end
138
63
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.16"
4
+ #: String
5
+ VERSION = "2.7.3"
5
6
  end
data/lib/zeitwerk.rb CHANGED
@@ -7,18 +7,19 @@ module Zeitwerk
7
7
  require_relative "zeitwerk/loader"
8
8
  require_relative "zeitwerk/gem_loader"
9
9
  require_relative "zeitwerk/registry"
10
- require_relative "zeitwerk/explicit_namespace"
11
10
  require_relative "zeitwerk/inflector"
12
11
  require_relative "zeitwerk/gem_inflector"
13
12
  require_relative "zeitwerk/null_inflector"
14
- require_relative "zeitwerk/kernel"
15
13
  require_relative "zeitwerk/error"
16
14
  require_relative "zeitwerk/version"
17
15
 
16
+ require_relative "zeitwerk/core_ext/kernel"
17
+ require_relative "zeitwerk/core_ext/module"
18
+
18
19
  # This is a dangerous method.
19
20
  #
20
21
  # @experimental
21
- # @sig () -> void
22
+ #: () -> void
22
23
  def self.with_loader
23
24
  loader = Zeitwerk::Loader.new
24
25
  yield loader
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeitwerk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.16
4
+ version: 2.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-06-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |2
14
13
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -23,14 +22,15 @@ files:
23
22
  - MIT-LICENSE
24
23
  - README.md
25
24
  - lib/zeitwerk.rb
25
+ - lib/zeitwerk/core_ext/kernel.rb
26
+ - lib/zeitwerk/core_ext/module.rb
26
27
  - lib/zeitwerk/cref.rb
28
+ - lib/zeitwerk/cref/map.rb
27
29
  - lib/zeitwerk/error.rb
28
- - lib/zeitwerk/explicit_namespace.rb
29
30
  - lib/zeitwerk/gem_inflector.rb
30
31
  - lib/zeitwerk/gem_loader.rb
31
32
  - lib/zeitwerk/inflector.rb
32
33
  - lib/zeitwerk/internal.rb
33
- - lib/zeitwerk/kernel.rb
34
34
  - lib/zeitwerk/loader.rb
35
35
  - lib/zeitwerk/loader/callbacks.rb
36
36
  - lib/zeitwerk/loader/config.rb
@@ -39,6 +39,10 @@ files:
39
39
  - lib/zeitwerk/null_inflector.rb
40
40
  - lib/zeitwerk/real_mod_name.rb
41
41
  - lib/zeitwerk/registry.rb
42
+ - lib/zeitwerk/registry/autoloads.rb
43
+ - lib/zeitwerk/registry/explicit_namespaces.rb
44
+ - lib/zeitwerk/registry/inceptions.rb
45
+ - lib/zeitwerk/registry/loaders.rb
42
46
  - lib/zeitwerk/version.rb
43
47
  homepage: https://github.com/fxn/zeitwerk
44
48
  licenses:
@@ -48,7 +52,6 @@ metadata:
48
52
  changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
49
53
  source_code_uri: https://github.com/fxn/zeitwerk
50
54
  bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
51
- post_install_message:
52
55
  rdoc_options: []
53
56
  require_paths:
54
57
  - lib
@@ -56,15 +59,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
59
  requirements:
57
60
  - - ">="
58
61
  - !ruby/object:Gem::Version
59
- version: '2.5'
62
+ version: '3.2'
60
63
  required_rubygems_version: !ruby/object:Gem::Requirement
61
64
  requirements:
62
65
  - - ">="
63
66
  - !ruby/object:Gem::Version
64
67
  version: '0'
65
68
  requirements: []
66
- rubygems_version: 3.5.10
67
- signing_key:
69
+ rubygems_version: 3.6.9
68
70
  specification_version: 4
69
71
  summary: Efficient and thread-safe constant autoloader
70
72
  test_files: []
@@ -1,93 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Zeitwerk
4
- # Centralizes the logic for the trace point used to detect the creation of
5
- # explicit namespaces, needed to descend into matching subdirectories right
6
- # after the constant has been defined.
7
- #
8
- # The implementation assumes an explicit namespace is managed by one loader.
9
- # Loaders that reopen namespaces owned by other projects are responsible for
10
- # loading their constant before setup. This is documented.
11
- module ExplicitNamespace # :nodoc: all
12
- class << self
13
- include RealModName
14
- extend Internal
15
-
16
- # Maps constant paths that correspond to explicit namespaces according to
17
- # the file system, to the loader responsible for them.
18
- #
19
- # @sig Hash[String, Zeitwerk::Loader]
20
- attr_reader :cpaths
21
- private :cpaths
22
-
23
- # @sig Mutex
24
- attr_reader :mutex
25
- private :mutex
26
-
27
- # @sig TracePoint
28
- attr_reader :tracer
29
- private :tracer
30
-
31
- # Asserts `cpath` corresponds to an explicit namespace for which `loader`
32
- # is responsible.
33
- #
34
- # @sig (String, Zeitwerk::Loader) -> void
35
- internal def register(cpath, loader)
36
- mutex.synchronize do
37
- cpaths[cpath] = loader
38
- # We check enabled? because, looking at the C source code, enabling an
39
- # enabled tracer does not seem to be a simple no-op.
40
- tracer.enable unless tracer.enabled?
41
- end
42
- end
43
-
44
- # @sig (Zeitwerk::Loader) -> void
45
- internal def unregister_loader(loader)
46
- cpaths.delete_if { |_cpath, l| l == loader }
47
- disable_tracer_if_unneeded
48
- end
49
-
50
- # This is an internal method only used by the test suite.
51
- #
52
- # @sig (String) -> bool
53
- internal def registered?(cpath)
54
- cpaths.key?(cpath)
55
- end
56
-
57
- # @sig () -> void
58
- private def disable_tracer_if_unneeded
59
- mutex.synchronize do
60
- tracer.disable if cpaths.empty?
61
- end
62
- end
63
-
64
- # @sig (TracePoint) -> void
65
- private def tracepoint_class_callback(event)
66
- # If the class is a singleton class, we won't do anything with it so we
67
- # can bail out immediately. This is several orders of magnitude faster
68
- # than accessing its name.
69
- return if event.self.singleton_class?
70
-
71
- # It might be tempting to return if name.nil?, to avoid the computation
72
- # of a hash code and delete call. But Ruby does not trigger the :class
73
- # event on Class.new or Module.new, so that would incur in an extra call
74
- # for nothing.
75
- #
76
- # On the other hand, if we were called, cpaths is not empty. Otherwise
77
- # the tracer is disabled. So we do need to go ahead with the hash code
78
- # computation and delete call.
79
- if loader = cpaths.delete(real_mod_name(event.self))
80
- loader.on_namespace_loaded(event.self)
81
- disable_tracer_if_unneeded
82
- end
83
- end
84
- end
85
-
86
- @cpaths = {}
87
- @mutex = Mutex.new
88
-
89
- # We go through a method instead of defining a block mainly to have a better
90
- # label when profiling.
91
- @tracer = TracePoint.new(:class, &method(:tracepoint_class_callback))
92
- end
93
- end