zeitwerk 2.0.0 → 2.1.0

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: 5d964999444d0bc99d003d604cb2634d2753c579a4aebcce4fccb4c9ded78530
4
- data.tar.gz: 95fc0030faf0472c8359c3665a09fa68dc3c79bff1d813e55d0f03f68c6f5282
3
+ metadata.gz: 2af29bb519da160c83620c23a1f1c119c7b534a3e3db308b2635b344b435dfa1
4
+ data.tar.gz: ec2dc810c74887befea3550b3305b0b096610cbb06a2983dad0c041c9bed34dc
5
5
  SHA512:
6
- metadata.gz: 81f2ac76566c1cda996eeac723fa7579dba24011f32fab94458ced0faf5eb7fe920ee2e016ce45ec83fc0d7b17244a1bf98764d71e667cbe067fa8ee87f01ca3
7
- data.tar.gz: a90efd80c7caba53d6ec12bc20ee1a76f8d49a6a2e59a82b60a646486eac758348ff1250eee42585dbb2294718a41d59d97c9254ee51b802cddd1ce7add46ead
6
+ metadata.gz: 8cafd96d9f21546ea8dcb58f8b589d59b5de994492a2f89f6cd4eb2701d673e58c888884b672ddd47580778090f4af918c908e6325d5690ce8297b0e152eb391
7
+ data.tar.gz: e1fba9ebf4e35fa10c2577bdb4a19124ee26e3d09a85b2382a5bc0c83688130b14a0c8e9900e6572f25efe86c388b54139d9ca82d7053cfc415b39a618d1ec16
@@ -68,8 +68,9 @@ module Zeitwerk
68
68
  # @return [{String => String}]
69
69
  attr_reader :shadowed_files
70
70
 
71
- # Maps real absolute paths for which an autoload has been set to their
72
- # corresponding parent class or module and constant name.
71
+ # Maps real absolute paths for which an autoload has been set ---and not
72
+ # executed--- to their corresponding parent class or module and constant
73
+ # name.
73
74
  #
74
75
  # "/Users/fxn/blog/app/models/user.rb" => [Object, "User"],
75
76
  # "/Users/fxn/blog/app/models/hotel/pricing.rb" => [Hotel, "Pricing"]
@@ -84,13 +85,18 @@ module Zeitwerk
84
85
  #
85
86
  # Files are removed as they are autoloaded, but directories need to wait due
86
87
  # to concurrency (see why in Zeitwerk::Loader::Callbacks#on_dir_autoloaded).
88
+ #
89
+ # @private
90
+ # @return [<String>]
87
91
  attr_reader :autoloaded_dirs
88
92
 
89
- # Constant paths loaded so far.
93
+ # Constants paths that would be unloaded if you would reload. If reloading
94
+ # is enabled, this hash is filled as constants are autoloaded or eager
95
+ # loaded. Otherwise, the collection remains empty.
90
96
  #
91
97
  # @private
92
- # @return [Set<String>]
93
- attr_reader :loaded_cpaths
98
+ # @return [{String => (String, (Module, String))}]
99
+ attr_reader :to_unload
94
100
 
95
101
  # Maps constant paths of namespaces to arrays of corresponding directories.
96
102
  #
@@ -137,7 +143,7 @@ module Zeitwerk
137
143
  @ignored_paths = Set.new
138
144
  @autoloads = {}
139
145
  @autoloaded_dirs = []
140
- @loaded_cpaths = Set.new
146
+ @to_unload = {}
141
147
  @lazy_subdirs = {}
142
148
  @shadowed_files = {}
143
149
  @eager_load_exclusions = Set.new
@@ -270,19 +276,21 @@ module Zeitwerk
270
276
 
271
277
  autoloads.each do |realpath, (parent, cname)|
272
278
  if parent.autoload?(cname)
273
- parent.send(:remove_const, cname)
274
- log("autoload for #{cpath(parent, cname)} removed") if logger
279
+ unload_autoload(parent, cname)
275
280
  else
276
- if cdef?(parent, cname)
277
- parent.send(:remove_const, cname)
278
- log("#{cpath(parent, cname)} unloaded") if logger
279
- else
280
- # Already unloaded somehow, that is fine.
281
- end
281
+ # Could happen if loaded with require_relative. require_relative is
282
+ # not supported, and the cpath would escape `to_unload?`. This is
283
+ # just defensive code to clean things up as much as we are able to.
284
+ unload_cref(parent, cname) if cdef?(parent, cname)
282
285
  unloaded_files.add(realpath) if ruby?(realpath)
283
286
  end
284
287
  end
285
288
 
289
+ to_unload.each_value do |(realpath, (parent, cname))|
290
+ unload_cref(parent, cname) if cdef?(parent, cname)
291
+ unloaded_files.add(realpath) if ruby?(realpath)
292
+ end
293
+
286
294
  unless unloaded_files.empty?
287
295
  # Bootsnap decorates Kernel#require to speed it up using a cache and
288
296
  # this optimization does not check if $LOADED_FEATURES has the file.
@@ -300,7 +308,7 @@ module Zeitwerk
300
308
 
301
309
  autoloads.clear
302
310
  autoloaded_dirs.clear
303
- loaded_cpaths.clear
311
+ to_unload.clear
304
312
  lazy_subdirs.clear
305
313
  shadowed_files.clear
306
314
 
@@ -369,12 +377,13 @@ module Zeitwerk
369
377
  mutex.synchronize { eager_load_exclusions.merge(expand_paths(paths)) }
370
378
  end
371
379
 
372
- # Says if the given constant path has been loaded.
380
+ # Says if the given constant path would be unloaded on reload. This
381
+ # predicate returns `false` if reloading is disabled.
373
382
  #
374
383
  # @param cpath [String]
375
384
  # @return [Boolean]
376
- def loaded?(cpath)
377
- loaded_cpaths.member?(cpath)
385
+ def to_unload?(cpath)
386
+ to_unload.key?(cpath)
378
387
  end
379
388
 
380
389
  # --- Class methods ---------------------------------------------------------------------------
@@ -662,5 +671,21 @@ module Zeitwerk
662
671
  end
663
672
  end
664
673
  end
674
+
675
+ # @param parent [Module]
676
+ # @param cname [String]
677
+ # @return [void]
678
+ def unload_autoload(parent, cname)
679
+ parent.send(:remove_const, cname)
680
+ log("autoload for #{cpath(parent, cname)} removed") if logger
681
+ end
682
+
683
+ # @param parent [Module]
684
+ # @param cname [String]
685
+ # @return [void]
686
+ def unload_cref(parent, cname)
687
+ parent.send(:remove_const, cname)
688
+ log("#{cpath(parent, cname)} unloaded") if logger
689
+ end
665
690
  end
666
691
  end
@@ -5,10 +5,10 @@ module Zeitwerk::Loader::Callbacks
5
5
  # @param file [String]
6
6
  # @return [void]
7
7
  def on_file_autoloaded(file)
8
- parent, cname = cref_autoloaded_from(file)
9
- loaded_cpaths.add(cpath(parent, cname))
8
+ cref = autoloads.delete(file)
9
+ to_unload[cpath(*cref)] = [file, cref] if reloading_enabled?
10
10
  Zeitwerk::Registry.unregister_autoload(file)
11
- log("constant #{cpath(parent, cname)} loaded from file #{file}") if logger
11
+ log("constant #{cpath(*cref)} loaded from file #{file}") if logger
12
12
  end
13
13
 
14
14
  # Invoked from our decorated Kernel#require when a managed directory is
@@ -25,23 +25,25 @@ module Zeitwerk::Loader::Callbacks
25
25
  # autovivifies the module, and while autoloads for its children are being
26
26
  # set, thread t2 autoloads the same namespace.
27
27
  #
28
- # Without the mutex and short-circuiting break, t2 would reset the module.
28
+ # Without the mutex and subsequent delete call, t2 would reset the module.
29
29
  # That not only would reassign the constant (undesirable per se) but, worse,
30
30
  # the module object created by t2 wouldn't have any of the autoloads for its
31
31
  # children, since t1 would have correctly deleted its lazy_subdirs entry.
32
32
  mutex2.synchronize do
33
- parent, cname = cref_autoloaded_from(dir)
34
- # If reloading is disabled and there are several threads autoloading the
35
- # same namespace at the same time, the parent is going to bbe nil for all
36
- # except the first one.
37
- break if parent.nil? || loaded_cpaths.include?(cpath(parent, cname))
33
+ if cref = autoloads.delete(dir)
34
+ autovivified_module = cref[0].const_set(cref[1], Module.new)
35
+ log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
38
36
 
39
- autovivified_module = parent.const_set(cname, Module.new)
40
- log("module #{autovivified_module.name} autovivified from directory #{dir}") if logger
37
+ to_unload[autovivified_module.name] = [dir, cref] if reloading_enabled?
41
38
 
42
- loaded_cpaths.add(autovivified_module.name)
43
- autoloaded_dirs << dir
44
- on_namespace_loaded(autovivified_module)
39
+ # We don't unregister `dir` in the registry because concurrent threads
40
+ # wouldn't find a loader associated to it in Kernel#require and would
41
+ # try to require the directory. Instead, we are going to keep track of
42
+ # these to be able to unregister later if eager loading.
43
+ autoloaded_dirs << dir
44
+
45
+ on_namespace_loaded(autovivified_module)
46
+ end
45
47
  end
46
48
  end
47
49
 
@@ -59,10 +61,4 @@ module Zeitwerk::Loader::Callbacks
59
61
  end
60
62
  end
61
63
  end
62
-
63
- # @private
64
- # @return [(Module, String)]
65
- def cref_autoloaded_from(path)
66
- reloading_enabled? ? autoloads[path] : autoloads.delete(path)
67
- end
68
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zeitwerk
4
- VERSION = "2.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
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.0.0
4
+ version: 2.1.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: 2019-04-07 00:00:00.000000000 Z
11
+ date: 2019-04-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Zeitwerk implements constant autoloading with Ruby semantics. Each gem