zeitwerk 2.4.1 → 2.5.0.beta3

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: b0c408b8f6001e150b16b1e06b1b05d31bcbf4f0144535c995093c822065be3d
4
- data.tar.gz: af26de0ecd68b8ebc1836ea431d59c6d7e10d79937c402e3529fc4275631c950
3
+ metadata.gz: 3c75f8cc8ed4e3df2f4bf5822402b5d1179e80d1bd2680efda63302f082dc366
4
+ data.tar.gz: ee91b83fb9c7e15c9502a3200c3d0a5cb785f5f2e9f75cd419e57bc335691784
5
5
  SHA512:
6
- metadata.gz: 012c9a70cd31973a7251f46f62c102bf11155a6ea90dcdfa62bb2cd1859fca07cfed268dd130528fc55eab4ea0d440347e7ed0cb78f54dca861f96f56014541d
7
- data.tar.gz: 7243c0f9b6c39dd389f0570fe03f11a8cadb01b74ae5d5efaa9b895ecf3b2717b5eb71e027c555f1cfeb95457dd5f5d8ace4516eb3a77500b05b1c8f973c1a94
6
+ metadata.gz: e2ccad2ec4ca741a01432875eb683307eeb9840b278a3fff45ec74d3b1c2892c6953e7ef18b42cca40bfb09ca21f952c7aadf8653fc67f5e7a29b40e07e8bb74
7
+ data.tar.gz: b74645e665af729b8fd3a2fd6fcc6da86bcc953979f0f7db4b70fffa67e09e399615158acdfac996aaf083c28f538dde90f98b7682f9feb5331bdc498f2f24a5
data/README.md CHANGED
@@ -3,44 +3,54 @@
3
3
 
4
4
 
5
5
  [![Gem Version](https://img.shields.io/gem/v/zeitwerk.svg?style=for-the-badge)](https://rubygems.org/gems/zeitwerk)
6
- [![Build Status](https://img.shields.io/travis/com/fxn/zeitwerk/master?style=for-the-badge)](https://travis-ci.com/fxn/zeitwerk)
6
+ [![Build Status](https://img.shields.io/github/workflow/status/fxn/zeitwerk/CI?event=push&style=for-the-badge)](https://github.com/fxn/zeitwerk/actions?query=event%3Apush)
7
7
 
8
8
  <!-- TOC -->
9
9
 
10
10
  - [Introduction](#introduction)
11
11
  - [Synopsis](#synopsis)
12
12
  - [File structure](#file-structure)
13
- - [Implicit namespaces](#implicit-namespaces)
14
- - [Explicit namespaces](#explicit-namespaces)
15
- - [Collapsing directories](#collapsing-directories)
13
+ - [The idea: File paths match constant paths](#the-idea-file-paths-match-constant-paths)
14
+ - [Inner simple constants](#inner-simple-constants)
15
+ - [Root directories and root namespaces](#root-directories-and-root-namespaces)
16
+ - [The default root namespace is `Object`](#the-default-root-namespace-is-object)
17
+ - [Custom root namespaces](#custom-root-namespaces)
16
18
  - [Nested root directories](#nested-root-directories)
19
+ - [Implicit namespaces](#implicit-namespaces)
20
+ - [Explicit namespaces](#explicit-namespaces)
21
+ - [Collapsing directories](#collapsing-directories)
17
22
  - [Usage](#usage)
18
- - [Setup](#setup)
19
- - [Generic](#generic)
20
- - [for_gem](#for_gem)
21
- - [Autoloading](#autoloading)
22
- - [Eager loading](#eager-loading)
23
- - [Reloading](#reloading)
24
- - [Inflection](#inflection)
25
- - [Zeitwerk::Inflector](#zeitwerkinflector)
26
- - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
27
- - [Custom inflector](#custom-inflector)
28
- - [Logging](#logging)
29
- - [Loader tag](#loader-tag)
30
- - [Ignoring parts of the project](#ignoring-parts-of-the-project)
31
- - [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
32
- - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
33
- - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
34
- - [Edge cases](#edge-cases)
35
- - [Reopening third-party namespaces](#reopening-third-party-namespaces)
36
- - [Rules of thumb](#rules-of-thumb)
37
- - [Debuggers](#debuggers)
38
- - [Break](#break)
39
- - [Byebug](#byebug)
23
+ - [Setup](#setup)
24
+ - [Generic](#generic)
25
+ - [for_gem](#for_gem)
26
+ - [Autoloading](#autoloading)
27
+ - [Eager loading](#eager-loading)
28
+ - [Reloading](#reloading)
29
+ - [Inflection](#inflection)
30
+ - [Zeitwerk::Inflector](#zeitwerkinflector)
31
+ - [Zeitwerk::GemInflector](#zeitwerkgeminflector)
32
+ - [Custom inflector](#custom-inflector)
33
+ - [The on_load callback](#the-on_load-callback)
34
+ - [The on_unload callback](#the-on_unload-callback)
35
+ - [Technical details](#technical-details)
36
+ - [Logging](#logging)
37
+ - [Loader tag](#loader-tag)
38
+ - [Ignoring parts of the project](#ignoring-parts-of-the-project)
39
+ - [Use case: Files that do not follow the conventions](#use-case-files-that-do-not-follow-the-conventions)
40
+ - [Use case: The adapter pattern](#use-case-the-adapter-pattern)
41
+ - [Use case: Test files mixed with implementation files](#use-case-test-files-mixed-with-implementation-files)
42
+ - [Edge cases](#edge-cases)
43
+ - [Reopening third-party namespaces](#reopening-third-party-namespaces)
44
+ - [Rules of thumb](#rules-of-thumb)
45
+ - [Debuggers](#debuggers)
46
+ - [Break](#break)
47
+ - [Byebug](#byebug)
40
48
  - [Pronunciation](#pronunciation)
41
49
  - [Supported Ruby versions](#supported-ruby-versions)
42
50
  - [Testing](#testing)
43
51
  - [Motivation](#motivation)
52
+ - [Kerner#require is brittle](#kernerrequire-is-brittle)
53
+ - [Rails autoloading was brittle](#rails-autoloading-was-brittle)
44
54
  - [Thanks](#thanks)
45
55
  - [License](#license)
46
56
 
@@ -116,6 +126,9 @@ Zeitwerk::Loader.eager_load_all
116
126
  <a id="markdown-file-structure" name="file-structure"></a>
117
127
  ## File structure
118
128
 
129
+ <a id="markdown-the-idea-file-paths-match-constant-paths" name="the-idea-file-paths-match-constant-paths"></a>
130
+ ### The idea: File paths match constant paths
131
+
119
132
  To have a file structure Zeitwerk can work with, just name files and directories after the name of the classes and modules they define:
120
133
 
121
134
  ```
@@ -125,25 +138,57 @@ lib/my_gem/bar_baz.rb -> MyGem::BarBaz
125
138
  lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
126
139
  ```
127
140
 
128
- Every directory configured with `push_dir` acts as root namespace. There can be several of them. For example, given
141
+ You can tune that a bit by [collapsing directories](#collapsing-directories), or by [ignoring parts of the project](#ignoring-parts-of-the-project), but that is the main idea.
142
+
143
+ <a id="markdown-inner-simple-constants" name="inner-simple-constants"></a>
144
+ ### Inner simple constants
145
+
146
+ While a simple constant like `HttpCrawler::MAX_RETRIES` can be defined in its own file:
129
147
 
130
148
  ```ruby
131
- loader.push_dir(Rails.root.join("app/models"))
132
- loader.push_dir(Rails.root.join("app/controllers"))
149
+ # http_crawler/max_retries.rb
150
+ HttpCrawler::MAX_RETRIES = 10
133
151
  ```
134
152
 
135
- Zeitwerk understands that their respective files and subdirectories belong to the root namespace:
153
+ that is not required, you can also define it the regular way:
136
154
 
155
+ ```ruby
156
+ # http_crawler.rb
157
+ class HttpCrawler
158
+ MAX_RETRIES = 10
159
+ end
137
160
  ```
138
- app/models/user.rb -> User
139
- app/controllers/admin/users_controller.rb -> Admin::UsersController
161
+
162
+ <a id="markdown-root-directories-and-root-namespaces" name="root-directories-and-root-namespaces"></a>
163
+ ### Root directories and root namespaces
164
+
165
+ Every directory configured with `push_dir` is called a _root directory_, and they represent _root namespaces_.
166
+
167
+ <a id="markdown-the-default-root-namespace-is-object" name="the-default-root-namespace-is-object"></a>
168
+ #### The default root namespace is `Object`
169
+
170
+ By default, the namespace associated to a root directory is the top-level one: `Object`.
171
+
172
+ For example, given
173
+
174
+ ```ruby
175
+ loader.push_dir("#{__dir__}/models")
176
+ loader.push_dir("#{__dir__}/serializers"))
140
177
  ```
141
178
 
142
- Alternatively, you can associate a custom namespace to a root directory by passing a class or module object in the optional `namespace` keyword argument.
179
+ these are the expected classes and modules being defined by these files:
143
180
 
144
- For example, Active Job queue adapters have to define a constant after their name in `ActiveJob::QueueAdapters`.
181
+ ```
182
+ models/user.rb -> User
183
+ serializers/user_serializer.rb -> UserSerializer
184
+ ```
185
+
186
+ <a id="markdown-custom-root-namespaces" name="custom-root-namespaces"></a>
187
+ #### Custom root namespaces
145
188
 
146
- So, if you declare
189
+ While `Object` is by far the most common root namespace, you can associate a different one to a particular root directory. The method `push_dir` accepts a class or module object in the optional `namespace` keyword argument.
190
+
191
+ For example, given:
147
192
 
148
193
  ```ruby
149
194
  require "active_job"
@@ -151,9 +196,26 @@ require "active_job/queue_adapters"
151
196
  loader.push_dir("#{__dir__}/adapters", namespace: ActiveJob::QueueAdapters)
152
197
  ```
153
198
 
154
- your adapter can be stored directly in that directory instead of the canonical `#{__dir__}/active_job/queue_adapters`.
199
+ a file defining `ActiveJob::QueueAdapters::MyQueueAdapter` does not need the conventional parent directories, you can (and have to) store the file directly below `adapters`:
155
200
 
156
- Please, note that the given namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::V2::Deliveries`, that one can be reloaded.
201
+ ```
202
+ adapters/my_queue_adapter.rb -> ActiveJob::QueueAdapters::MyQueueAdapter
203
+ ```
204
+
205
+ Please, note that the given root namespace must be non-reloadable, though autoloaded constants in that namespace can be. That is, if you associate `app/api` with an existing `Api` module, that module should not be reloadable. However, if the project defines and autoloads the class `Api::Deliveries`, that one can be reloaded.
206
+
207
+ <a id="markdown-nested-root-directories" name="nested-root-directories"></a>
208
+ #### Nested root directories
209
+
210
+ Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
211
+
212
+ Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
213
+
214
+ ```
215
+ app/models/concerns/geolocatable.rb
216
+ ```
217
+
218
+ should define `Geolocatable`, not `Concerns::Geolocatable`.
157
219
 
158
220
  <a id="markdown-implicit-namespaces" name="implicit-namespaces"></a>
159
221
  ### Implicit namespaces
@@ -215,19 +277,6 @@ To illustrate usage of glob patterns, if `actions` in the example above is part
215
277
  loader.collapse("#{__dir__}/*/actions")
216
278
  ```
217
279
 
218
- <a id="markdown-nested-root-directories" name="nested-root-directories"></a>
219
- ### Nested root directories
220
-
221
- Root directories should not be ideally nested, but Zeitwerk supports them because in Rails, for example, both `app/models` and `app/models/concerns` belong to the autoload paths.
222
-
223
- Zeitwerk detects nested root directories, and treats them as roots only. In the example above, `concerns` is not considered to be a namespace below `app/models`. For example, the file:
224
-
225
- ```
226
- app/models/concerns/geolocatable.rb
227
- ```
228
-
229
- should define `Geolocatable`, not `Concerns::Geolocatable`.
230
-
231
280
  <a id="markdown-usage" name="usage"></a>
232
281
  ## Usage
233
282
 
@@ -386,11 +435,13 @@ On reloading, client code has to update anything that would otherwise be storing
386
435
  <a id="markdown-inflection" name="inflection"></a>
387
436
  ### Inflection
388
437
 
389
- Each individual loader needs an inflector to figure out which constant path would a given file or directory map to. Zeitwerk ships with two basic inflectors.
438
+ Each individual loader needs an inflector to figure out which constant path would a given file or directory map to. Zeitwerk ships with two basic inflectors, and you can define your own.
390
439
 
391
440
  <a id="markdown-zeitwerkinflector" name="zeitwerkinflector"></a>
392
441
  #### Zeitwerk::Inflector
393
442
 
443
+ Each loader instantiated with `Zeitwerk::Loader.new` has an inflector of this type by default.
444
+
394
445
  This is a very basic inflector that converts snake case to camel case:
395
446
 
396
447
  ```
@@ -417,16 +468,16 @@ loader.inflector.inflect "mysql_adapter" => "MySQLAdapter"
417
468
 
418
469
  Overrides need to be configured before calling `setup`.
419
470
 
420
- There are no inflection rules or global configuration that can affect this inflector. It is deterministic.
421
-
422
- Loaders instantiated with `Zeitwerk::Loader.new` have an inflector of this type, independent of each other.
471
+ The inflectors of different loaders are independent of each other. There are no global inflection rules or global configuration that can affect this inflector. It is deterministic.
423
472
 
424
473
  <a id="markdown-zeitwerkgeminflector" name="zeitwerkgeminflector"></a>
425
474
  #### Zeitwerk::GemInflector
426
475
 
476
+ Each loader instantiated with `Zeitwerk::Loader.for_gem` has an inflector of this type by default.
477
+
427
478
  This inflector is like the basic one, except it expects `lib/my_gem/version.rb` to define `MyGem::VERSION`.
428
479
 
429
- Loaders instantiated with `Zeitwerk::Loader.for_gem` have an inflector of this type, independent of each other.
480
+ The inflectors of different loaders are independent of each other. There are no global inflection rules or global configuration that can affect this inflector. It is deterministic.
430
481
 
431
482
  <a id="markdown-custom-inflector" name="custom-inflector"></a>
432
483
  #### Custom inflector
@@ -502,6 +553,109 @@ class MyGem::Inflector < Zeitwerk::GemInflector
502
553
  end
503
554
  ```
504
555
 
556
+ <a id="markdown-the-on_load-callback" name="the-on_load-callback"></a>
557
+ ### The on_load callback
558
+
559
+ The usual place to run something when a file is loaded is the file itself. However, sometimes you'd like to be called, and this is possible with the `on_load` callback.
560
+
561
+ For example, let's imagine this class belongs to a Rails application:
562
+
563
+ ```ruby
564
+ class SomeApiClient
565
+ class << self
566
+ attr_accessor :endpoint
567
+ end
568
+ end
569
+ ```
570
+
571
+ With `on_load`, it is easy to schedule code at boot time that initializes `endpoint` according to the configuration:
572
+
573
+ ```ruby
574
+ # config/environments/development.rb
575
+ loader.on_load("SomeApiClient") do |klass, _abspath|
576
+ klass.endpoint = "https://api.dev"
577
+ end
578
+
579
+ # config/environments/production.rb
580
+ loader.on_load("SomeApiClient") do |klass, _abspath|
581
+ klass.endpoint = "https://api.prod"
582
+ end
583
+ ```
584
+
585
+ Some uses cases:
586
+
587
+ * Doing something with a reloadable class or module in a Rails application during initialization, in a way that plays well with reloading. As in the previous example.
588
+ * Delaying the execution of the block until the class is loaded for performance.
589
+ * Delaying the execution of the block until the class is loaded because it follows the adapter pattern and better not to load the class if the user does not need it.
590
+
591
+ `on_load` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is loaded. That includes reloads.
592
+
593
+ Multiple callbacks on the same target are supported, and they run in order of definition.
594
+
595
+ The block is executed once the loader has loaded the target. In particular, if the target was already loaded when the callback is defined, the block won't run. But if you reload and load the target again, then it will. Normally, you'll want to define `on_load` callbacks before `setup`.
596
+
597
+ Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
598
+
599
+ It is also possible to be called when any constant managed by the loader is loaded:
600
+
601
+ ```ruby
602
+ loader.on_load do |cpath, value, abspath|
603
+ # ...
604
+ end
605
+ ```
606
+
607
+ The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
608
+
609
+ Multiple callbacks like these are supported, and they run in order of definition.
610
+
611
+ There are use cases for this last catch-all callback, but they are rare. If you just need to understand how things are being loaded for debugging purposes, please remember that `Zeitwerk::Loader#log!` logs plenty of information.
612
+
613
+ If both types of callbacks are defined, the specific ones run first.
614
+
615
+ <a id="markdown-the-on_unload-callback" name="the-on_unload-callback"></a>
616
+ ### The on_unload callback
617
+
618
+ When reloading is enabled, you may occasionally need to execute something before a certain autoloaded class or module is unloaded. The `on_unload` callback allows you to do that.
619
+
620
+ For example, let's imagine that a `Country` class fetches a list of countries and caches them when it is loaded. You might want to clear that cache if unloaded:
621
+
622
+ ```ruby
623
+ loader.on_unload("Country") do |klass, _abspath|
624
+ klass.clear_cache
625
+ end
626
+ ```
627
+
628
+ `on_unload` gets a target constant path as a string (e.g., "User", or "Service::NotificationsGateway"). When fired, its block receives the stored value, and the absolute path to the corresponding file or directory as a string. The callback is executed every time the target is unloaded.
629
+
630
+ `on_unload` blocks are executed before the class is unloaded, but in the middle of unloading, which happens in an unspecified order. Therefore, **that callback should not refer to any reloadable constant because there is no guarantee the constant works there**. Those blocks should rely on objects only, as in the example above, or regular constants not managed by the loader. This remark is transitive, applies to any methods invoked within the block.
631
+
632
+ Multiple callbacks on the same target are supported, and they run in order of definition.
633
+
634
+ Defining a callback for a target not managed by the receiver is not an error, the block simply won't ever be executed.
635
+
636
+ It is also possible to be called when any constant managed by the loader is unloaded:
637
+
638
+ ```ruby
639
+ loader.on_unload do |cpath, value, abspath|
640
+ # ...
641
+ end
642
+ ```
643
+
644
+ The block gets the constant path as a string (e.g., "User", or "Foo::VERSION"), the value it stores (e.g., the class object stored in `User`, or "2.5.0"), and the absolute path to the corresponding file or directory as a string.
645
+
646
+ Multiple callbacks like these are supported, and they run in order of definition.
647
+
648
+ If both types of callbacks are defined, the specific ones run first.
649
+
650
+ <a id="markdown-technical-details" name="technical-details"></a>
651
+ #### Technical details
652
+
653
+ Zeitwerk uses the word "unload" to ease communication and for symmetry with `on_load`. However, in Ruby you cannot unload things for real. So, when does `on_unload` technically happen?
654
+
655
+ When unloading, Zeitwerk issues `Module#remove_const` calls. Classes and modules are no longer reachable through their constants, and `on_unload` callbacks are executed right before those calls.
656
+
657
+ Technically, though, the objects themselves are still alive, but if everything is used as expected and they are not stored in any non-reloadable place (don't do that), they are ready for garbage collection, which is when the real unloading happens.
658
+
505
659
  <a id="markdown-logging" name="logging"></a>
506
660
  ### Logging
507
661
 
@@ -773,9 +927,19 @@ and run `bin/test`.
773
927
  <a id="markdown-motivation" name="motivation"></a>
774
928
  ## Motivation
775
929
 
776
- Since `require` has global side-effects, and there is no static way to verify that you have issued the `require` calls for code that your file depends on, in practice it is very easy to forget some. That introduces bugs that depend on the load order. Zeitwerk provides a way to forget about `require` in your own code, just name things following conventions and done.
930
+ <a id="markdown-kernerrequire-is-brittle" name="kernerrequire-is-brittle"></a>
931
+ ### Kerner#require is brittle
932
+
933
+ Since `require` has global side-effects, and there is no static way to verify that you have issued the `require` calls for code that your file depends on, in practice it is very easy to forget some. That introduces bugs that depend on the load order.
934
+
935
+ Also, if the project has namespaces, setting things up and getting client code to load things in a consistent way needs discipline. For example, `require "foo/bar"` may define `Foo`, instead of reopen it. That may be a broken window, giving place to superclass mismatches or partially-defined namespaces.
936
+
937
+ With Zeitwerk, you just name things following conventions and done. Things are available everywhere, and descend is always orderly. Without effort and without broken windows.
938
+
939
+ <a id="markdown-rails-autoloading-was-brittle" name="rails-autoloading-was-brittle"></a>
940
+ ### Rails autoloading was brittle
777
941
 
778
- On the other hand, autoloading in Rails is based on `const_missing`, which lacks fundamental information like the nesting and the resolution algorithm that was being used. Because of that, Rails autoloading is not able to match Ruby's semantics and that introduces a series of gotchas. The original goal of this project was to bring a better autoloading mechanism for Rails 6.
942
+ Autoloading in Rails was based on `const_missing` up to Rails 5. That callback lacks fundamental information like the nesting or the resolution algorithm being used. Because of that, Rails autoloading was not able to match Ruby's semantics, and that introduced a series of issues. Zeitwerk is based on a different technique and fixed Rails autoloading starting with Rails 6.
779
943
 
780
944
  <a id="markdown-thanks" name="thanks"></a>
781
945
  ## Thanks
@@ -0,0 +1,69 @@
1
+ module Zeitwerk
2
+ # @private
3
+ class Autoloads
4
+ # Maps crefs for which an autoload has been defined to the corresponding
5
+ # absolute path.
6
+ #
7
+ # [Object, :User] => "/Users/fxn/blog/app/models/user.rb"
8
+ # [Object, :Hotel] => "/Users/fxn/blog/app/models/hotel"
9
+ # ...
10
+ #
11
+ # This colection is transient, callbacks delete its entries as autoloads get
12
+ # executed.
13
+ #
14
+ # @sig Hash[[Module, Symbol], String]
15
+ attr_reader :c2a
16
+
17
+ # This is the inverse of c2a, for inverse lookups.
18
+ #
19
+ # @sig Hash[String, [Module, Symbol]]
20
+ attr_reader :a2c
21
+
22
+ # @sig () -> void
23
+ def initialize
24
+ @c2a = {}
25
+ @a2c = {}
26
+ end
27
+
28
+ # @sig (Module, Symbol, String) -> void
29
+ def define(parent, cname, abspath)
30
+ parent.autoload(cname, abspath)
31
+ cref = [parent, cname]
32
+ c2a[cref] = abspath
33
+ a2c[abspath] = cref
34
+ end
35
+
36
+ # @sig () { () -> [[Module, Symbol], String] } -> void
37
+ def each(&block)
38
+ c2a.each(&block)
39
+ end
40
+
41
+ # @sig (Module, Symbol) -> String?
42
+ def abspath_for(parent, cname)
43
+ c2a[[parent, cname]]
44
+ end
45
+
46
+ # @sig (String) -> [Module, Symbol]?
47
+ def cref_for(abspath)
48
+ a2c[abspath]
49
+ end
50
+
51
+ # @sig (String) -> [Module, Symbol]?
52
+ def delete(abspath)
53
+ cref = a2c.delete(abspath)
54
+ c2a.delete(cref)
55
+ cref
56
+ end
57
+
58
+ # @sig () -> void
59
+ def clear
60
+ c2a.clear
61
+ a2c.clear
62
+ end
63
+
64
+ # @sig () -> bool
65
+ def empty?
66
+ c2a.empty? && a2c.empty?
67
+ end
68
+ end
69
+ end