zeitwerk 2.5.0.beta5 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a57a618cc6c371488c6b2a1360b3bfb37b4914e7c44addd688dd9ff6d245f92b
4
- data.tar.gz: 927300a0bec3a2941e79e3065068cb3359ced9a368a23204e31eb76c1de347bc
3
+ metadata.gz: edfc3d6e8c62e95f5a008e6625b5b7f2ff7e0067e3d311ec352e1bf6c6922c27
4
+ data.tar.gz: 9087c6cc6524a8fd2b6843c283496e29cc5d9157421f6ba7994feb362f383333
5
5
  SHA512:
6
- metadata.gz: 2c26a751d80bcf719e2705c30c24e78300141b8cfe4a57e3bed2a9bf26e80534755a0fe067b6f32e6c1f4e7bbaf7693232640a76ff710e868c07f11128e9da6d
7
- data.tar.gz: 270a9053e7ecccf58366272d6e60d2d3967d80f3d9777a0825700efcacf95e468a31c29924436df4231a53a971d31b0b4157d9f8bb8b82a91bd6fb6b2fdfb592
6
+ metadata.gz: 4d31bf02d352d61024f553f21b14093bc4d8cfc9276c106e8a67b499044aaf12bdd076288c89828b342a1980f163706b62c82d9bd21c15cdc4cadacf117e5299
7
+ data.tar.gz: 89cc85a2efeb95760de195fc2acf854256dba89cdee2208992c33ba5d8cb78ad71adaddc757026660dbc4a2d562e1efa623572b91d2107e9cf30020ea83524ab
data/README.md CHANGED
@@ -50,8 +50,8 @@
50
50
  - [Rules of thumb](#rules-of-thumb)
51
51
  - [Debuggers](#debuggers)
52
52
  - [debug.rb](#debugrb)
53
- - [Break](#break)
54
53
  - [Byebug](#byebug)
54
+ - [Break](#break)
55
55
  - [Pronunciation](#pronunciation)
56
56
  - [Supported Ruby versions](#supported-ruby-versions)
57
57
  - [Testing](#testing)
@@ -810,7 +810,9 @@ Kernel.module_eval do
810
810
  end
811
811
  ```
812
812
 
813
- That file does not define a constant path after the path name and you need to tell Zeitwerk:
813
+ `Kernel` is already defined by Ruby so the module cannot be autoloaded. Also, that file does not define a constant path after the path name. Therefore, Zeitwerk should not process it at all.
814
+
815
+ The extension can still coexist with the rest of the project, you only need to tell Zeitwerk to ignore it:
814
816
 
815
817
  ```ruby
816
818
  kernel_ext = "#{__dir__}/my_gem/core_ext/kernel.rb"
@@ -826,6 +828,14 @@ loader.ignore(core_ext)
826
828
  loader.setup
827
829
  ```
828
830
 
831
+ Now, that file has to be loaded manually with `require` or `require_relative`:
832
+
833
+ ```ruby
834
+ require_relative "my_gem/core_ext/kernel"
835
+ ```
836
+
837
+ and you can do that anytime, before configuring the loader, or after configuring the loader, does not matter.
838
+
829
839
  <a id="markdown-use-case-the-adapter-pattern" name="use-case-the-adapter-pattern"></a>
830
840
  #### Use case: The adapter pattern
831
841
 
@@ -973,17 +983,19 @@ With that, when Zeitwerk scans the file system and reaches the gem directories `
973
983
  <a id="markdown-debugrb" name="debugrb"></a>
974
984
  #### debug.rb
975
985
 
976
- The new [debug.rb](https://github.com/ruby/debug) gem and Zeitwerk seem to be compatible, as far as I can tell. This is the new debugger that is going to ship with Ruby 3.1.
986
+ The new [debug.rb](https://github.com/ruby/debug) gem and Zeitwerk are mostly compatible. This is the new debugger that is going to ship with Ruby 3.1.
977
987
 
978
- <a id="markdown-break" name="break"></a>
979
- #### Break
980
-
981
- Zeitwerk works fine with [@gsamokovarov](https://github.com/gsamokovarov)'s [Break](https://github.com/gsamokovarov/break) debugger.
988
+ There's one exception, though: Due to a technical limitation of tracepoints, explicit namespaces are not autoloaded while expressions are evaluated in the REPL. See [ruby/debug#408](https://github.com/ruby/debug/issues/408).
982
989
 
983
990
  <a id="markdown-byebug" name="byebug"></a>
984
991
  #### Byebug
985
992
 
986
- Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug) are incompatible, classes or modules that belong to [explicit namespaces](#explicit-namespaces) are not autoloaded inside a Byebug session. See [this issue](https://github.com/deivid-rodriguez/byebug/issues/564#issuecomment-499413606) for further details.
993
+ Zeitwerk and [Byebug](https://github.com/deivid-rodriguez/byebug) have a similar edge incompatibility.
994
+
995
+ <a id="markdown-break" name="break"></a>
996
+ #### Break
997
+
998
+ Zeitwerk works fine with [@gsamokovarov](https://github.com/gsamokovarov)'s [Break](https://github.com/gsamokovarov/break) debugger.
987
999
 
988
1000
  <a id="markdown-pronunciation" name="pronunciation"></a>
989
1001
  ## Pronunciation
@@ -24,22 +24,23 @@ module Kernel
24
24
  def require(path)
25
25
  if loader = Zeitwerk::Registry.loader_for(path)
26
26
  if path.end_with?(".rb")
27
- zeitwerk_original_require(path).tap do |required|
28
- loader.on_file_autoloaded(path) if required
29
- end
27
+ required = zeitwerk_original_require(path)
28
+ loader.on_file_autoloaded(path) if required
29
+ required
30
30
  else
31
31
  loader.on_dir_autoloaded(path)
32
+ $LOADED_FEATURES << path
32
33
  true
33
34
  end
34
35
  else
35
- zeitwerk_original_require(path).tap do |required|
36
- if required
37
- abspath = $LOADED_FEATURES.last
38
- if loader = Zeitwerk::Registry.loader_for(abspath)
39
- loader.on_file_autoloaded(abspath)
40
- end
36
+ required = zeitwerk_original_require(path)
37
+ if required
38
+ abspath = $LOADED_FEATURES.last
39
+ if loader = Zeitwerk::Registry.loader_for(abspath)
40
+ loader.on_file_autoloaded(abspath)
41
41
  end
42
42
  end
43
+ required
43
44
  end
44
45
  end
45
46
 
@@ -11,16 +11,19 @@ module Zeitwerk::Loader::Callbacks
11
11
  cref = autoloads.delete(file)
12
12
  cpath = cpath(*cref)
13
13
 
14
+ # If reloading is enabled, we need to put this constant for unloading
15
+ # regardless of what cdef? says. In Ruby < 3.1 the internal state is not
16
+ # fully cleared. Module#constants still includes it, and you need to
17
+ # remove_const. See https://github.com/ruby/ruby/pull/4715.
14
18
  to_unload[cpath] = [file, cref] if reloading_enabled?
15
19
  Zeitwerk::Registry.unregister_autoload(file)
16
20
 
17
- if logger && cdef?(*cref)
18
- log("constant #{cpath} loaded from file #{file}")
19
- elsif !cdef?(*cref)
21
+ if cdef?(*cref)
22
+ log("constant #{cpath} loaded from file #{file}") if logger
23
+ run_on_load_callbacks(cpath, cget(*cref), file) unless on_load_callbacks.empty?
24
+ else
20
25
  raise Zeitwerk::NameError.new("expected file #{file} to define constant #{cpath}, but didn't", cref.last)
21
26
  end
22
-
23
- run_on_load_callbacks(cpath, cget(*cref), file) unless on_load_callbacks.empty?
24
27
  end
25
28
 
26
29
  # Invoked from our decorated Kernel#require when a managed directory is
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
- require "securerandom"
5
4
 
6
5
  module Zeitwerk
7
6
  class Loader
@@ -14,22 +13,16 @@ module Zeitwerk
14
13
  include Helpers
15
14
  include Config
16
15
 
17
- # Keeps track of autoloads defined by the loader which have not been
18
- # executed so far.
16
+ # Maps absolute paths for which an autoload has been set ---and not
17
+ # executed--- to their corresponding parent class or module and constant
18
+ # name.
19
19
  #
20
- # This metadata helps us implement a few things:
21
- #
22
- # 1. When autoloads are triggered, ensure they define the expected constant
23
- # and invoke user callbacks. If reloading is enabled, remember cref and
24
- # abspath for later unloading logic.
25
- #
26
- # 2. When unloading, remove autoloads that have not been executed.
27
- #
28
- # 3. Eager load with a recursive const_get, rather than a recursive require,
29
- # for consistency with lazy loading.
20
+ # "/Users/fxn/blog/app/models/user.rb" => [Object, :User],
21
+ # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, :Pricing]
22
+ # ...
30
23
  #
31
24
  # @private
32
- # @sig Zeitwerk::Autoloads
25
+ # @sig Hash[String, [Module, Symbol]]
33
26
  attr_reader :autoloads
34
27
 
35
28
  # We keep track of autoloaded directories to remove them from the registry
@@ -87,7 +80,7 @@ module Zeitwerk
87
80
  def initialize
88
81
  super
89
82
 
90
- @autoloads = Autoloads.new
83
+ @autoloads = {}
91
84
  @autoloaded_dirs = []
92
85
  @to_unload = {}
93
86
  @lazy_subdirs = Hash.new { |h, cpath| h[cpath] = [] }
@@ -130,23 +123,17 @@ module Zeitwerk
130
123
  # @sig () -> void
131
124
  def unload
132
125
  mutex.synchronize do
133
- # We are going to keep track of the files that were required by our
134
- # autoloads to later remove them from $LOADED_FEATURES, thus making them
135
- # loadable by Kernel#require again.
136
- #
137
- # Directories are not stored in $LOADED_FEATURES, keeping track of files
138
- # is enough.
139
- unloaded_files = Set.new
140
-
141
- autoloads.each do |(parent, cname), abspath|
126
+ abspaths_of_unloaded_crefs = Set.new
127
+
128
+ autoloads.each do |abspath, (parent, cname)|
142
129
  if parent.autoload?(cname)
143
130
  unload_autoload(parent, cname)
144
131
  else
145
132
  # Could happen if loaded with require_relative. That is unsupported,
146
133
  # and the constant path would escape unloadable_cpath? This is just
147
134
  # defensive code to clean things up as much as we are able to.
148
- unload_cref(parent, cname) if cdef?(parent, cname)
149
- unloaded_files.add(abspath) if ruby?(abspath)
135
+ unload_cref(parent, cname)
136
+ abspaths_of_unloaded_crefs.add(abspath)
150
137
  end
151
138
  end
152
139
 
@@ -156,11 +143,15 @@ module Zeitwerk
156
143
  run_on_unload_callbacks(cpath, value, abspath)
157
144
  end
158
145
 
159
- unload_cref(parent, cname) if cdef?(parent, cname)
160
- unloaded_files.add(abspath) if ruby?(abspath)
146
+ unload_cref(parent, cname)
147
+ abspaths_of_unloaded_crefs.add(abspath)
161
148
  end
162
149
 
163
- unless unloaded_files.empty?
150
+ unless abspaths_of_unloaded_crefs.empty?
151
+ # We remove these abspaths from $LOADED_FEATURES because, otherwise,
152
+ # `require`'s idempotence would prevent newly defined autoloads from
153
+ # loading them again.
154
+ #
164
155
  # Bootsnap decorates Kernel#require to speed it up using a cache and
165
156
  # this optimization does not check if $LOADED_FEATURES has the file.
166
157
  #
@@ -172,7 +163,7 @@ module Zeitwerk
172
163
  # Rails applications may depend on bootsnap, so for unloading to work
173
164
  # in that setting it is preferable that we restrict our API choice to
174
165
  # one of those methods.
175
- $LOADED_FEATURES.reject! { |file| unloaded_files.member?(file) }
166
+ $LOADED_FEATURES.reject! { |file| abspaths_of_unloaded_crefs.member?(file) }
176
167
  end
177
168
 
178
169
  autoloads.clear
@@ -234,7 +225,7 @@ module Zeitwerk
234
225
  next if honour_exclusions && excluded_from_eager_load?(abspath)
235
226
 
236
227
  if ruby?(abspath)
237
- if cref = autoloads.cref_for(abspath)
228
+ if cref = autoloads[abspath]
238
229
  cget(*cref)
239
230
  end
240
231
  elsif dir?(abspath) && !root_dirs.key?(abspath)
@@ -376,7 +367,7 @@ module Zeitwerk
376
367
 
377
368
  # @sig (Module, Symbol, String) -> void
378
369
  def autoload_subdir(parent, cname, subdir)
379
- if autoload_path = autoloads.abspath_for(parent, cname)
370
+ if autoload_path = autoload_path_set_by_me_for?(parent, cname)
380
371
  cpath = cpath(parent, cname)
381
372
  register_explicit_namespace(cpath) if ruby?(autoload_path)
382
373
  # We do not need to issue another autoload, the existing one is enough
@@ -432,7 +423,7 @@ module Zeitwerk
432
423
 
433
424
  # @sig (Module, Symbol, String) -> void
434
425
  def set_autoload(parent, cname, abspath)
435
- autoloads.define(parent, cname, abspath)
426
+ parent.autoload(cname, abspath)
436
427
 
437
428
  if logger
438
429
  if ruby?(abspath)
@@ -442,6 +433,7 @@ module Zeitwerk
442
433
  end
443
434
  end
444
435
 
436
+ autoloads[abspath] = [parent, cname]
445
437
  Registry.register_autoload(self, abspath)
446
438
 
447
439
  # See why in the documentation of Zeitwerk::Registry.inceptions.
@@ -450,6 +442,15 @@ module Zeitwerk
450
442
  end
451
443
  end
452
444
 
445
+ # @sig (Module, Symbol) -> String?
446
+ def autoload_path_set_by_me_for?(parent, cname)
447
+ if autoload_path = strict_autoload_path(parent, cname)
448
+ autoload_path if autoloads.key?(autoload_path)
449
+ else
450
+ Registry.inception?(cpath(parent, cname))
451
+ end
452
+ end
453
+
453
454
  # @sig (String) -> void
454
455
  def register_explicit_namespace(cpath)
455
456
  ExplicitNamespace.register(cpath, self)
@@ -494,7 +495,13 @@ module Zeitwerk
494
495
 
495
496
  # @sig (Module, Symbol) -> void
496
497
  def unload_cref(parent, cname)
498
+ # Let's optimistically remove_const. The way we use it, this is going to
499
+ # succeed always if all is good.
497
500
  parent.__send__(:remove_const, cname)
501
+ rescue ::NameError
502
+ # There are a few edge scenarios in which this may happen. If the constant
503
+ # is gone, that is OK, anyway.
504
+ else
498
505
  log("#{cpath(parent, cname)} unloaded") if logger
499
506
  end
500
507
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.5.0.beta5"
4
+ VERSION = "2.5.2"
5
5
  end
data/lib/zeitwerk.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  module Zeitwerk
4
4
  require_relative "zeitwerk/real_mod_name"
5
5
  require_relative "zeitwerk/loader"
6
- require_relative "zeitwerk/autoloads"
7
6
  require_relative "zeitwerk/registry"
8
7
  require_relative "zeitwerk/explicit_namespace"
9
8
  require_relative "zeitwerk/inflector"
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.5.0.beta5
4
+ version: 2.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Xavier Noria
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-29 00:00:00.000000000 Z
11
+ date: 2021-12-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem
@@ -23,7 +23,6 @@ files:
23
23
  - MIT-LICENSE
24
24
  - README.md
25
25
  - lib/zeitwerk.rb
26
- - lib/zeitwerk/autoloads.rb
27
26
  - lib/zeitwerk/error.rb
28
27
  - lib/zeitwerk/explicit_namespace.rb
29
28
  - lib/zeitwerk/gem_inflector.rb
@@ -55,9 +54,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
54
  version: '2.5'
56
55
  required_rubygems_version: !ruby/object:Gem::Requirement
57
56
  requirements:
58
- - - ">"
57
+ - - ">="
59
58
  - !ruby/object:Gem::Version
60
- version: 1.3.1
59
+ version: '0'
61
60
  requirements: []
62
61
  rubygems_version: 3.2.22
63
62
  signing_key:
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Zeitwerk
4
- # @private
5
- class Autoloads
6
- # Maps crefs for which an autoload has been defined to the corresponding
7
- # absolute path.
8
- #
9
- # [Object, :User] => "/Users/fxn/blog/app/models/user.rb"
10
- # [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
11
- # ...
12
- #
13
- # This colection is transient, callbacks delete its entries as autoloads get
14
- # executed.
15
- #
16
- # @sig Hash[[Module, Symbol], String]
17
- attr_reader :c2a
18
-
19
- # This is the inverse of c2a, for inverse lookups.
20
- #
21
- # @sig Hash[String, [Module, Symbol]]
22
- attr_reader :a2c
23
-
24
- # @sig () -> void
25
- def initialize
26
- @c2a = {}
27
- @a2c = {}
28
- end
29
-
30
- # @sig (Module, Symbol, String) -> void
31
- def define(parent, cname, abspath)
32
- parent.autoload(cname, abspath)
33
- cref = [parent, cname]
34
- c2a[cref] = abspath
35
- a2c[abspath] = cref
36
- end
37
-
38
- # @sig () { () -> [[Module, Symbol], String] } -> void
39
- def each(&block)
40
- c2a.each(&block)
41
- end
42
-
43
- # @sig (Module, Symbol) -> String?
44
- def abspath_for(parent, cname)
45
- c2a[[parent, cname]]
46
- end
47
-
48
- # @sig (String) -> [Module, Symbol]?
49
- def cref_for(abspath)
50
- a2c[abspath]
51
- end
52
-
53
- # @sig (String) -> [Module, Symbol]?
54
- def delete(abspath)
55
- cref = a2c.delete(abspath)
56
- c2a.delete(cref)
57
- cref
58
- end
59
-
60
- # @sig () -> void
61
- def clear
62
- c2a.clear
63
- a2c.clear
64
- end
65
-
66
- # @sig () -> bool
67
- def empty?
68
- c2a.empty? && a2c.empty?
69
- end
70
- end
71
- end