zeitwerk 2.6.17 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51e7aed4aef39ce0a2caaea2f23852ea4f2e8fcb7b65819d14a4d92e7aec70d0
4
- data.tar.gz: 7e310bac85d85018cb840339a42958b9fbc6fac566fee51e6a5c599a3cb132e3
3
+ metadata.gz: 79a3e282ee7602f7d40c75c1299b24de4de890ee90a22cd37ed9c7a660936074
4
+ data.tar.gz: bd60115bc776137770e16ee527e4a07e815af547d1d5de851039e0187d50eff8
5
5
  SHA512:
6
- metadata.gz: 0274e4685362f6585b9fb90291561926c8b45434ea3df71858ed99c5dfbffd54e814ed6dfcc4c6e8bfd8a9f266dbe4261bf6010856558474ad7e85045851e4cd
7
- data.tar.gz: 210f457fad164472582fc67264bed2bd981612588bd3a8cdce265b2f2c67b12d10d9d2b9abb555dead008df20d5a73955072e703305e8f55b9d4dd5877d9b693
6
+ metadata.gz: ebdab4575a6e35591d1603ed11dc31f77eeffb15c8270f54719eb0850f3641efa968bebd8f02c32330ca768b8211d9aeb972fd41e0efac710faa023a0d62a841
7
+ data.tar.gz: 6d58d0256f35d6ac6445cba6619943aa4b3974905eb28588284f14aadd27304b1f11fc46d4f0905074c8888e52eabc03e312054930b702c875856d6c0a6a0694
data/README.md CHANGED
@@ -54,7 +54,6 @@
54
54
  - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
55
55
  - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
56
56
  - [Shadowed files](#shadowed-files)
57
- - [Edge cases](#edge-cases)
58
57
  - [Beware of circular dependencies](#beware-of-circular-dependencies)
59
58
  - [Reopening third-party namespaces](#reopening-third-party-namespaces)
60
59
  - [Introspection](#introspection)
@@ -63,7 +62,6 @@
63
62
  - [`Zeitwerk::Loader#all_expected_cpaths`](#zeitwerkloaderall_expected_cpaths)
64
63
  - [Encodings](#encodings)
65
64
  - [Rules of thumb](#rules-of-thumb)
66
- - [Debuggers](#debuggers)
67
65
  - [Pronunciation](#pronunciation)
68
66
  - [Supported Ruby versions](#supported-ruby-versions)
69
67
  - [Testing](#testing)
@@ -1178,36 +1176,6 @@ file #{file} is ignored because #{constant_path} is already defined
1178
1176
 
1179
1177
  Shadowing only applies to Ruby files, namespace definition can be spread over multiple directories. And you can also reopen third-party namespaces if done [orderly](#reopening-third-party-namespaces).
1180
1178
 
1181
- <a id="markdown-edge-cases" name="edge-cases"></a>
1182
- ### Edge cases
1183
-
1184
- [Explicit namespaces](#explicit-namespaces) like `Trip` here:
1185
-
1186
- ```ruby
1187
- # trip.rb
1188
- class Trip
1189
- include Geolocation
1190
- end
1191
-
1192
- # trip/geolocation.rb
1193
- module Trip::Geolocation
1194
- ...
1195
- end
1196
- ```
1197
-
1198
- have to be defined with the `class`/`module` keywords, as in the example above.
1199
-
1200
- For technical reasons, raw constant assignment is not supported:
1201
-
1202
- ```ruby
1203
- # trip.rb
1204
- Trip = Class { ...} # NOT SUPPORTED
1205
- Trip = Struct.new { ... } # NOT SUPPORTED
1206
- Trip = Data.define { ... } # NOT SUPPORTED
1207
- ```
1208
-
1209
- This only affects explicit namespaces, those idioms work well for any other ordinary class or module.
1210
-
1211
1179
  <a id="markdown-beware-of-circular-dependencies" name="beware-of-circular-dependencies"></a>
1212
1180
  ### Beware of circular dependencies
1213
1181
 
@@ -1406,15 +1374,6 @@ The test suite passes on Windows with codepage `Windows-1252` if all the involve
1406
1374
 
1407
1375
  6. In a given process, ideally, there should be at most one loader with reloading enabled. Technically, you can have more, but it may get tricky if one refers to constants managed by the other one. Do that only if you know what you are doing.
1408
1376
 
1409
- <a id="markdown-debuggers" name="debuggers"></a>
1410
- ### Debuggers
1411
-
1412
- Zeitwerk and [debug.rb](https://github.com/ruby/debug) are fully compatible if CRuby is ≥ 3.1 (see [ruby/debug#558](https://github.com/ruby/debug/pull/558)).
1413
-
1414
- [Byebug](https://github.com/deivid-rodriguez/byebug) is compatible except for an edge case explained in [deivid-rodriguez/byebug#564](https://github.com/deivid-rodriguez/byebug/issues/564). Prior to CRuby 3.1, `debug.rb` has a similar edge incompatibility.
1415
-
1416
- [Break](https://github.com/gsamokovarov/break) is fully compatible.
1417
-
1418
1377
  <a id="markdown-pronunciation" name="pronunciation"></a>
1419
1378
  ## Pronunciation
1420
1379
 
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zeitwerk::ConstAdded
4
+ def const_added(cname)
5
+ if loader = Zeitwerk::ExplicitNamespace.__loader_for(self, cname)
6
+ loader.on_namespace_loaded(const_get(cname, false))
7
+ end
8
+ super
9
+ end
10
+
11
+ Module.prepend(self)
12
+ end
data/lib/zeitwerk/cref.rb CHANGED
@@ -26,48 +26,14 @@ class Zeitwerk::Cref
26
26
  @path = nil
27
27
  end
28
28
 
29
- if Symbol.method_defined?(:name)
30
- # Symbol#name was introduced in Ruby 3.0. It returns always the same
31
- # frozen object, so we may save a few string allocations.
32
- #
33
- # @sig () -> String
34
- def path
35
- @path ||= Object.equal?(@mod) ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}"
36
- end
37
- else
38
- # @sig () -> String
39
- def path
40
- @path ||= Object.equal?(@mod) ? @cname.to_s : "#{real_mod_name(@mod)}::#{@cname}"
41
- end
29
+ # @sig () -> String
30
+ def path
31
+ @path ||= Object.equal?(@mod) ? @cname.name : "#{real_mod_name(@mod)}::#{@cname.name}".freeze
42
32
  end
43
33
 
44
- # The autoload? predicate takes into account the ancestor chain of the
45
- # receiver, like const_defined? and other methods in the constants API do.
46
- #
47
- # For example, given
48
- #
49
- # class A
50
- # autoload :X, "x.rb"
51
- # end
52
- #
53
- # class B < A
54
- # end
55
- #
56
- # B.autoload?(:X) returns "x.rb".
57
- #
58
- # We need a way to retrieve it ignoring ancestors.
59
- #
60
34
  # @sig () -> String?
61
- if method(:autoload?).arity == 1
62
- # @sig () -> String?
63
- def autoload?
64
- @mod.autoload?(@cname) if self.defined?
65
- end
66
- else
67
- # @sig () -> String?
68
- def autoload?
69
- @mod.autoload?(@cname, false)
70
- end
35
+ def autoload?
36
+ @mod.autoload?(@cname, false)
71
37
  end
72
38
 
73
39
  # @sig (String) -> bool
@@ -1,93 +1,84 @@
1
1
  # frozen_string_literal: true
2
2
 
3
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.
4
+ # Centralizes the logic needed to descend into matching subdirectories right
5
+ # after the constant for an explicit namespace has been defined.
7
6
  #
8
7
  # The implementation assumes an explicit namespace is managed by one loader.
9
8
  # Loaders that reopen namespaces owned by other projects are responsible for
10
9
  # loading their constant before setup. This is documented.
11
10
  module ExplicitNamespace # :nodoc: all
11
+ # Maps cpaths of explicit namespaces with their corresponding loader.
12
+ # Entries are added as the namespaces are found, and removed as they are
13
+ # autoloaded.
14
+ #
15
+ # @sig Hash[String => Zeitwerk::Loader]
16
+ @cpaths = {}
17
+
12
18
  class << self
13
19
  include RealModName
14
20
  extend Internal
15
21
 
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.
22
+ # Registers `cpath` as being the constant path of an explicit namespace
23
+ # managed by `loader`.
33
24
  #
34
25
  # @sig (String, Zeitwerk::Loader) -> void
35
26
  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
27
+ @cpaths[cpath] = loader
28
+ end
29
+
30
+ # @sig (String) -> Zeitwerk::Loader?
31
+ internal def loader_for(mod, cname)
32
+ cpath = mod.equal?(Object) ? cname.name : "#{real_mod_name(mod)}::#{cname}"
33
+ @cpaths.delete(cpath)
42
34
  end
43
35
 
44
36
  # @sig (Zeitwerk::Loader) -> void
45
37
  internal def unregister_loader(loader)
46
- cpaths.delete_if { |_cpath, l| l == loader }
47
- disable_tracer_if_unneeded
38
+ @cpaths.delete_if { _2.equal?(loader) }
48
39
  end
49
40
 
50
41
  # This is an internal method only used by the test suite.
51
42
  #
52
- # @sig (String) -> bool
43
+ # @sig (String) -> Zeitwerk::Loader?
53
44
  internal def registered?(cpath)
54
- cpaths.key?(cpath)
45
+ @cpaths[cpath]
55
46
  end
56
47
 
48
+ # This is an internal method only used by the test suite.
49
+ #
57
50
  # @sig () -> void
58
- private def disable_tracer_if_unneeded
59
- mutex.synchronize do
60
- tracer.disable if cpaths.empty?
61
- end
51
+ internal def clear
52
+ @cpaths.clear
62
53
  end
63
54
 
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?
55
+ module Synchronized
56
+ extend Internal
57
+
58
+ MUTEX = Mutex.new
70
59
 
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
60
+ internal def register(...)
61
+ MUTEX.synchronize { super }
82
62
  end
83
- end
84
- end
85
63
 
86
- @cpaths = {}
87
- @mutex = Mutex.new
64
+ internal def loader_for(...)
65
+ MUTEX.synchronize { super }
66
+ end
67
+
68
+ internal def unregister_loader(...)
69
+ MUTEX.synchronize { super }
70
+ end
88
71
 
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))
72
+ internal def registered?(...)
73
+ MUTEX.synchronize { super }
74
+ end
75
+
76
+ internal def clear
77
+ MUTEX.synchronize { super }
78
+ end
79
+ end
80
+
81
+ prepend Synchronized unless RUBY_ENGINE == "ruby"
82
+ end
92
83
  end
93
84
  end
@@ -6,6 +6,7 @@ module Zeitwerk::Loader::Callbacks
6
6
 
7
7
  # Invoked from our decorated Kernel#require when a managed file is autoloaded.
8
8
  #
9
+ # @raise [Zeitwerk::NameError]
9
10
  # @sig (String) -> void
10
11
  internal def on_file_autoloaded(file)
11
12
  cref = autoloads.delete(file)
@@ -72,7 +73,7 @@ module Zeitwerk::Loader::Callbacks
72
73
  end
73
74
 
74
75
  # Invoked when a class or module is created or reopened, either from the
75
- # tracer or from module autovivification. If the namespace has matching
76
+ # const_added or from module autovivification. If the namespace has matching
76
77
  # subdirectories, we descend into them now.
77
78
  #
78
79
  # @private
@@ -57,9 +57,7 @@ module Zeitwerk::Loader::Helpers
57
57
  to_visit = [dir]
58
58
 
59
59
  while (dir = to_visit.shift)
60
- children = Dir.children(dir)
61
-
62
- children.each do |basename|
60
+ Dir.each_child(dir) do |basename|
63
61
  next if hidden?(basename)
64
62
 
65
63
  abspath = File.join(dir, basename)
@@ -469,7 +469,7 @@ module Zeitwerk
469
469
  # Registering is idempotent, and we have to keep the autoload pointing
470
470
  # to the file. This may run again if more directories are found later
471
471
  # on, no big deal.
472
- register_explicit_namespace(cref.path)
472
+ register_explicit_namespace(cref)
473
473
  end
474
474
  # If the existing autoload points to a file, it has to be preserved, if
475
475
  # not, it is fine as it is. In either case, we do not need to override.
@@ -515,8 +515,10 @@ module Zeitwerk
515
515
 
516
516
  log("earlier autoload for #{cref.path} discarded, it is actually an explicit namespace defined in #{file}") if logger
517
517
 
518
+ # Order matters: When Module#const_added is triggered by the autoload, we
519
+ # don't want the namespace to be registered yet.
518
520
  define_autoload(cref, file)
519
- register_explicit_namespace(cref.path)
521
+ register_explicit_namespace(cref)
520
522
  end
521
523
 
522
524
  # @sig (Module, Symbol, String) -> void
@@ -545,13 +547,13 @@ module Zeitwerk
545
547
  if autoload_path = cref.autoload?
546
548
  autoload_path if autoloads.key?(autoload_path)
547
549
  else
548
- Registry.inception?(cref.path)
550
+ Registry.inception?(cref.path, self)
549
551
  end
550
552
  end
551
553
 
552
- # @sig (String) -> void
553
- private def register_explicit_namespace(cpath)
554
- ExplicitNamespace.__register(cpath, self)
554
+ # @sig (Zeitwerk::Cref) -> void
555
+ private def register_explicit_namespace(cref)
556
+ ExplicitNamespace.__register(cref.path, self)
555
557
  end
556
558
 
557
559
  # @sig (String) -> void
@@ -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
@@ -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.17"
4
+ VERSION = "2.7.0"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -11,10 +11,12 @@ module Zeitwerk
11
11
  require_relative "zeitwerk/inflector"
12
12
  require_relative "zeitwerk/gem_inflector"
13
13
  require_relative "zeitwerk/null_inflector"
14
- require_relative "zeitwerk/kernel"
15
14
  require_relative "zeitwerk/error"
16
15
  require_relative "zeitwerk/version"
17
16
 
17
+ require_relative "zeitwerk/core_ext/kernel"
18
+ require_relative "zeitwerk/core_ext/module"
19
+
18
20
  # This is a dangerous method.
19
21
  #
20
22
  # @experimental
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.17
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-29 00:00:00.000000000 Z
11
+ date: 2024-10-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -23,6 +23,8 @@ files:
23
23
  - MIT-LICENSE
24
24
  - README.md
25
25
  - lib/zeitwerk.rb
26
+ - lib/zeitwerk/core_ext/kernel.rb
27
+ - lib/zeitwerk/core_ext/module.rb
26
28
  - lib/zeitwerk/cref.rb
27
29
  - lib/zeitwerk/error.rb
28
30
  - lib/zeitwerk/explicit_namespace.rb
@@ -30,7 +32,6 @@ files:
30
32
  - lib/zeitwerk/gem_loader.rb
31
33
  - lib/zeitwerk/inflector.rb
32
34
  - lib/zeitwerk/internal.rb
33
- - lib/zeitwerk/kernel.rb
34
35
  - lib/zeitwerk/loader.rb
35
36
  - lib/zeitwerk/loader/callbacks.rb
36
37
  - lib/zeitwerk/loader/config.rb
@@ -56,14 +57,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
57
  requirements:
57
58
  - - ">="
58
59
  - !ruby/object:Gem::Version
59
- version: '2.5'
60
+ version: '3.2'
60
61
  required_rubygems_version: !ruby/object:Gem::Requirement
61
62
  requirements:
62
63
  - - ">="
63
64
  - !ruby/object:Gem::Version
64
65
  version: '0'
65
66
  requirements: []
66
- rubygems_version: 3.5.15
67
+ rubygems_version: 3.5.11
67
68
  signing_key:
68
69
  specification_version: 4
69
70
  summary: Efficient and thread-safe constant autoloader
File without changes