zeitwerk 2.5.0.beta5 → 2.5.2

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: 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