zeitwerk 2.7.2 → 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.
- checksums.yaml +4 -4
- data/lib/zeitwerk/core_ext/kernel.rb +3 -3
- data/lib/zeitwerk/core_ext/module.rb +4 -4
- data/lib/zeitwerk/cref/map.rb +62 -27
- data/lib/zeitwerk/cref.rb +14 -16
- data/lib/zeitwerk/error.rb +2 -0
- data/lib/zeitwerk/gem_inflector.rb +2 -2
- data/lib/zeitwerk/gem_loader.rb +4 -4
- data/lib/zeitwerk/inflector.rb +3 -3
- data/lib/zeitwerk/internal.rb +1 -1
- data/lib/zeitwerk/loader/callbacks.rb +7 -9
- data/lib/zeitwerk/loader/config.rb +37 -44
- data/lib/zeitwerk/loader/eager_load.rb +7 -7
- data/lib/zeitwerk/loader/helpers.rb +9 -10
- data/lib/zeitwerk/loader.rb +57 -52
- data/lib/zeitwerk/null_inflector.rb +1 -1
- data/lib/zeitwerk/real_mod_name.rb +7 -4
- data/lib/zeitwerk/registry/autoloads.rb +38 -0
- data/lib/zeitwerk/registry/explicit_namespaces.rb +36 -39
- data/lib/zeitwerk/registry/inceptions.rb +20 -20
- data/lib/zeitwerk/registry/loaders.rb +33 -0
- data/lib/zeitwerk/registry.rb +17 -39
- data/lib/zeitwerk/version.rb +2 -1
- data/lib/zeitwerk.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1500c4f54c6ac7e64eef74c8702682764fa845533a5b16c67a48ee11781ef3e0
|
4
|
+
data.tar.gz: f7201576b8b59ab3786cd4bc6fd58f3cd3afa3d8dcc1e86477121000f06ac50a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab113d90c9a42ec5c75c6ebe68ab5a04395f1b33228de42de6952f2e673a329f30de85477977ec11d582df82f40c4dc5c08e8ba2305f46a6d07ac4e88c564887
|
7
|
+
data.tar.gz: 73fb9dc78a9a7148119b306f93a513f841e4888d421fbf178963bbfa1368a8c7c66cd8661a28e8d95d14f51dab16fb4ff886f23183ec13a74847c45c1850f30d
|
@@ -19,9 +19,9 @@ module Kernel
|
|
19
19
|
alias_method :zeitwerk_original_require, :require
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
#: (String) -> bool
|
23
23
|
def require(path)
|
24
|
-
if loader = Zeitwerk::Registry.
|
24
|
+
if loader = Zeitwerk::Registry.autoloads.registered?(path)
|
25
25
|
if path.end_with?(".rb")
|
26
26
|
required = zeitwerk_original_require(path)
|
27
27
|
loader.__on_file_autoloaded(path) if required
|
@@ -34,7 +34,7 @@ module Kernel
|
|
34
34
|
required = zeitwerk_original_require(path)
|
35
35
|
if required
|
36
36
|
abspath = $LOADED_FEATURES.last
|
37
|
-
if loader = Zeitwerk::Registry.
|
37
|
+
if loader = Zeitwerk::Registry.autoloads.registered?(abspath)
|
38
38
|
loader.__on_file_autoloaded(abspath)
|
39
39
|
end
|
40
40
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Zeitwerk::ConstAdded # :nodoc:
|
4
|
-
|
4
|
+
#: (Symbol) -> void
|
5
5
|
def const_added(cname)
|
6
|
-
if loader = Zeitwerk::Registry
|
6
|
+
if loader = Zeitwerk::Registry.explicit_namespaces.loader_for(self, cname)
|
7
7
|
namespace = const_get(cname, false)
|
8
|
+
cref = Zeitwerk::Cref.new(self, cname)
|
8
9
|
|
9
10
|
unless namespace.is_a?(Module)
|
10
|
-
cref = Zeitwerk::Cref.new(self, cname)
|
11
11
|
raise Zeitwerk::Error, "#{cref} is expected to be a namespace, should be a class or module (got #{namespace.class})"
|
12
12
|
end
|
13
13
|
|
14
|
-
loader.__on_namespace_loaded(
|
14
|
+
loader.__on_namespace_loaded(cref, namespace)
|
15
15
|
end
|
16
16
|
super
|
17
17
|
end
|
data/lib/zeitwerk/cref/map.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Description of the structure
|
4
|
+
# ----------------------------
|
5
|
+
#
|
3
6
|
# This class emulates a hash table whose keys are of type Zeitwerk::Cref.
|
4
7
|
#
|
5
|
-
# It is a synchronized 2-level hash.
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
8
|
+
# It is a synchronized 2-level hash.
|
9
|
+
#
|
10
|
+
# The keys of the top one, stored in `@map`, are class and module objects, but
|
11
|
+
# their hash code is forced to be their object IDs because class and module
|
12
|
+
# objects may not be hashable (https://github.com/fxn/zeitwerk/issues/188).
|
13
|
+
#
|
14
|
+
# Then, each one of them stores a hash table with their constants and values.
|
15
|
+
# Constants are stored as symbols.
|
9
16
|
#
|
10
17
|
# For example, if we store values 0, 1, and 2 for the crefs that would
|
11
18
|
# correspond to `M::X`, `M::Y`, and `N::Z`, the map will look like this:
|
@@ -14,36 +21,64 @@
|
|
14
21
|
#
|
15
22
|
# This structure is internal, so only the needed interface is implemented.
|
16
23
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
24
|
+
# Alternative approaches
|
25
|
+
# -----------------------
|
26
|
+
#
|
27
|
+
# 1. We could also use a 1-level hash whose keys are constant paths. In the
|
28
|
+
# example above it would be:
|
29
|
+
#
|
30
|
+
# { "M::X" => 0, "M::Y" => 1, "N::Z" => 2 }
|
31
|
+
#
|
32
|
+
# The gem used this approach for several years.
|
20
33
|
#
|
21
|
-
#
|
34
|
+
# 2. Write a custom `hash`/`eql?` in Zeitwerk::Cref. Hash code would be
|
22
35
|
#
|
23
|
-
#
|
24
|
-
# module names. In the example above it would be:
|
36
|
+
# real_mod_hash(@mod) ^ @cname.hash
|
25
37
|
#
|
26
|
-
#
|
38
|
+
# where `real_mod_hash(@mod)` would actually be a call to the real `hash`
|
39
|
+
# method in Module. Like what we do for module names to bypass overrides.
|
27
40
|
#
|
28
|
-
#
|
41
|
+
# 3. Similar to 2, but use
|
29
42
|
#
|
30
|
-
#
|
43
|
+
# @mod.object_id ^ @cname.object_id
|
31
44
|
#
|
32
|
-
#
|
45
|
+
# as hash code instead.
|
33
46
|
#
|
34
|
-
#
|
47
|
+
# Benchamrks
|
48
|
+
# ----------
|
35
49
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
50
|
+
# Writing:
|
51
|
+
#
|
52
|
+
# map - baseline
|
53
|
+
# (3) - 1.74x slower
|
54
|
+
# (2) - 2.91x slower
|
55
|
+
# (1) - 3.87x slower
|
56
|
+
#
|
57
|
+
# Reading:
|
58
|
+
#
|
59
|
+
# map - baseline
|
60
|
+
# (3) - 1.99x slower
|
61
|
+
# (2) - 2.80x slower
|
62
|
+
# (1) - 3.48x slower
|
63
|
+
#
|
64
|
+
# Extra ball
|
65
|
+
# ----------
|
66
|
+
#
|
67
|
+
# In addition to that, the map is synchronized and provides `delete_mod_cname`,
|
68
|
+
# which is ad-hoc for the hot path in `const_added`, we do not need to create
|
69
|
+
# unnecessary cref objects for constants we do not manage (but we do not know in
|
70
|
+
# advance there).
|
71
|
+
|
72
|
+
#: [Value]
|
39
73
|
class Zeitwerk::Cref::Map # :nodoc: all
|
74
|
+
#: () -> void
|
40
75
|
def initialize
|
41
76
|
@map = {}
|
42
77
|
@map.compare_by_identity
|
43
78
|
@mutex = Mutex.new
|
44
79
|
end
|
45
80
|
|
46
|
-
|
81
|
+
#: (Zeitwerk::Cref, Value) -> Value
|
47
82
|
def []=(cref, value)
|
48
83
|
@mutex.synchronize do
|
49
84
|
cnames = (@map[cref.mod] ||= {})
|
@@ -51,14 +86,14 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
51
86
|
end
|
52
87
|
end
|
53
88
|
|
54
|
-
|
89
|
+
#: (Zeitwerk::Cref) -> Value?
|
55
90
|
def [](cref)
|
56
91
|
@mutex.synchronize do
|
57
92
|
@map[cref.mod]&.[](cref.cname)
|
58
93
|
end
|
59
94
|
end
|
60
95
|
|
61
|
-
|
96
|
+
#: (Zeitwerk::Cref, { () -> Value }) -> Value
|
62
97
|
def get_or_set(cref, &block)
|
63
98
|
@mutex.synchronize do
|
64
99
|
cnames = (@map[cref.mod] ||= {})
|
@@ -66,7 +101,7 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
66
101
|
end
|
67
102
|
end
|
68
103
|
|
69
|
-
|
104
|
+
#: (Zeitwerk::Cref) -> Value?
|
70
105
|
def delete(cref)
|
71
106
|
delete_mod_cname(cref.mod, cref.cname)
|
72
107
|
end
|
@@ -74,7 +109,7 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
74
109
|
# Ad-hoc for loader_for, called from const_added. That is a hot path, I prefer
|
75
110
|
# to not create a cref in every call, since that is global.
|
76
111
|
#
|
77
|
-
|
112
|
+
#: (Module, Symbol) -> Value?
|
78
113
|
def delete_mod_cname(mod, cname)
|
79
114
|
@mutex.synchronize do
|
80
115
|
if cnames = @map[mod]
|
@@ -85,7 +120,7 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
85
120
|
end
|
86
121
|
end
|
87
122
|
|
88
|
-
|
123
|
+
#: (Value) -> void
|
89
124
|
def delete_by_value(value)
|
90
125
|
@mutex.synchronize do
|
91
126
|
@map.delete_if do |mod, cnames|
|
@@ -97,7 +132,7 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
97
132
|
|
98
133
|
# Order of yielded crefs is undefined.
|
99
134
|
#
|
100
|
-
|
135
|
+
#: () { (Zeitwerk::Cref) -> void } -> void
|
101
136
|
def each_key
|
102
137
|
@mutex.synchronize do
|
103
138
|
@map.each do |mod, cnames|
|
@@ -108,14 +143,14 @@ class Zeitwerk::Cref::Map # :nodoc: all
|
|
108
143
|
end
|
109
144
|
end
|
110
145
|
|
111
|
-
|
146
|
+
#: () -> void
|
112
147
|
def clear
|
113
148
|
@mutex.synchronize do
|
114
149
|
@map.clear
|
115
150
|
end
|
116
151
|
end
|
117
152
|
|
118
|
-
|
153
|
+
#: () -> bool
|
119
154
|
def empty? # for tests
|
120
155
|
@mutex.synchronize do
|
121
156
|
@map.empty?
|
data/lib/zeitwerk/cref.rb
CHANGED
@@ -2,69 +2,67 @@
|
|
2
2
|
|
3
3
|
# This private class encapsulates pairs (mod, cname).
|
4
4
|
#
|
5
|
-
# Objects represent the constant cname in the class or module object mod
|
6
|
-
# have API to manage them. Examples:
|
5
|
+
# Objects represent the constant `cname` in the class or module object `mod`,
|
6
|
+
# and have API to manage them. Examples:
|
7
7
|
#
|
8
8
|
# cref.path
|
9
9
|
# cref.set(value)
|
10
10
|
# cref.get
|
11
11
|
#
|
12
|
-
# The constant may or may not exist in mod
|
12
|
+
# The constant may or may not exist in `mod`.
|
13
13
|
class Zeitwerk::Cref
|
14
14
|
require_relative "cref/map"
|
15
15
|
|
16
16
|
include Zeitwerk::RealModName
|
17
17
|
|
18
|
-
|
18
|
+
#: Module
|
19
19
|
attr_reader :mod
|
20
20
|
|
21
|
-
|
21
|
+
#: Symbol
|
22
22
|
attr_reader :cname
|
23
23
|
|
24
24
|
# The type of the first argument is Module because Class < Module, class
|
25
25
|
# objects are also valid.
|
26
26
|
#
|
27
|
-
|
27
|
+
#: (Module, Symbol) -> void
|
28
28
|
def initialize(mod, cname)
|
29
29
|
@mod = mod
|
30
30
|
@cname = cname
|
31
31
|
@path = nil
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
#: () -> String
|
35
35
|
def path
|
36
|
-
@path ||= Object
|
36
|
+
@path ||= Object == @mod ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}".freeze
|
37
37
|
end
|
38
38
|
alias to_s path
|
39
39
|
|
40
|
-
|
40
|
+
#: () -> String?
|
41
41
|
def autoload?
|
42
42
|
@mod.autoload?(@cname, false)
|
43
43
|
end
|
44
44
|
|
45
|
-
|
45
|
+
#: (String) -> nil
|
46
46
|
def autoload(abspath)
|
47
47
|
@mod.autoload(@cname, abspath)
|
48
48
|
end
|
49
49
|
|
50
|
-
|
50
|
+
#: () -> bool
|
51
51
|
def defined?
|
52
52
|
@mod.const_defined?(@cname, false)
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
#: (top) -> top
|
56
56
|
def set(value)
|
57
57
|
@mod.const_set(@cname, value)
|
58
58
|
end
|
59
59
|
|
60
|
-
|
61
|
-
# @sig () -> top
|
60
|
+
#: () -> top ! NameError
|
62
61
|
def get
|
63
62
|
@mod.const_get(@cname, false)
|
64
63
|
end
|
65
64
|
|
66
|
-
|
67
|
-
# @sig () -> void
|
65
|
+
#: () -> void ! NameError
|
68
66
|
def remove
|
69
67
|
@mod.__send__(:remove_const, @cname)
|
70
68
|
end
|
data/lib/zeitwerk/error.rb
CHANGED
@@ -5,6 +5,7 @@ module Zeitwerk
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class ReloadingDisabledError < Error
|
8
|
+
#: () -> void
|
8
9
|
def initialize
|
9
10
|
super("can't reload, please call loader.enable_reloading before setup")
|
10
11
|
end
|
@@ -14,6 +15,7 @@ module Zeitwerk
|
|
14
15
|
end
|
15
16
|
|
16
17
|
class SetupRequired < Error
|
18
|
+
#: () -> void
|
17
19
|
def initialize
|
18
20
|
super("please, finish your configuration and call Zeitwerk::Loader#setup once all is ready")
|
19
21
|
end
|
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
module Zeitwerk
|
4
4
|
class GemInflector < Inflector
|
5
|
-
|
5
|
+
#: (String) -> void
|
6
6
|
def initialize(root_file)
|
7
7
|
namespace = File.basename(root_file, ".rb")
|
8
8
|
root_dir = File.dirname(root_file)
|
9
9
|
@version_file = File.join(root_dir, namespace, "version.rb")
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
#: (String, String) -> String
|
13
13
|
def camelize(basename, abspath)
|
14
14
|
abspath == @version_file ? "VERSION" : super
|
15
15
|
end
|
data/lib/zeitwerk/gem_loader.rb
CHANGED
@@ -10,12 +10,12 @@ module Zeitwerk
|
|
10
10
|
private_class_method :new
|
11
11
|
|
12
12
|
# @private
|
13
|
-
|
13
|
+
#: (String, namespace: Module, warn_on_extra_files: boolish) -> Zeitwerk::GemLoader
|
14
14
|
def self.__new(root_file, namespace:, warn_on_extra_files:)
|
15
15
|
new(root_file, namespace: namespace, warn_on_extra_files: warn_on_extra_files)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
#: (String, namespace: Module, warn_on_extra_files: boolish) -> void
|
19
19
|
def initialize(root_file, namespace:, warn_on_extra_files:)
|
20
20
|
super()
|
21
21
|
|
@@ -30,7 +30,7 @@ module Zeitwerk
|
|
30
30
|
push_dir(@root_dir, namespace: namespace)
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
#: () -> void
|
34
34
|
def setup
|
35
35
|
warn_on_extra_files if @warn_on_extra_files
|
36
36
|
super
|
@@ -38,7 +38,7 @@ module Zeitwerk
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
|
41
|
+
#: () -> void
|
42
42
|
def warn_on_extra_files
|
43
43
|
expected_namespace_dir = @root_file.delete_suffix(".rb")
|
44
44
|
|
data/lib/zeitwerk/inflector.rb
CHANGED
@@ -11,7 +11,7 @@ module Zeitwerk
|
|
11
11
|
#
|
12
12
|
# Takes into account hard-coded mappings configured with `inflect`.
|
13
13
|
#
|
14
|
-
|
14
|
+
#: (String, String) -> String
|
15
15
|
def camelize(basename, _abspath)
|
16
16
|
overrides[basename] || basename.split('_').each(&:capitalize!).join
|
17
17
|
end
|
@@ -28,7 +28,7 @@ module Zeitwerk
|
|
28
28
|
# inflector.camelize("mysql_adapter", abspath) # => "MySQLAdapter"
|
29
29
|
# inflector.camelize("users_controller", abspath) # => "UsersController"
|
30
30
|
#
|
31
|
-
|
31
|
+
#: (Hash[String, String]) -> void
|
32
32
|
def inflect(inflections)
|
33
33
|
overrides.merge!(inflections)
|
34
34
|
end
|
@@ -38,7 +38,7 @@ module Zeitwerk
|
|
38
38
|
# Hard-coded basename to constant name user maps that override the default
|
39
39
|
# inflection logic.
|
40
40
|
#
|
41
|
-
|
41
|
+
#: () -> Hash[String, String]
|
42
42
|
def overrides
|
43
43
|
@overrides ||= {}
|
44
44
|
end
|
data/lib/zeitwerk/internal.rb
CHANGED
@@ -5,12 +5,11 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
5
5
|
|
6
6
|
# Invoked from our decorated Kernel#require when a managed file is autoloaded.
|
7
7
|
#
|
8
|
-
|
9
|
-
# @sig (String) -> void
|
8
|
+
#: (String) -> void ! Zeitwerk::NameError
|
10
9
|
internal def on_file_autoloaded(file)
|
11
10
|
cref = autoloads.delete(file)
|
12
11
|
|
13
|
-
Zeitwerk::Registry.
|
12
|
+
Zeitwerk::Registry.autoloads.unregister(file)
|
14
13
|
|
15
14
|
if cref.defined?
|
16
15
|
log("constant #{cref} loaded from file #{file}") if logger
|
@@ -36,7 +35,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
36
35
|
# Invoked from our decorated Kernel#require when a managed directory is
|
37
36
|
# autoloaded.
|
38
37
|
#
|
39
|
-
|
38
|
+
#: (String) -> void
|
40
39
|
internal def on_dir_autoloaded(dir)
|
41
40
|
# Module#autoload does not serialize concurrent requires in CRuby < 3.2, and
|
42
41
|
# we handle directories ourselves without going through Kernel#require, so
|
@@ -53,8 +52,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
53
52
|
dirs_autoload_monitor.synchronize do
|
54
53
|
if cref = autoloads.delete(dir)
|
55
54
|
implicit_namespace = cref.set(Module.new)
|
56
|
-
|
57
|
-
log("module #{cpath} autovivified from directory #{dir}") if logger
|
55
|
+
log("module #{cref} autovivified from directory #{dir}") if logger
|
58
56
|
|
59
57
|
to_unload[dir] = cref if reloading_enabled?
|
60
58
|
|
@@ -66,7 +64,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
66
64
|
|
67
65
|
on_namespace_loaded(cref, implicit_namespace)
|
68
66
|
|
69
|
-
run_on_load_callbacks(
|
67
|
+
run_on_load_callbacks(cref.path, implicit_namespace, dir) unless on_load_callbacks.empty?
|
70
68
|
end
|
71
69
|
end
|
72
70
|
end
|
@@ -75,7 +73,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
75
73
|
# autovivification. If the namespace has matching subdirectories, we descend
|
76
74
|
# into them now.
|
77
75
|
#
|
78
|
-
|
76
|
+
#: (Zeitwerk::Cref, Module) -> void
|
79
77
|
internal def on_namespace_loaded(cref, namespace)
|
80
78
|
if dirs = namespace_dirs.delete(cref)
|
81
79
|
dirs.each do |dir|
|
@@ -86,7 +84,7 @@ module Zeitwerk::Loader::Callbacks # :nodoc: all
|
|
86
84
|
|
87
85
|
private
|
88
86
|
|
89
|
-
|
87
|
+
#: (String, top, String) -> void
|
90
88
|
def run_on_load_callbacks(cpath, value, abspath)
|
91
89
|
# Order matters. If present, run the most specific one.
|
92
90
|
callbacks = reloading_enabled? ? on_load_callbacks[cpath] : on_load_callbacks.delete(cpath)
|