rbs 4.0.3 → 4.1.3
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 +4 -4
- data/.dockerignore +37 -0
- data/.github/dependabot.yml +1 -1
- data/.github/workflows/bundle-update.yml +2 -2
- data/.github/workflows/c-check.yml +14 -6
- data/.github/workflows/comments.yml +2 -2
- data/.github/workflows/dependabot.yml +2 -2
- data/.github/workflows/jruby.yml +74 -0
- data/.github/workflows/release-gems.yml +235 -0
- data/.github/workflows/ruby.yml +43 -3
- data/.github/workflows/rust.yml +13 -10
- data/.github/workflows/truffleruby.yml +54 -0
- data/.github/workflows/typecheck.yml +5 -2
- data/.github/workflows/wasm.yml +55 -0
- data/.github/workflows/windows.yml +5 -2
- data/.gitignore +8 -0
- data/CHANGELOG.md +113 -0
- data/Dockerfile.jruby +53 -0
- data/README.md +4 -4
- data/Rakefile +512 -82
- data/Steepfile +9 -0
- data/config.yml +26 -0
- data/core/array.rbs +243 -180
- data/core/builtin.rbs +7 -6
- data/core/class.rbs +5 -3
- data/core/enumerable.rbs +109 -109
- data/core/enumerator/product.rbs +5 -5
- data/core/enumerator.rbs +28 -28
- data/core/file.rbs +24 -1018
- data/core/file_constants.rbs +463 -0
- data/core/file_stat.rbs +534 -0
- data/core/float.rbs +0 -24
- data/core/hash.rbs +117 -101
- data/core/integer.rbs +21 -58
- data/core/io.rbs +25 -7
- data/core/kernel.rbs +26 -11
- data/core/match_data.rbs +1 -1
- data/core/module.rbs +88 -74
- data/core/numeric.rbs +3 -0
- data/core/object_space/weak_key_map.rbs +7 -7
- data/core/pathname.rbs +0 -10
- data/core/ractor.rbs +0 -10
- data/core/range.rbs +23 -23
- data/core/rbs/ops.rbs +154 -0
- data/core/rbs/unnamed/argf.rbs +3 -3
- data/core/ruby_vm.rbs +40 -0
- data/core/rubygems/errors.rbs +4 -1
- data/core/rubygems/requirement.rbs +0 -10
- data/core/rubygems/rubygems.rbs +4 -1
- data/core/rubygems/specification.rbs +8 -0
- data/core/rubygems/version.rbs +0 -160
- data/core/set.rbs +3 -3
- data/core/struct.rbs +16 -16
- data/core/thread.rbs +9 -14
- data/docs/CONTRIBUTING.md +2 -1
- data/docs/inline.md +65 -7
- data/docs/rbs_by_example.md +20 -20
- data/docs/release.md +247 -0
- data/docs/syntax.md +2 -2
- data/docs/wasm_serialization.md +80 -0
- data/ext/rbs_extension/ast_translation.c +1298 -956
- data/ext/rbs_extension/ast_translation.h +4 -0
- data/ext/rbs_extension/class_constants.c +2 -0
- data/ext/rbs_extension/class_constants.h +1 -0
- data/ext/rbs_extension/extconf.rb +1 -0
- data/ext/rbs_extension/legacy_location.c +11 -6
- data/ext/rbs_extension/main.c +139 -4
- data/include/rbs/ast.h +323 -298
- data/include/rbs/defines.h +18 -0
- data/include/rbs/lexer.h +1 -0
- data/include/rbs/serialize.h +39 -0
- data/lib/rbs/annotate/rdoc_annotator.rb +27 -31
- data/lib/rbs/ast/ruby/annotations.rb +42 -0
- data/lib/rbs/ast/ruby/comment_block.rb +6 -4
- data/lib/rbs/ast/ruby/declarations.rb +11 -1
- data/lib/rbs/ast/ruby/helpers/location_helper.rb +1 -1
- data/lib/rbs/ast/ruby/members.rb +40 -1
- data/lib/rbs/buffer.rb +48 -11
- data/lib/rbs/cli.rb +3 -5
- data/lib/rbs/collection/config/lockfile_generator.rb +14 -1
- data/lib/rbs/collection/sources/git.rb +6 -0
- data/lib/rbs/definition_builder/method_builder.rb +12 -6
- data/lib/rbs/environment.rb +10 -3
- data/lib/rbs/inline_parser.rb +54 -28
- data/lib/rbs/namespace.rb +47 -11
- data/lib/rbs/parser_aux.rb +4 -2
- data/lib/rbs/prototype/rbi.rb +193 -25
- data/lib/rbs/prototype/runtime.rb +2 -0
- data/lib/rbs/resolver/type_name_resolver.rb +12 -14
- data/lib/rbs/rewriter.rb +70 -0
- data/lib/rbs/test/type_check.rb +6 -1
- data/lib/rbs/type_name.rb +33 -13
- data/lib/rbs/unit_test/type_assertions.rb +14 -5
- data/lib/rbs/version.rb +1 -1
- data/lib/rbs/wasm/deserializer.rb +213 -0
- data/lib/rbs/wasm/location.rb +61 -0
- data/lib/rbs/wasm/parser.rb +137 -0
- data/lib/rbs/wasm/runtime.rb +196 -0
- data/lib/rbs/wasm/serialization_schema.rb +110 -0
- data/lib/rbs.rb +14 -2
- data/lib/rbs_jars.rb +39 -0
- data/lib/rdoc_plugin/parser.rb +5 -0
- data/rbs.gemspec +32 -2
- data/sig/annotate/rdoc_annotater.rbs +12 -9
- data/sig/ast/ruby/annotations.rbs +49 -0
- data/sig/ast/ruby/members.rbs +21 -1
- data/sig/buffer.rbs +19 -1
- data/sig/collection/config/lockfile_generator.rbs +2 -0
- data/sig/inline_parser.rbs +2 -0
- data/sig/manifest.yaml +0 -1
- data/sig/namespace.rbs +20 -0
- data/sig/parser.rbs +10 -0
- data/sig/prototype/rbi.rbs +33 -4
- data/sig/resolver/type_name_resolver.rbs +2 -4
- data/sig/rewriter.rbs +45 -0
- data/sig/typename.rbs +15 -0
- data/sig/unit_test/type_assertions.rbs +6 -2
- data/sig/wasm/deserializer.rbs +66 -0
- data/sig/wasm/serialization_schema.rbs +13 -0
- data/src/ast.c +186 -162
- data/src/lexer.c +220 -193
- data/src/lexer.re +2 -1
- data/src/lexstate.c +10 -1
- data/src/parser.c +61 -8
- data/src/serialize.c +958 -0
- data/src/util/rbs_allocator.c +14 -8
- data/stdlib/abbrev/0/array.rbs +1 -1
- data/stdlib/csv/0/csv.rbs +5 -5
- data/stdlib/delegate/0/delegator.rbs +2 -1
- data/stdlib/digest/0/digest.rbs +11 -5
- data/stdlib/erb/0/erb.rbs +1 -1
- data/stdlib/etc/0/etc.rbs +18 -4
- data/stdlib/fileutils/0/fileutils.rbs +21 -21
- data/stdlib/ipaddr/0/ipaddr.rbs +0 -5
- data/stdlib/json/0/json.rbs +6 -6
- data/stdlib/monitor/0/monitor.rbs +2 -2
- data/stdlib/openssl/0/openssl.rbs +46 -40
- data/stdlib/resolv/0/resolv.rbs +1 -1
- data/stdlib/shellwords/0/shellwords.rbs +1 -1
- data/stdlib/stringio/0/stringio.rbs +32 -10
- data/stdlib/strscan/0/string_scanner.rbs +74 -55
- data/stdlib/tempfile/0/manifest.yaml +3 -0
- data/stdlib/timeout/0/timeout.rbs +0 -5
- data/stdlib/tsort/0/cyclic.rbs +1 -1
- data/stdlib/tsort/0/interfaces.rbs +8 -8
- data/stdlib/tsort/0/tsort.rbs +9 -9
- data/stdlib/uri/0/generic.rbs +0 -5
- data/stdlib/zlib/0/gzip_reader.rbs +2 -2
- data/stdlib/zlib/0/zstream.rbs +0 -1
- data/wasm/README.md +93 -0
- data/wasm/rbs_wasm.c +423 -0
- metadata +29 -6
- data/.github/workflows/milestone.yml +0 -80
- data/.vscode/extensions.json +0 -5
- data/.vscode/settings.json +0 -19
data/core/struct.rbs
CHANGED
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
# * #inspect (aliased as #to_s): Returns a string representation of `self`.
|
|
105
105
|
# * #to_h: Returns a hash of the member name/value pairs in `self`.
|
|
106
106
|
#
|
|
107
|
-
class Struct[
|
|
108
|
-
include Enumerable[
|
|
107
|
+
class Struct[E]
|
|
108
|
+
include Enumerable[E]
|
|
109
109
|
|
|
110
110
|
# The types that can be used when "indexing" into a `Struct` via `[]`, `[]=`, `dig`, and
|
|
111
111
|
# `deconstruct_keys`.
|
|
@@ -362,7 +362,7 @@ class Struct[Elem]
|
|
|
362
362
|
#
|
|
363
363
|
# Related: #members.
|
|
364
364
|
#
|
|
365
|
-
def to_a: () -> Array[
|
|
365
|
+
def to_a: () -> Array[E]
|
|
366
366
|
|
|
367
367
|
# <!--
|
|
368
368
|
# rdoc-file=struct.c
|
|
@@ -385,8 +385,8 @@ class Struct[Elem]
|
|
|
385
385
|
#
|
|
386
386
|
# Raises ArgumentError if the block returns an inappropriate value.
|
|
387
387
|
#
|
|
388
|
-
def to_h: () -> Hash[Symbol,
|
|
389
|
-
| [K, V] () { (Symbol key,
|
|
388
|
+
def to_h: () -> Hash[Symbol, E]
|
|
389
|
+
| [K, V] () { (Symbol key, E value) -> [K, V] } -> Hash[K, V]
|
|
390
390
|
|
|
391
391
|
# <!-- rdoc-file=struct.c -->
|
|
392
392
|
# Returns the values in `self` as an array:
|
|
@@ -441,8 +441,8 @@ class Struct[Elem]
|
|
|
441
441
|
#
|
|
442
442
|
# Related: #each_pair.
|
|
443
443
|
#
|
|
444
|
-
def each: () -> Enumerator[
|
|
445
|
-
| () { (
|
|
444
|
+
def each: () -> Enumerator[E, self]
|
|
445
|
+
| () { (E value) -> void } -> self
|
|
446
446
|
|
|
447
447
|
# <!--
|
|
448
448
|
# rdoc-file=struct.c
|
|
@@ -465,8 +465,8 @@ class Struct[Elem]
|
|
|
465
465
|
#
|
|
466
466
|
# Related: #each.
|
|
467
467
|
#
|
|
468
|
-
def each_pair: () -> Enumerator[[Symbol,
|
|
469
|
-
| () { ([Symbol,
|
|
468
|
+
def each_pair: () -> Enumerator[[Symbol, E], self]
|
|
469
|
+
| () { ([Symbol, E] key_value) -> void } -> self
|
|
470
470
|
|
|
471
471
|
# <!--
|
|
472
472
|
# rdoc-file=struct.c
|
|
@@ -492,7 +492,7 @@ class Struct[Elem]
|
|
|
492
492
|
#
|
|
493
493
|
# Raises IndexError if `n` is out of range.
|
|
494
494
|
#
|
|
495
|
-
def []: (index name_or_position) ->
|
|
495
|
+
def []: (index name_or_position) -> E
|
|
496
496
|
|
|
497
497
|
# <!--
|
|
498
498
|
# rdoc-file=struct.c
|
|
@@ -521,7 +521,7 @@ class Struct[Elem]
|
|
|
521
521
|
#
|
|
522
522
|
# Raises IndexError if `n` is out of range.
|
|
523
523
|
#
|
|
524
|
-
def []=: (index name_or_position,
|
|
524
|
+
def []=: (index name_or_position, E value) -> E
|
|
525
525
|
|
|
526
526
|
# <!--
|
|
527
527
|
# rdoc-file=struct.c
|
|
@@ -540,8 +540,8 @@ class Struct[Elem]
|
|
|
540
540
|
#
|
|
541
541
|
# With no block given, returns an Enumerator.
|
|
542
542
|
#
|
|
543
|
-
def select: () -> Enumerator[
|
|
544
|
-
| () { (
|
|
543
|
+
def select: () -> Enumerator[E, Array[E]]
|
|
544
|
+
| () { (E value) -> boolish } -> Array[E]
|
|
545
545
|
|
|
546
546
|
# <!-- rdoc-file=struct.c -->
|
|
547
547
|
# With a block given, returns an array of values from `self` for which the block
|
|
@@ -591,7 +591,7 @@ class Struct[Elem]
|
|
|
591
591
|
# Raises RangeError if any element of the range is negative and out of range;
|
|
592
592
|
# see Array@Array+Indexes.
|
|
593
593
|
#
|
|
594
|
-
def values_at: (*int | range[int?] positions) -> Array[
|
|
594
|
+
def values_at: (*int | range[int?] positions) -> Array[E]
|
|
595
595
|
|
|
596
596
|
# <!--
|
|
597
597
|
# rdoc-file=struct.c
|
|
@@ -634,7 +634,7 @@ class Struct[Elem]
|
|
|
634
634
|
# f.dig(0, 0, :b, 0) # => 1
|
|
635
635
|
# f.dig(:b, 0) # => nil
|
|
636
636
|
#
|
|
637
|
-
def dig: (index name_or_position) ->
|
|
637
|
+
def dig: (index name_or_position) -> E
|
|
638
638
|
| (index name_or_position, untyped, *untyped) -> untyped
|
|
639
639
|
|
|
640
640
|
# <!-- rdoc-file=struct.c -->
|
|
@@ -664,5 +664,5 @@ class Struct[Elem]
|
|
|
664
664
|
# h = joe.deconstruct_keys(nil)
|
|
665
665
|
# h # => {:name=>"Joseph Smith, Jr.", :address=>"123 Maple, Anytown NC", :zip=>12345}
|
|
666
666
|
#
|
|
667
|
-
def deconstruct_keys: (Array[index & Hash::_Key]? indices) -> Hash[index & Hash::_Key,
|
|
667
|
+
def deconstruct_keys: (Array[index & Hash::_Key]? indices) -> Hash[index & Hash::_Key, E]
|
|
668
668
|
end
|
data/core/thread.rbs
CHANGED
|
@@ -1565,10 +1565,10 @@ class Thread::Mutex < Object
|
|
|
1565
1565
|
|
|
1566
1566
|
# <!--
|
|
1567
1567
|
# rdoc-file=thread_sync.rb
|
|
1568
|
-
# - mutex.
|
|
1568
|
+
# - mutex.unlock -> self
|
|
1569
1569
|
# -->
|
|
1570
|
-
#
|
|
1571
|
-
#
|
|
1570
|
+
# Releases the lock. Raises `ThreadError` if `mutex` wasn't locked by the
|
|
1571
|
+
# current thread.
|
|
1572
1572
|
#
|
|
1573
1573
|
def unlock: () -> self
|
|
1574
1574
|
end
|
|
@@ -1604,7 +1604,7 @@ end
|
|
|
1604
1604
|
#
|
|
1605
1605
|
# consumer.join
|
|
1606
1606
|
#
|
|
1607
|
-
class Thread::Queue[
|
|
1607
|
+
class Thread::Queue[E = untyped] < Object
|
|
1608
1608
|
# <!-- rdoc-file=thread_sync.c -->
|
|
1609
1609
|
# Pushes the given `object` to the queue.
|
|
1610
1610
|
#
|
|
@@ -1719,7 +1719,7 @@ class Thread::Queue[Elem = untyped] < Object
|
|
|
1719
1719
|
# If `timeout` seconds have passed and no data is available `nil` is returned.
|
|
1720
1720
|
# If `timeout` is `0` it returns immediately.
|
|
1721
1721
|
#
|
|
1722
|
-
def pop: (?boolish non_block, ?timeout: _ToF?) ->
|
|
1722
|
+
def pop: (?boolish non_block, ?timeout: _ToF?) -> E?
|
|
1723
1723
|
|
|
1724
1724
|
# <!--
|
|
1725
1725
|
# rdoc-file=thread_sync.c
|
|
@@ -1729,7 +1729,7 @@ class Thread::Queue[Elem = untyped] < Object
|
|
|
1729
1729
|
# -->
|
|
1730
1730
|
# Pushes the given `object` to the queue.
|
|
1731
1731
|
#
|
|
1732
|
-
def push: (
|
|
1732
|
+
def push: (E obj) -> void
|
|
1733
1733
|
|
|
1734
1734
|
# <!--
|
|
1735
1735
|
# rdoc-file=thread_sync.rb
|
|
@@ -1750,12 +1750,7 @@ end
|
|
|
1750
1750
|
#
|
|
1751
1751
|
# See Thread::Queue for an example of how a Thread::SizedQueue works.
|
|
1752
1752
|
#
|
|
1753
|
-
class Thread::SizedQueue[
|
|
1754
|
-
# <!--
|
|
1755
|
-
# rdoc-file=thread_sync.rb
|
|
1756
|
-
# - <<(object, non_block = false, timeout: nil)
|
|
1757
|
-
# -->
|
|
1758
|
-
#
|
|
1753
|
+
class Thread::SizedQueue[E = untyped] < Thread::Queue[E]
|
|
1759
1754
|
alias << push
|
|
1760
1755
|
|
|
1761
1756
|
# <!--
|
|
@@ -1813,8 +1808,8 @@ class Thread::SizedQueue[Elem = untyped] < Thread::Queue[Elem]
|
|
|
1813
1808
|
# If `timeout` seconds have passed and no space is available `nil` is returned.
|
|
1814
1809
|
# If `timeout` is `0` it returns immediately. Otherwise it returns `self`.
|
|
1815
1810
|
#
|
|
1816
|
-
def push: (
|
|
1817
|
-
| (
|
|
1811
|
+
def push: (E obj, ?boolish non_block) -> void
|
|
1812
|
+
| (E obj, timeout: _ToF?) -> self?
|
|
1818
1813
|
end
|
|
1819
1814
|
|
|
1820
1815
|
class ConditionVariable = Thread::ConditionVariable
|
data/docs/CONTRIBUTING.md
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
The RBS repository contains the type definitions of Core API and Standard Libraries.
|
|
13
13
|
There are some discussions whether if it is the best to have them in this repository, but we have them and continue updating the files meanwhile.
|
|
14
14
|
|
|
15
|
-
The target version of the bundled type definitions is the latest _release_ of Ruby -- `
|
|
15
|
+
The target version of the bundled type definitions is the [latest _release_ of Ruby](https://www.ruby-lang.org/en/downloads/branches/) -- `4.0` as of 2026.
|
|
16
|
+
Note, however, that the CI runs the tests on every non-EOL Ruby -- `3.3` and later as of 2026.
|
|
16
17
|
|
|
17
18
|
**The core API** type definitions are in `core` directory.
|
|
18
19
|
You will find the familiar class names in the directory, like `string.rbs` or `array.rbs`.
|
data/docs/inline.md
CHANGED
|
@@ -123,10 +123,38 @@ end
|
|
|
123
123
|
|
|
124
124
|
This creates the types `::API`, `::API::V1`, and `::API::V1::Resources`.
|
|
125
125
|
|
|
126
|
+
### `module-self` constraint
|
|
127
|
+
|
|
128
|
+
The `module-self` constraint declares which classes or modules the module can be mixed into.
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
# @rbs module-self: _Each[String]
|
|
132
|
+
module Enumerable2
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This is equivalent to `module Enumerable2 : _Each[String]` in RBS, meaning `Enumerable2` can only be included in classes that satisfy the `_Each[String]` interface.
|
|
137
|
+
|
|
138
|
+
Multiple `module-self` constraints can be declared with separate annotations:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
# @rbs module-self: _Each[String]
|
|
142
|
+
# @rbs module-self: Comparable
|
|
143
|
+
module StringCollection
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
You can add a description after `--`:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
# @rbs module-self: Minitest::Test -- depending on assertion methods
|
|
151
|
+
module TestHelper
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
126
155
|
### Current Limitations
|
|
127
156
|
|
|
128
157
|
- Generic module definitions are not supported
|
|
129
|
-
- Module self-type constraints are not supported
|
|
130
158
|
|
|
131
159
|
## Method Definitions
|
|
132
160
|
|
|
@@ -135,6 +163,8 @@ Inline RBS supports methods defined using the `def` syntax in Ruby.
|
|
|
135
163
|
```ruby
|
|
136
164
|
class Calculator
|
|
137
165
|
def add(x, y) = x+y
|
|
166
|
+
|
|
167
|
+
def self.zero = 0
|
|
138
168
|
end
|
|
139
169
|
```
|
|
140
170
|
|
|
@@ -205,6 +235,18 @@ The type of both methods is `(Integer, Integer) -> Integer | (Float, Float) -> F
|
|
|
205
235
|
> The `@rbs METHOD-TYPE` syntax allows overloads with the `|` operator, just like in RBS files.
|
|
206
236
|
> Multiple `: METHOD-TYPE` declarations are required for overloads.
|
|
207
237
|
|
|
238
|
+
The `#:` syntax can also be used as a trailing annotation to declare the return type of a method:
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
class Calculator
|
|
242
|
+
def add(x, y) #: Integer
|
|
243
|
+
x + y
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def subtract(x, y) = x - y #: Integer
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
208
250
|
The `@rbs METHOD-TYPE` syntax allows having `...` at the last part.
|
|
209
251
|
|
|
210
252
|
```ruby
|
|
@@ -309,9 +351,10 @@ end
|
|
|
309
351
|
|
|
310
352
|
### Current Limitations
|
|
311
353
|
|
|
312
|
-
- Class methods and singleton methods are not supported
|
|
313
|
-
- Only positional and keyword parameters are supported. Splat parameters (`*x`, `**y`) and block parameter (`&block`) are not supported yet.
|
|
314
354
|
- Method visibility declaration is not supported yet
|
|
355
|
+
- The `class << self` syntax is not supported
|
|
356
|
+
- Top-level method definitions (outside any class/module) are not supported
|
|
357
|
+
- Method definitions with a non-self receiver (e.g. `def obj.foo`) are not supported
|
|
315
358
|
|
|
316
359
|
## Attributes
|
|
317
360
|
|
|
@@ -389,6 +432,7 @@ The attribute definitions are ignored because the names are given by string lite
|
|
|
389
432
|
### Current Limitations
|
|
390
433
|
|
|
391
434
|
- Attribute visibility is not supported yet. All attributes are _public_
|
|
435
|
+
- Top-level attribute definitions (outside any class/module) are not supported
|
|
392
436
|
|
|
393
437
|
## Mixin
|
|
394
438
|
|
|
@@ -487,7 +531,7 @@ end
|
|
|
487
531
|
```
|
|
488
532
|
|
|
489
533
|
The `@rbs @VAR-NAME: TYPE` syntax enclosed in `class`/`module` syntax declares instance variables.
|
|
490
|
-
You can add the documentation of the variable followed by two
|
|
534
|
+
You can add the documentation of the variable followed by two hyphens (`--`).
|
|
491
535
|
|
|
492
536
|
Instance variable declarations must be under the `class`/`module` syntax, and they are ignored if written inside method definitions.
|
|
493
537
|
|
|
@@ -519,14 +563,16 @@ The types of constants may be automatically inferred when the right-hand side co
|
|
|
519
563
|
- **Floats**: `RATE = 3.14` → `Float`
|
|
520
564
|
- **Booleans**: `ENABLED = true` → `bool`
|
|
521
565
|
- **Strings**: `NAME = "test"` → `String`
|
|
522
|
-
- **Symbols**: `STATUS = :ready` →
|
|
566
|
+
- **Symbols**: `STATUS = :ready` → `Symbol`
|
|
567
|
+
- **Nil**: `EMPTY = nil` → `nil`
|
|
523
568
|
|
|
524
569
|
```ruby
|
|
525
570
|
MAX_SIZE = 100 # Inferred as Integer
|
|
526
571
|
PI = 3.14159 # Inferred as Float
|
|
527
572
|
DEBUG = false # Inferred as bool
|
|
528
573
|
APP_NAME = "MyApp" # Inferred as String
|
|
529
|
-
DEFAULT_MODE = :strict # Inferred as
|
|
574
|
+
DEFAULT_MODE = :strict # Inferred as Symbol
|
|
575
|
+
NONE = nil # Inferred as nil
|
|
530
576
|
```
|
|
531
577
|
|
|
532
578
|
### Explicit Type Annotations
|
|
@@ -567,10 +613,22 @@ MyKernel = Kernel #: module-alias
|
|
|
567
613
|
|
|
568
614
|
This creates new type names that refer to the same class or module as the original.
|
|
569
615
|
|
|
570
|
-
The annotations can have optional type name to specify the class/module name, for the case it cannot be
|
|
616
|
+
The annotations can have optional type name to specify the class/module name, for the case it cannot be inferred through the right-hand-side of the constant declaration.
|
|
571
617
|
|
|
572
618
|
```ruby
|
|
573
619
|
MyObject = object #: class-alias Object
|
|
574
620
|
|
|
575
621
|
MyKernel = kernel #: module-alias Kernel
|
|
576
622
|
```
|
|
623
|
+
|
|
624
|
+
## Skip Annotation
|
|
625
|
+
|
|
626
|
+
The `@rbs skip` annotation makes inline RBS ignore the following declaration.
|
|
627
|
+
|
|
628
|
+
```ruby
|
|
629
|
+
class Calculator
|
|
630
|
+
# @rbs skip
|
|
631
|
+
def debug_internal
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
```
|
data/docs/rbs_by_example.md
CHANGED
|
@@ -107,14 +107,14 @@ end
|
|
|
107
107
|
For now, it's safe to ignore them, but they're included for completeness.
|
|
108
108
|
|
|
109
109
|
```rbs
|
|
110
|
-
class Array[
|
|
110
|
+
class Array[E]
|
|
111
111
|
def *: (String) -> String
|
|
112
|
-
| (Integer) -> Array[
|
|
112
|
+
| (Integer) -> Array[E]
|
|
113
113
|
end
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
`Array`'s `*` method, when given a `String` returns a `String`. When given an
|
|
117
|
-
`Integer`, it returns an `Array` of the same contained type `
|
|
117
|
+
`Integer`, it returns an `Array` of the same contained type `E` (in our example case, `E` corresponds to `Integer`).
|
|
118
118
|
|
|
119
119
|
### Union types
|
|
120
120
|
|
|
@@ -150,9 +150,9 @@ end
|
|
|
150
150
|
```
|
|
151
151
|
|
|
152
152
|
```rbs
|
|
153
|
-
class Enumerable[
|
|
154
|
-
def first: () ->
|
|
155
|
-
| (Integer) -> Array[
|
|
153
|
+
class Enumerable[E]
|
|
154
|
+
def first: () -> E?
|
|
155
|
+
| (Integer) -> Array[E]
|
|
156
156
|
end
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -160,12 +160,12 @@ end
|
|
|
160
160
|
|
|
161
161
|
When called with no arguments, the return value will either be an instance of
|
|
162
162
|
whatever type is contained in the enumerable, or `nil`. We represent that with
|
|
163
|
-
the type variable `
|
|
163
|
+
the type variable `E`, and the `?` suffix nilable marker.
|
|
164
164
|
|
|
165
165
|
When called with an `Integer` positional argument, the return value will be an
|
|
166
166
|
`Array` of whatever type is contained.
|
|
167
167
|
|
|
168
|
-
The `?` syntax is a convenient shorthand for a union with nil. An equivalent union type would be `(
|
|
168
|
+
The `?` syntax is a convenient shorthand for a union with nil. An equivalent union type would be `(E | nil)`.
|
|
169
169
|
|
|
170
170
|
### Keyword Arguments
|
|
171
171
|
|
|
@@ -222,9 +222,9 @@ end
|
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
```rbs
|
|
225
|
-
class Array[
|
|
226
|
-
def filter: () { (
|
|
227
|
-
| () -> ::Enumerator[
|
|
225
|
+
class Array[E]
|
|
226
|
+
def filter: () { (E) -> boolish } -> ::Array[E]
|
|
227
|
+
| () -> ::Enumerator[E, ::Array[E]]
|
|
228
228
|
end
|
|
229
229
|
```
|
|
230
230
|
|
|
@@ -264,13 +264,13 @@ a.collect.with_index {|x, i| x * i}
|
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
```rbs
|
|
267
|
-
class Array[
|
|
268
|
-
def collect: [U] () { (
|
|
269
|
-
| () -> Enumerator[
|
|
267
|
+
class Array[E]
|
|
268
|
+
def collect: [U] () { (E) -> U } -> Array[U]
|
|
269
|
+
| () -> Enumerator[E, Array[untyped]]
|
|
270
270
|
end
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
Type variables can also be introduced in methods. Here, in `Array`'s `#collect` method, we introduce a type variable `U`. The block passed to `#collect` will receive a parameter of type `
|
|
273
|
+
Type variables can also be introduced in methods. Here, in `Array`'s `#collect` method, we introduce a type variable `U`. The block passed to `#collect` will receive a parameter of type `E`, and return a value of type `U`. Then `#collect` will return an `Array` of type `U`.
|
|
274
274
|
|
|
275
275
|
In this example, the method receives its signature from the inferred return type of the passed block. When then block is absent, as in when the method returns an `Enumerator`, we can't infer the type, and so the return value of the enumerator can only be described as `Array[untyped]`.
|
|
276
276
|
|
|
@@ -284,9 +284,9 @@ In this example, the method receives its signature from the inferred return type
|
|
|
284
284
|
```
|
|
285
285
|
|
|
286
286
|
```rbs
|
|
287
|
-
class Enumerable[
|
|
288
|
-
def partition: () { (
|
|
289
|
-
| () -> ::Enumerator[
|
|
287
|
+
class Enumerable[E]
|
|
288
|
+
def partition: () { (E) -> boolish } -> [Array[E], Array[E]]
|
|
289
|
+
| () -> ::Enumerator[E, [Array[E], Array[E] ]]
|
|
290
290
|
end
|
|
291
291
|
```
|
|
292
292
|
|
|
@@ -300,9 +300,9 @@ Tuples can be of any size, and they can have mixed types.
|
|
|
300
300
|
```
|
|
301
301
|
|
|
302
302
|
```rbs
|
|
303
|
-
class Enumerable[
|
|
303
|
+
class Enumerable[E]
|
|
304
304
|
def to_h: () -> ::Hash[untyped, untyped]
|
|
305
|
-
| [T, U] () { (
|
|
305
|
+
| [T, U] () { (E) -> [T, U] } -> ::Hash[T, U]
|
|
306
306
|
end
|
|
307
307
|
```
|
|
308
308
|
|
data/docs/release.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Releasing RBS
|
|
2
|
+
|
|
3
|
+
A release is a pull request and one workflow run. Everything that leaves the
|
|
4
|
+
repository — the tag, both gems, and the GitHub release — is produced by the
|
|
5
|
+
`Release gems` workflow, so nothing has to be built or pushed from a laptop.
|
|
6
|
+
|
|
7
|
+
Each release ships **two gems**:
|
|
8
|
+
|
|
9
|
+
| Gem | Platform | Parser |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension, compiled on install |
|
|
12
|
+
| `rbs-X.Y.Z-java.gem` | `java` (JRuby) | `rbs_parser.wasm`, built by the workflow |
|
|
13
|
+
|
|
14
|
+
The `-java` gem contains no native code — just `rbs_parser.wasm`. The Chicory/ASM
|
|
15
|
+
jars it needs are not shipped in the gem; they are declared as `jar-dependencies`
|
|
16
|
+
requirements and fetched from Maven when the gem is installed. So the gem can be
|
|
17
|
+
built once in any environment and runs on every JRuby.
|
|
18
|
+
|
|
19
|
+
There are three kinds of release, and they differ in what gets written up:
|
|
20
|
+
|
|
21
|
+
| Version | CHANGELOG section | GitHub release |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| `X.Y.Z` | The whole cycle since the previous release proper, prereleases included | Published |
|
|
24
|
+
| `X.Y.Z.pre.N` | What changed since `X.Y.Z.pre.N-1` | Published, marked as a prerelease |
|
|
25
|
+
| `X.Y.Z.dev.N` | None | None |
|
|
26
|
+
|
|
27
|
+
`.dev.N` releases are cut from the development line for people who need a change
|
|
28
|
+
early, so they are gems and tags and nothing else.
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
Push rights to the `rbs` gem on RubyGems are **not** needed: the workflow
|
|
33
|
+
authenticates through a trusted publisher registered for this repository and
|
|
34
|
+
`release-gems.yml`. What is needed is write access to the repository, since that
|
|
35
|
+
is what lets you dispatch the workflow.
|
|
36
|
+
|
|
37
|
+
## Steps
|
|
38
|
+
|
|
39
|
+
The release pull request in step 1 is merged by a person who has reviewed it. Its merge commit is
|
|
40
|
+
what step 2 dispatches, tags, and pushes to RubyGems, and none of that can be taken back — so
|
|
41
|
+
prepare that pull request and stop there, rather than merging it and carrying on to step 2.
|
|
42
|
+
|
|
43
|
+
The bump that starts a new minor is the only other pull request that sets `RBS::VERSION`. It
|
|
44
|
+
publishes nothing and another bump undoes it, so one opened on an explicit request can go through
|
|
45
|
+
on its own.
|
|
46
|
+
|
|
47
|
+
### 1. Prepare the release
|
|
48
|
+
|
|
49
|
+
Open a pull request that carries everything the release needs:
|
|
50
|
+
|
|
51
|
+
- `lib/rbs/version.rb` — set `RBS::VERSION` to the version being released.
|
|
52
|
+
- `Gemfile.lock` — run `bundle install` after the bump; the lockfile records the version too.
|
|
53
|
+
- `CHANGELOG.md` — add a section for the new version, directly under the `# CHANGELOG` heading.
|
|
54
|
+
Sections are newest first.
|
|
55
|
+
|
|
56
|
+
Label the pull request `skip-changelog`. It carries no change of its own, and without the label it
|
|
57
|
+
shows up in the next release's list — that is why 4.1.0's changelog contains a `Version 4.1.0`
|
|
58
|
+
entry.
|
|
59
|
+
|
|
60
|
+
`rake gem:changelog` lists the pull requests merged since the last release, already formatted:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
$ bundle exec rake gem:changelog | pbcopy
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Where it starts follows `RBS::VERSION`, so bump the version first: a prerelease starts from the
|
|
67
|
+
latest tag, and a release proper skips the prerelease tags and starts from the previous release
|
|
68
|
+
proper. Pass a version to override it (`rake 'gem:changelog[4.1.0]'`). Only the list goes to
|
|
69
|
+
STDOUT, so it pipes cleanly. Pull requests labeled `skip-changelog` are left out and reported on
|
|
70
|
+
STDERR, and pull requests that only touch `rust/` are left out because the crates have their own
|
|
71
|
+
release cycle.
|
|
72
|
+
|
|
73
|
+
On a release proper, the `X.Y.Z.pre.N` sections above the previous release are replaced by the one
|
|
74
|
+
section being written — their pull requests are in it, and the notes they were published with stay
|
|
75
|
+
on their own GitHub releases.
|
|
76
|
+
|
|
77
|
+
Sort the list into the sections below. `rake gem:changelog:json` prints the same pull requests with
|
|
78
|
+
the changed files, labels, and body of each, which is what the sorting is based on.
|
|
79
|
+
|
|
80
|
+
```markdown
|
|
81
|
+
## X.Y.Z (YYYY-MM-DD)
|
|
82
|
+
|
|
83
|
+
### Signature updates
|
|
84
|
+
|
|
85
|
+
### Language updates
|
|
86
|
+
|
|
87
|
+
### Library changes
|
|
88
|
+
|
|
89
|
+
#### rbs prototype
|
|
90
|
+
|
|
91
|
+
#### rbs collection
|
|
92
|
+
|
|
93
|
+
### Miscellaneous
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The sections always appear in this order; delete the ones that end up empty, which is most of them
|
|
97
|
+
on a small release. Two things scale with the size of the release:
|
|
98
|
+
|
|
99
|
+
- **Summary paragraphs**, above the first section. A patch release usually has none, 4.1.0 has four
|
|
100
|
+
paragraphs, and 4.0.0 has nine.
|
|
101
|
+
- **A list of the types whose signatures changed**, as the first line of `### Signature updates`,
|
|
102
|
+
written as `**Updated classes/modules/methods:**` followed by the names in backticks. Used on
|
|
103
|
+
`X.Y.0` releases only.
|
|
104
|
+
|
|
105
|
+
The date is the day the gem is released, matching the `vX.Y.Z` tag — not the day this pull request
|
|
106
|
+
is opened. Fix it up before step 2 if the pull request sat for a few days.
|
|
107
|
+
|
|
108
|
+
### 2. Run the `Release gems` workflow
|
|
109
|
+
|
|
110
|
+
Once the pull request is merged, dispatch
|
|
111
|
+
[`release-gems.yml`](../.github/workflows/release-gems.yml) from the Actions tab with two inputs:
|
|
112
|
+
|
|
113
|
+
| Input | Value |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `commit` | The full 40-character SHA of the merge commit, taken from the merged pull request |
|
|
116
|
+
| `version` | `X.Y.Z`, without the leading `v` |
|
|
117
|
+
|
|
118
|
+
The ref selector picks which copy of the workflow file runs, not what gets released — leave it on
|
|
119
|
+
`master`. Everything is built from `commit`, so the run is unaffected by whatever lands on `master`
|
|
120
|
+
in the meantime, and a patch release cut from a release branch is dispatched the same way as any
|
|
121
|
+
other: the workflow does not care which branch the commit is on.
|
|
122
|
+
|
|
123
|
+
The two inputs say the same thing twice, once as a commit and once as a name, and the run stops
|
|
124
|
+
before anything is built unless they agree with each other and with the repository:
|
|
125
|
+
|
|
126
|
+
- `commit` has to be a full SHA that some branch contains,
|
|
127
|
+
- `version` has to be the `RBS::VERSION` that commit declares,
|
|
128
|
+
- CHANGELOG.md has to start with a section for `version` (skipped for `.dev.N`, which is not
|
|
129
|
+
written up),
|
|
130
|
+
- `vX.Y.Z` must not exist yet.
|
|
131
|
+
|
|
132
|
+
It then:
|
|
133
|
+
|
|
134
|
+
- builds `rbs-X.Y.Z.gem`,
|
|
135
|
+
- compiles `rbs_parser.wasm` and builds `rbs-X.Y.Z-java.gem`,
|
|
136
|
+
- checks both: platforms, the C extension on one and its absence on the other, and that the wasm
|
|
137
|
+
module made it into the `java` gem,
|
|
138
|
+
- installs the `java` gem on JRuby and parses with it, so the WebAssembly runtime is exercised
|
|
139
|
+
before anything is published,
|
|
140
|
+
- uploads both gems as an artifact,
|
|
141
|
+
- tags `commit` as `vX.Y.Z` and pushes the tag,
|
|
142
|
+
- pushes both gems to RubyGems through trusted publishing,
|
|
143
|
+
- publishes the GitHub release with the notes from CHANGELOG.md, skipping this last step for
|
|
144
|
+
`.dev.N` versions.
|
|
145
|
+
|
|
146
|
+
The tag is created once both gems are known to build and run, and before anything is published: a
|
|
147
|
+
tag can be deleted, while a version pushed to RubyGems can only be yanked.
|
|
148
|
+
|
|
149
|
+
Checking the `dry_run` box runs everything up to the artifact and stops — no tag, no gems pushed,
|
|
150
|
+
no release — which is how the build is exercised without releasing. `version` still has to match
|
|
151
|
+
the commit, so a dry run is also how a release is rehearsed before it is cut.
|
|
152
|
+
|
|
153
|
+
## The version on `master`
|
|
154
|
+
|
|
155
|
+
`RBS::VERSION` on `master` is read one of two ways, told apart by how the version ends:
|
|
156
|
+
|
|
157
|
+
| On `master` | Means |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `X.Y.0.dev` — a bare `.dev` | `X.Y.0` is being developed |
|
|
160
|
+
| A complete version — `X.Y.Z`, `X.Y.Z.pre.N`, `X.Y.Z.dev.N` | The version *after* the one named is being developed |
|
|
161
|
+
|
|
162
|
+
So `4.1.1` on `master` is not a claim that `master` is 4.1.1. It says 4.1.1 has shipped and what
|
|
163
|
+
comes after it is being worked on. `4.1.2.dev.1` says the same thing about itself: that release is
|
|
164
|
+
out, and the line continues towards 4.1.2.
|
|
165
|
+
|
|
166
|
+
Both become true the moment the release is tagged, so **nothing has to be done to `master` after a
|
|
167
|
+
release**. `4.0.1` was followed by `4.0.2` with no version change in between, and `4.1.2.dev.1` is
|
|
168
|
+
what `master` carries today.
|
|
169
|
+
|
|
170
|
+
The bare `X.Y.0.dev` is the exception because it is the one version that names a target rather than
|
|
171
|
+
a predecessor: a new minor is developed towards `X.Y.0` for a long time, before it is known whether
|
|
172
|
+
the next thing to ship is `X.Y.0.pre.1` or `X.Y.0` itself. Setting it is the only version change
|
|
173
|
+
that has to be made deliberately.
|
|
174
|
+
|
|
175
|
+
`rake gem:changelog` reads `RBS::VERSION` too, to decide where the next changelog starts — but the
|
|
176
|
+
version is set to the one being released before the changelog is generated, so it sees that rather
|
|
177
|
+
than whatever `master` was carrying.
|
|
178
|
+
|
|
179
|
+
## Starting a new minor
|
|
180
|
+
|
|
181
|
+
`master` is the development line of one minor at a time. Moving it from `X.Y` to `X.(Y+1)` is not
|
|
182
|
+
part of any one release — it is the decision that the `X.Y` line is done, taken whenever that
|
|
183
|
+
becomes true — and it is the one moment the version on `master` is changed by hand. Two changes, in
|
|
184
|
+
opposite places:
|
|
185
|
+
|
|
186
|
+
1. **Branch the line being left behind**, from the last `master` commit that belongs to it:
|
|
187
|
+
|
|
188
|
+
```console
|
|
189
|
+
$ git switch --create aaa-X.Y.x <that commit>
|
|
190
|
+
$ git push -u origin aaa-X.Y.x
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Branch from the commit *before* the bump below, so the branch keeps the version its line was
|
|
194
|
+
released under. Patch releases of `X.Y` are cut from here from now on, with their changes
|
|
195
|
+
cherry-picked from `master` — see [Backports](#backports). The `aaa-` prefix carries no meaning
|
|
196
|
+
beyond sorting the release branches to the top of the branch list.
|
|
197
|
+
|
|
198
|
+
2. **Bump `master`** to `X.(Y+1).0.dev`, in a pull request with `Gemfile.lock` regenerated and
|
|
199
|
+
labeled `skip-changelog` like the release pull request itself. `4.1` was started exactly this
|
|
200
|
+
way: `aaa-4.0.x` was branched at the commit before `Start 4.1 development`, which set
|
|
201
|
+
`RBS::VERSION` to `4.1.0.dev`.
|
|
202
|
+
|
|
203
|
+
Two loose ends that are easy to forget:
|
|
204
|
+
|
|
205
|
+
- **The release note of the new line.** `rake gem:gh_release` links every published release to
|
|
206
|
+
`https://github.com/ruby/rbs/wiki/Release-Note-X.Y`, built from the version number without
|
|
207
|
+
checking that the page is there. Nothing has to be written when the line starts — the page comes
|
|
208
|
+
together as the first release proper of the line comes into view — but it does have to exist by
|
|
209
|
+
the time that release is published, or its notes link to an empty page.
|
|
210
|
+
- **Release branches that are done.** A branch is worth keeping only while its line might still
|
|
211
|
+
get a patch. The ones that exist do not cover every line that ever had one — `3.8.1` shipped and
|
|
212
|
+
there is no `aaa-3.8.x` — so this is housekeeping rather than a rule, but starting a new minor is
|
|
213
|
+
the natural moment to look at the bottom of the branch list and delete what has been superseded.
|
|
214
|
+
|
|
215
|
+
## Backports
|
|
216
|
+
|
|
217
|
+
A patch release is cut from a release branch (`aaa-X.Y.x`), and what it carries beyond the previous
|
|
218
|
+
release is cherry-picked from the development line. Cherry-pick with `-x`:
|
|
219
|
+
|
|
220
|
+
```console
|
|
221
|
+
$ git cherry-pick -x <commit>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`-x` records the commit the change was copied from, and that recorded line is what `rake
|
|
225
|
+
gem:changelog` follows to reach the pull request the change was written and reviewed in. Without
|
|
226
|
+
it, the only pull request a backported commit is associated with is the one that carried the
|
|
227
|
+
backport, which says nothing about the change and is the same for every commit it brought over —
|
|
228
|
+
that is why the 4.0.3 changelog credits its three entries to the same pull request.
|
|
229
|
+
|
|
230
|
+
## Notes
|
|
231
|
+
|
|
232
|
+
- Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install rbs --pre`;
|
|
233
|
+
a plain `gem install rbs` is unaffected. On JRuby, `gem install rbs [--pre]`
|
|
234
|
+
resolves to the `-java` gem automatically.
|
|
235
|
+
- The WASI SDK version is pinned in `wasm.yml`, `jruby.yml`, `release-gems.yml`, and
|
|
236
|
+
`Dockerfile.jruby`, each carrying its own copy. Keep them in sync when bumping. The
|
|
237
|
+
Chicory/ASM versions are not duplicated: they are the `jar` requirements in
|
|
238
|
+
`rbs.gemspec`, which is where the workflow, `Dockerfile.jruby` and `gem install` all
|
|
239
|
+
read them from.
|
|
240
|
+
- `rake 'gem:check_release[X.Y.Z]'` and `rake gem:tag` are what the workflow runs to
|
|
241
|
+
check the release and to create the tag. Both work locally, which is the fallback
|
|
242
|
+
if the tag ever has to be created by hand.
|
|
243
|
+
- Those two tasks and `rake gem:gh_release` come from the Rakefile of the commit
|
|
244
|
+
being released, not from the branch the workflow was dispatched from. Releasing
|
|
245
|
+
from a release branch (`aaa-X.Y.x`) therefore needs the release tooling on that
|
|
246
|
+
branch as well; without it the run fails on the missing task, before publishing
|
|
247
|
+
anything.
|
data/docs/syntax.md
CHANGED
|
@@ -196,8 +196,8 @@ It is an alias of `top` type, and you can use `boolish` if we want to allow any
|
|
|
196
196
|
We can see an example at the definition of `Enumerable#find`:
|
|
197
197
|
|
|
198
198
|
```rbs
|
|
199
|
-
module Enumerable[
|
|
200
|
-
def find: () { (
|
|
199
|
+
module Enumerable[E, R]
|
|
200
|
+
def find: () { (E) -> boolish } -> E?
|
|
201
201
|
end
|
|
202
202
|
```
|
|
203
203
|
|