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.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk::Registry
4
+ # This module is a registry for explicit namespaces.
5
+ #
6
+ # When a loader determines that a certain file should define an explicit
7
+ # namespace, it registers it here, associating its cref with itself.
8
+ #
9
+ # If the namespace is autoloaded, our const_added callback retrieves its
10
+ # loader by calling loader_for. That way, the loader is able to scan the
11
+ # subdirectories that conform the namespace and set autoloads for their
12
+ # expected constants just in time.
13
+ #
14
+ # Once autoloaded, the namespace is unregistered.
15
+ #
16
+ # The implementation assumes an explicit namespace is managed by one loader.
17
+ # Loaders that reopen namespaces owned by other projects are responsible for
18
+ # loading their constant before setup. This is documented.
19
+ module ExplicitNamespaces # :nodoc: all
20
+ # Maps crefs of explicit namespaces with their corresponding loader.
21
+ #
22
+ # Entries are added as the namespaces are found, and removed as they are
23
+ # autoloaded.
24
+ #
25
+ # @sig Zeitwerk::Cref::Map[Zeitwerk::Loader]
26
+ @loaders = Zeitwerk::Cref::Map.new
27
+
28
+ class << self
29
+ extend Zeitwerk::Internal
30
+
31
+ # Registers `cref` as being the constant path of an explicit namespace
32
+ # managed by `loader`.
33
+ #
34
+ # @sig (Zeitwerk::Cref, Zeitwerk::Loader) -> void
35
+ internal def register(cref, loader)
36
+ @loaders[cref] = loader
37
+ end
38
+
39
+ # @sig (Module, Symbol) -> Zeitwerk::Loader?
40
+ internal def loader_for(mod, cname)
41
+ @loaders.delete_mod_cname(mod, cname)
42
+ end
43
+
44
+ # @sig (Zeitwerk::Loader) -> void
45
+ internal def unregister_loader(loader)
46
+ @loaders.delete_by_value(loader)
47
+ end
48
+
49
+ # This is an internal method only used by the test suite.
50
+ #
51
+ # @sig (Symbol | String) -> Zeitwerk::Loader?
52
+ internal def registered?(cref)
53
+ @loaders[cref]
54
+ end
55
+
56
+ # This is an internal method only used by the test suite.
57
+ #
58
+ # @sig () -> void
59
+ internal def clear
60
+ @loaders.clear
61
+ end
62
+ end
63
+ end
64
+ end
@@ -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
+ module Inceptions # :nodoc: all
6
+ # @sig Zeitwerk::Cref::Map[String]
7
+ @inceptions = Zeitwerk::Cref::Map.new
8
+
9
+ class << self
10
+ # @sig (Zeitwerk::Cref, String) -> void
11
+ def register(cref, autoload_path)
12
+ @inceptions[cref] = autoload_path
13
+ end
14
+
15
+ # @sig (String) -> String?
16
+ def registered?(cref)
17
+ @inceptions[cref]
18
+ end
19
+
20
+ # @sig (String) -> void
21
+ def unregister(cref)
22
+ @inceptions.delete(cref)
23
+ end
24
+
25
+ # @sig () -> void
26
+ def clear # for tests
27
+ @inceptions.clear
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,6 +2,9 @@
2
2
 
3
3
  module Zeitwerk
4
4
  module Registry # :nodoc: all
5
+ require_relative "registry/explicit_namespaces"
6
+ require_relative "registry/inceptions"
7
+
5
8
  class << self
6
9
  # Keeps track of all loaders. Useful to broadcast messages and to prevent
7
10
  # them from being garbage collected.
@@ -25,45 +28,6 @@ module Zeitwerk
25
28
  # @sig Hash[String, Zeitwerk::Loader]
26
29
  attr_reader :autoloads
27
30
 
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
- # @private
64
- # @sig Hash[String, [String, Zeitwerk::Loader]]
65
- attr_reader :inceptions
66
-
67
31
  # Registers a loader.
68
32
  #
69
33
  # @private
@@ -78,7 +42,6 @@ module Zeitwerk
78
42
  loaders.delete(loader)
79
43
  gem_loaders_by_root_file.delete_if { |_, l| l == loader }
80
44
  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
@@ -102,20 +65,6 @@ module Zeitwerk
102
65
  autoloads.delete(abspath)
103
66
  end
104
67
 
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
68
  # @private
120
69
  # @sig (String) -> Zeitwerk::Loader?
121
70
  def loader_for(path)
@@ -126,13 +75,11 @@ module Zeitwerk
126
75
  # @sig (Zeitwerk::Loader) -> void
127
76
  def on_unload(loader)
128
77
  autoloads.delete_if { |_path, object| object == loader }
129
- inceptions.delete_if { |_cpath, (_path, object)| object == loader }
130
78
  end
131
79
  end
132
80
 
133
81
  @loaders = []
134
82
  @gem_loaders_by_root_file = {}
135
83
  @autoloads = {}
136
- @inceptions = {}
137
84
  end
138
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.6.13"
4
+ VERSION = "2.7.2"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -3,17 +3,19 @@
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
- require_relative "zeitwerk/explicit_namespace"
10
10
  require_relative "zeitwerk/inflector"
11
11
  require_relative "zeitwerk/gem_inflector"
12
12
  require_relative "zeitwerk/null_inflector"
13
- require_relative "zeitwerk/kernel"
14
13
  require_relative "zeitwerk/error"
15
14
  require_relative "zeitwerk/version"
16
15
 
16
+ require_relative "zeitwerk/core_ext/kernel"
17
+ require_relative "zeitwerk/core_ext/module"
18
+
17
19
  # This is a dangerous method.
18
20
  #
19
21
  # @experimental
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.13
4
+ version: 2.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-02-06 00:00:00.000000000 Z
10
+ date: 2025-02-18 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |2
14
13
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -23,13 +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
27
+ - lib/zeitwerk/cref.rb
28
+ - lib/zeitwerk/cref/map.rb
26
29
  - lib/zeitwerk/error.rb
27
- - lib/zeitwerk/explicit_namespace.rb
28
30
  - lib/zeitwerk/gem_inflector.rb
29
31
  - lib/zeitwerk/gem_loader.rb
30
32
  - lib/zeitwerk/inflector.rb
31
33
  - lib/zeitwerk/internal.rb
32
- - lib/zeitwerk/kernel.rb
33
34
  - lib/zeitwerk/loader.rb
34
35
  - lib/zeitwerk/loader/callbacks.rb
35
36
  - lib/zeitwerk/loader/config.rb
@@ -38,6 +39,8 @@ files:
38
39
  - lib/zeitwerk/null_inflector.rb
39
40
  - lib/zeitwerk/real_mod_name.rb
40
41
  - lib/zeitwerk/registry.rb
42
+ - lib/zeitwerk/registry/explicit_namespaces.rb
43
+ - lib/zeitwerk/registry/inceptions.rb
41
44
  - lib/zeitwerk/version.rb
42
45
  homepage: https://github.com/fxn/zeitwerk
43
46
  licenses:
@@ -47,7 +50,6 @@ metadata:
47
50
  changelog_uri: https://github.com/fxn/zeitwerk/blob/master/CHANGELOG.md
48
51
  source_code_uri: https://github.com/fxn/zeitwerk
49
52
  bug_tracker_uri: https://github.com/fxn/zeitwerk/issues
50
- post_install_message:
51
53
  rdoc_options: []
52
54
  require_paths:
53
55
  - lib
@@ -55,15 +57,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
57
  requirements:
56
58
  - - ">="
57
59
  - !ruby/object:Gem::Version
58
- version: '2.5'
60
+ version: '3.2'
59
61
  required_rubygems_version: !ruby/object:Gem::Requirement
60
62
  requirements:
61
63
  - - ">="
62
64
  - !ruby/object:Gem::Version
63
65
  version: '0'
64
66
  requirements: []
65
- rubygems_version: 3.5.5
66
- signing_key:
67
+ rubygems_version: 3.6.4
67
68
  specification_version: 4
68
69
  summary: Efficient and thread-safe constant autoloader
69
70
  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
File without changes