rbs 4.1.0 → 4.2.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +1 -0
  3. data/.github/dependabot.yml +1 -1
  4. data/.github/workflows/bundle-update.yml +3 -3
  5. data/.github/workflows/c-check.yml +2 -2
  6. data/.github/workflows/changelog.yml +121 -0
  7. data/.github/workflows/comments.yml +2 -2
  8. data/.github/workflows/dependabot.yml +1 -1
  9. data/.github/workflows/jruby.yml +4 -9
  10. data/.github/workflows/release-gems.yml +234 -0
  11. data/.github/workflows/ruby.yml +6 -6
  12. data/.github/workflows/rust.yml +12 -12
  13. data/.github/workflows/truffleruby.yml +2 -2
  14. data/.github/workflows/typecheck.yml +2 -2
  15. data/.github/workflows/wasm.yml +3 -3
  16. data/.github/workflows/windows.yml +2 -2
  17. data/.rubocop.yml +0 -1
  18. data/CHANGELOG.md +83 -0
  19. data/README.md +1 -1
  20. data/Rakefile +443 -86
  21. data/config.yml +7 -0
  22. data/core/io.rbs +2 -1
  23. data/docs/CONTRIBUTING.md +1 -1
  24. data/docs/release.md +265 -31
  25. data/docs/stdlib.md +8 -0
  26. data/docs/syntax.md +12 -0
  27. data/ext/rbs_extension/ast_translation.c +15 -0
  28. data/ext/rbs_extension/class_constants.c +2 -0
  29. data/ext/rbs_extension/class_constants.h +1 -0
  30. data/ext/rbs_extension/main.c +55 -19
  31. data/include/rbs/ast.h +25 -17
  32. data/include/rbs/defines.h +8 -3
  33. data/include/rbs/lexer.h +13 -12
  34. data/include/rbs/parser.h +37 -3
  35. data/lib/rbs/ast/declarations.rb +1 -1
  36. data/lib/rbs/ast/type_param.rb +1 -1
  37. data/lib/rbs/definition_builder/ancestor_builder.rb +8 -5
  38. data/lib/rbs/definition_builder/method_builder.rb +4 -4
  39. data/lib/rbs/definition_builder.rb +5 -2
  40. data/lib/rbs/environment/class_entry.rb +12 -0
  41. data/lib/rbs/environment/module_entry.rb +26 -1
  42. data/lib/rbs/parser_aux.rb +2 -2
  43. data/lib/rbs/prototype/helpers.rb +19 -52
  44. data/lib/rbs/prototype/runtime/value_object_generator.rb +0 -1
  45. data/lib/rbs/prototype/runtime.rb +1 -1
  46. data/lib/rbs/types.rb +44 -2
  47. data/lib/rbs/unit_test/type_assertions.rb +5 -5
  48. data/lib/rbs/version.rb +1 -1
  49. data/lib/rbs/wasm/parser.rb +62 -25
  50. data/lib/rbs/wasm/runtime.rb +19 -4
  51. data/lib/rbs/wasm/serialization_schema.rb +3 -2
  52. data/rbs.gemspec +1 -5
  53. data/schema/function.json +12 -1
  54. data/sig/environment/class_entry.rbs +6 -0
  55. data/sig/environment/module_entry.rbs +15 -0
  56. data/sig/parser.rbs +4 -4
  57. data/sig/types.rbs +11 -1
  58. data/sig/unit_test/type_assertions.rbs +2 -2
  59. data/src/ast.c +19 -3
  60. data/src/lexer.c +1562 -1560
  61. data/src/lexer.re +50 -18
  62. data/src/lexstate.c +2 -1
  63. data/src/parser.c +136 -73
  64. data/src/serialize.c +20 -13
  65. data/src/util/rbs_encoding.c +195 -49
  66. data/stdlib/erb/0/erb.rbs +4 -4
  67. data/stdlib/monitor/0/monitor.rbs +4 -4
  68. data/stdlib/singleton/0/singleton.rbs +3 -0
  69. data/stdlib/stringio/0/stringio.rbs +2 -1
  70. data/stdlib/zlib/0/gzip_file.rbs +1 -1
  71. data/stdlib/zlib/0/gzip_writer.rbs +2 -1
  72. data/wasm/README.md +53 -4
  73. data/wasm/rbs_wasm.c +93 -37
  74. metadata +6 -4
  75. data/.github/workflows/milestone.yml +0 -91
data/docs/release.md CHANGED
@@ -1,69 +1,303 @@
1
1
  # Releasing RBS
2
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
+
3
7
  Each release ships **two gems**:
4
8
 
5
- | Gem | Platform | Parser | How it is built |
6
- | --- | --- | --- | --- |
7
- | `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension | `rake release` (re-builds it) |
8
- | `rbs-X.Y.Z-java.gem` | `java` (JRuby) | WebAssembly (`lib/rbs/wasm`) | Docker image, pushed manually |
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 |
9
13
 
10
14
  The `-java` gem contains no native code — just `rbs_parser.wasm`. The Chicory/ASM
11
15
  jars it needs are not shipped in the gem; they are declared as `jar-dependencies`
12
16
  requirements and fetched from Maven when the gem is installed. So the gem can be
13
17
  built once in any environment and runs on every JRuby.
14
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
+
15
30
  ## Prerequisites
16
31
 
17
- - Push rights to the `rbs` gem on RubyGems (`gem signin`). If your account has
18
- MFA enabled, `gem push` / `rake release` will prompt for an OTP.
19
- - Docker, for the `-java` gem. The WASI SDK is baked into the image, so there is
20
- nothing to install on the host.
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.
21
36
 
22
37
  ## Steps
23
38
 
24
- ### 1. Release the `ruby` gem
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.
25
55
 
26
- Once the version is bumped and committed, run on CRuby:
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:
27
61
 
28
62
  ```console
29
- $ bundle exec rake release
63
+ $ bundle exec rake gem:changelog | pbcopy
30
64
  ```
31
65
 
32
- This re-builds the `ruby`-platform gem and then:
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
+ Both tasks reach GitHub through `gh`, which a Claude Code on the web session cannot do. See
81
+ [Assembling the changelog without `gh`](#assembling-the-changelog-without-gh), which runs them on a
82
+ runner instead.
83
+
84
+ ```markdown
85
+ ## X.Y.Z (YYYY-MM-DD)
86
+
87
+ ### Signature updates
88
+
89
+ ### Language updates
90
+
91
+ ### Library changes
92
+
93
+ #### rbs prototype
94
+
95
+ #### rbs collection
96
+
97
+ ### Miscellaneous
98
+ ```
99
+
100
+ The sections always appear in this order; delete the ones that end up empty, which is most of them
101
+ on a small release. Two things scale with the size of the release:
102
+
103
+ - **Summary paragraphs**, above the first section. A patch release usually has none, 4.1.0 has four
104
+ paragraphs, and 4.0.0 has nine. A prerelease has none whatever its size: the cycle it belongs to
105
+ is summarized once, on the release proper that folds it in.
106
+ - **A list of the types whose signatures changed**, as the first line of `### Signature updates`,
107
+ written as `**Updated classes/modules/methods:**` followed by the names in backticks. Used on
108
+ `X.Y.0` releases only.
109
+
110
+ The date is the day the gem is released, matching the `vX.Y.Z` tag — not the day this pull request
111
+ is opened. Fix it up before step 2 if the pull request sat for a few days.
112
+
113
+ ### 2. Run the `Release gems` workflow
114
+
115
+ Once the pull request is merged, dispatch
116
+ [`release-gems.yml`](../.github/workflows/release-gems.yml) from the Actions tab with two inputs:
117
+
118
+ | Input | Value |
119
+ | --- | --- |
120
+ | `commit` | The full 40-character SHA of the merge commit, taken from the merged pull request |
121
+ | `version` | `X.Y.Z`, without the leading `v` |
122
+
123
+ The ref selector picks which copy of the workflow file runs, not what gets released — leave it on
124
+ `master`. Everything is built from `commit`, so the run is unaffected by whatever lands on `master`
125
+ in the meantime, and a patch release cut from a release branch is dispatched the same way as any
126
+ other: the workflow does not care which branch the commit is on.
127
+
128
+ The two inputs say the same thing twice, once as a commit and once as a name, and the run stops
129
+ before anything is built unless they agree with each other and with the repository:
130
+
131
+ - `commit` has to be a full SHA that some branch contains,
132
+ - `version` has to be the `RBS::VERSION` that commit declares,
133
+ - CHANGELOG.md has to start with a section for `version` (skipped for `.dev.N`, which is not
134
+ written up),
135
+ - `vX.Y.Z` must not exist yet.
33
136
 
34
- - creates the tag `vX.Y.Z`,
35
- - pushes the current branch and the tag to `origin`,
36
- - pushes the gem to RubyGems,
37
- - runs `release:note`, which opens a GitHub **draft** release (with
38
- `--prerelease` for `*.pre.*` versions) and prints the remaining manual steps.
137
+ It then:
39
138
 
40
- ### 2. Build and push the `java` gem
139
+ - builds `rbs-X.Y.Z.gem`,
140
+ - compiles `rbs_parser.wasm` and builds `rbs-X.Y.Z-java.gem`,
141
+ - checks both: platforms, the C extension on one and its absence on the other, and that the wasm
142
+ module made it into the `java` gem,
143
+ - installs the `java` gem on JRuby and parses with it, so the WebAssembly runtime is exercised
144
+ before anything is published,
145
+ - uploads both gems as an artifact,
146
+ - tags `commit` as `vX.Y.Z` and pushes the tag,
147
+ - pushes both gems to RubyGems through trusted publishing,
148
+ - publishes the GitHub release with the notes from CHANGELOG.md, skipping this last step for
149
+ `.dev.N` versions.
41
150
 
42
- The `java` gem is not built by `rake release`, so build and push it manually:
151
+ The tag is created once both gems are known to build and run, and before anything is published: a
152
+ tag can be deleted, while a version pushed to RubyGems can only be yanked.
153
+
154
+ Checking the `dry_run` box runs everything up to the artifact and stops — no tag, no gems pushed,
155
+ no release — which is how the build is exercised without releasing. `version` still has to match
156
+ the commit, so a dry run is also how a release is rehearsed before it is cut.
157
+
158
+ ## The version on `master`
159
+
160
+ `RBS::VERSION` on `master` is read one of two ways, told apart by how the version ends:
161
+
162
+ | On `master` | Means |
163
+ | --- | --- |
164
+ | `X.Y.0.dev` — a bare `.dev` | `X.Y.0` is being developed |
165
+ | 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 |
166
+
167
+ 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
168
+ comes after it is being worked on. `4.1.2.dev.1` says the same thing about itself: that release is
169
+ out, and the line continues towards 4.1.2.
170
+
171
+ Both become true the moment the release is tagged, so **nothing has to be done to `master` after a
172
+ release**. `4.0.1` was followed by `4.0.2` with no version change in between, and `4.1.2.dev.1` is
173
+ what `master` carries today.
174
+
175
+ The bare `X.Y.0.dev` is the exception because it is the one version that names a target rather than
176
+ a predecessor: a new minor is developed towards `X.Y.0` for a long time, before it is known whether
177
+ the next thing to ship is `X.Y.0.pre.1` or `X.Y.0` itself. Setting it is the only version change
178
+ that has to be made deliberately.
179
+
180
+ `rake gem:changelog` reads `RBS::VERSION` too, to decide where the next changelog starts — but the
181
+ version is set to the one being released before the changelog is generated, so it sees that rather
182
+ than whatever `master` was carrying.
183
+
184
+ ## Starting a new minor
185
+
186
+ `master` is the development line of one minor at a time. Moving it from `X.Y` to `X.(Y+1)` is not
187
+ part of any one release — it is the decision that the `X.Y` line is done, taken whenever that
188
+ becomes true — and it is the one moment the version on `master` is changed by hand. Two changes, in
189
+ opposite places:
190
+
191
+ 1. **Branch the line being left behind**, from the last `master` commit that belongs to it:
192
+
193
+ ```console
194
+ $ git switch --create aaa-X.Y.x <that commit>
195
+ $ git push -u origin aaa-X.Y.x
196
+ ```
197
+
198
+ Branch from the commit *before* the bump below, so the branch keeps the version its line was
199
+ released under. Patch releases of `X.Y` are cut from here from now on, with their changes
200
+ cherry-picked from `master` — see [Backports](#backports). The `aaa-` prefix carries no meaning
201
+ beyond sorting the release branches to the top of the branch list.
202
+
203
+ The branch carries its own release tooling, since that is read from the ref rather than from
204
+ `master`: the `gem:` tasks, and `changelog.yml` for the changelog. Branching from `master`
205
+ brings both along; what needs watching is a later change to either, which reaches this line
206
+ only if it is cherry-picked here too.
207
+
208
+ 2. **Bump `master`** to `X.(Y+1).0.dev`, in a pull request with `Gemfile.lock` regenerated and
209
+ labeled `skip-changelog` like the release pull request itself. `4.1` was started exactly this
210
+ way: `aaa-4.0.x` was branched at the commit before `Start 4.1 development`, which set
211
+ `RBS::VERSION` to `4.1.0.dev`.
212
+
213
+ Two loose ends that are easy to forget:
214
+
215
+ - **The release note of the new line.** `rake gem:gh_release` links every published release to
216
+ `https://github.com/ruby/rbs/wiki/Release-Note-X.Y`, built from the version number without
217
+ checking that the page is there. Nothing has to be written when the line starts — the page comes
218
+ together as the first release proper of the line comes into view — but it does have to exist by
219
+ the time that release is published, or its notes link to an empty page.
220
+ - **Release branches that are done.** A branch is worth keeping only while its line might still
221
+ get a patch. The ones that exist do not cover every line that ever had one — `3.8.1` shipped and
222
+ there is no `aaa-3.8.x` — so this is housekeeping rather than a rule, but starting a new minor is
223
+ the natural moment to look at the bottom of the branch list and delete what has been superseded.
224
+
225
+ ## Backports
226
+
227
+ A patch release is cut from a release branch (`aaa-X.Y.x`), and what it carries beyond the previous
228
+ release is cherry-picked from the development line. Cherry-pick with `-x`:
43
229
 
44
230
  ```console
45
- # Build from the committed state (the gemspec's file list comes from `git ls-files`).
46
- $ docker build -f Dockerfile.jruby -t rbs-jruby .
231
+ $ git cherry-pick -x <commit>
232
+ ```
233
+
234
+ `-x` records the commit the change was copied from, and that recorded line is what `rake
235
+ gem:changelog` follows to reach the pull request the change was written and reviewed in. Without
236
+ it, the only pull request a backported commit is associated with is the one that carried the
237
+ backport, which says nothing about the change and is the same for every commit it brought over —
238
+ that is why the 4.0.3 changelog credits its three entries to the same pull request.
47
239
 
48
- # Build rbs_parser.wasm and the -java gem into ./pkg on the host. The Chicory/ASM
49
- # jars are not bundled; they are fetched from Maven when the gem is installed.
50
- $ docker run --rm -e RBS_PLATFORM=java -v "$PWD/pkg:/out" rbs-jruby \
51
- gem build rbs.gemspec -o /out/rbs-X.Y.Z-java.gem
240
+ The entry names that pull request and adds the one that carried the backport:
52
241
 
53
- $ gem push pkg/rbs-X.Y.Z-java.gem
242
+ ```markdown
243
+ * {title} ([#{original}](https://github.com/ruby/rbs/pull/{original}), Backported in [#{backport}](https://github.com/ruby/rbs/pull/{backport}))
54
244
  ```
55
245
 
56
- Optionally confirm it installs and runs on JRuby before pushing:
246
+ `gem:changelog` prints this form on its own, from the same `-x` trailer: the origin it resolves is
247
+ the first link, and the pull request of the cherry-pick in front of it is the second. On the
248
+ development line nothing is a cherry-pick, so entries there keep the plain single link.
249
+
250
+ The second link is what keeps the entry from reading as a mistake. The original pull request is
251
+ against `master`, so it is listed again when the development line ships — and with nothing to tell
252
+ the two apart, the same link under two version headings looks like a change written into the wrong
253
+ section. #1923 is the pair to look at: plain under 3.6.0.pre.1, annotated under 3.5.2, which
254
+ backported it.
255
+
256
+ ## Assembling the changelog without `gh`
257
+
258
+ `gem:changelog` and `gem:changelog:json` reach GitHub through `gh`, so they cannot run from a
259
+ Claude Code on the web session. `api.github.com` refuses anything the shell does there, and the
260
+ refusal is keyed on the session rather than on the client, so installing `gh` does not help:
57
261
 
58
262
  ```console
59
- $ docker run --rm -v "$PWD/pkg:/pkg" -w /tmp rbs-jruby bash -c \
60
- 'gem install /pkg/rbs-X.Y.Z-java.gem && ruby -e "require %q{rbs}; puts [RUBY_ENGINE, RBS::VERSION].join(%q{ })"'
263
+ $ curl -s -o /dev/null -w '%{http_code}' https://api.github.com/repos/ruby/rbs
264
+ 403
61
265
  ```
62
266
 
267
+ Nothing else about the release is affected. `gem:check_release` and `gem:tag` read git and the
268
+ working tree, `gem:gh_release` runs on a runner, and git itself reaches github.com normally —
269
+ clone, fetch and push all work.
270
+
271
+ So the task is run where it does work. Dispatch
272
+ [`changelog.yml`](../.github/workflows/changelog.yml), which runs it on a runner, and read the list
273
+ from the run summary, the log, or the `changelog` artifact.
274
+
275
+ | Input | Value |
276
+ | --- | --- |
277
+ | The ref selector | The branch the changelog is for: `aaa-X.Y.x` for a patch release, `master` otherwise |
278
+ | `version` | Where the changelog starts, when that should not follow `RBS::VERSION`. It names the release before the one being written, so `4.1.2` produces the 4.1.3 changelog |
279
+ | `format` | `list` for the template, `json` for the pull request details the sections are sorted from |
280
+
281
+ The ref is not incidental the way it is for `release-gems.yml`: it picks the history being
282
+ described *and* the copy of the task that describes it. So a release branch needs `changelog.yml`
283
+ on it, the same way it needs the release tasks — dispatching on a ref without the file fails with
284
+ `Workflow does not have 'workflow_dispatch' trigger`, since the trigger is read from the ref.
285
+
63
286
  ## Notes
64
287
 
65
288
  - Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install rbs --pre`;
66
289
  a plain `gem install rbs` is unaffected. On JRuby, `gem install rbs [--pre]`
67
290
  resolves to the `-java` gem automatically.
68
- - The Dockerfile pins the WASI SDK / Chicory / ASM versions to match the
69
- `wasm` and `jruby` CI workflows. Keep them in sync when bumping.
291
+ - The WASI SDK version is pinned in `wasm.yml`, `jruby.yml`, `release-gems.yml`, and
292
+ `Dockerfile.jruby`, each carrying its own copy. Keep them in sync when bumping. The
293
+ Chicory/ASM versions are not duplicated: they are the `jar` requirements in
294
+ `rbs.gemspec`, which is where the workflow, `Dockerfile.jruby` and `gem install` all
295
+ read them from.
296
+ - `rake 'gem:check_release[X.Y.Z]'` and `rake gem:tag` are what the workflow runs to
297
+ check the release and to create the tag. Both work locally, which is the fallback
298
+ if the tag ever has to be created by hand.
299
+ - Those two tasks and `rake gem:gh_release` come from the Rakefile of the commit
300
+ being released, not from the branch the workflow was dispatched from. Releasing
301
+ from a release branch (`aaa-X.Y.x`) therefore needs the release tooling on that
302
+ branch as well; without it the run fails on the missing task, before publishing
303
+ anything.
data/docs/stdlib.md CHANGED
@@ -15,6 +15,14 @@ $ bundle exec rake 'generate:stdlib_test[String]'
15
15
  Created: test/stdlib/String_test.rb
16
16
  ```
17
17
 
18
+ Core signatures are loaded by default. To generate a test for a class defined in standard library signatures,
19
+ pass the paths containing those signatures and their dependencies after the class name.
20
+
21
+ ```console
22
+ $ bundle exec rake 'generate:stdlib_test[CSV::Row,stdlib/csv/0,stdlib/forwardable/0]'
23
+ Created: test/stdlib/CSV_Row_test.rb
24
+ ```
25
+
18
26
  It generates `test/stdlib/[class_name]_test.rb`.
19
27
  The test scripts would look like the following:
20
28
 
data/docs/syntax.md CHANGED
@@ -48,6 +48,18 @@ _proc_ ::= `^` _parameters?_ _self-type-binding?_ _block?_ `->` _type_
48
48
  | `^` `(` `?` `)` `->` _type_ # Proc type with untyped parameter
49
49
  ```
50
50
 
51
+ `\w` above, and everywhere else in this document, is `[a-zA-Z0-9_]` together
52
+ with every character outside ASCII -- the same set Ruby takes into an
53
+ identifier. So `ServicioÚltimaVez` is a class name and `nombre_único` is an
54
+ alias name.
55
+
56
+ The leading character is the exception. RBS reads it to tell a class name from
57
+ an interface name from an alias name, so where it makes that distinction it has
58
+ to be ASCII: `class 日本語` is a class in Ruby but not a name RBS can write.
59
+ Nowhere else is restricted -- a method name, a variable name, an instance
60
+ variable name and a class variable name may all open with any character Ruby
61
+ accepts.
62
+
51
63
  ### Class instance type
52
64
 
53
65
  Class instance type denotes _an instance of a class_.
@@ -1636,6 +1636,7 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
1636
1636
  VALUE arg_required_keywords = rbs_hash_to_ruby_hash(ctx, node->required_keywords);
1637
1637
  VALUE arg_optional_keywords = rbs_hash_to_ruby_hash(ctx, node->optional_keywords);
1638
1638
  VALUE arg_rest_keywords = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->rest_keywords); // rbs_node
1639
+ VALUE arg_forwarding = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->forwarding); // rbs_node
1639
1640
  VALUE arg_return_type = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->return_type); // rbs_node
1640
1641
 
1641
1642
  // Claim the shared kwargs hash, clear it, fill it, and hand it to `.new`.
@@ -1649,9 +1650,23 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan
1649
1650
  rb_hash_aset(h, ID2SYM(rb_intern("required_keywords")), arg_required_keywords);
1650
1651
  rb_hash_aset(h, ID2SYM(rb_intern("optional_keywords")), arg_optional_keywords);
1651
1652
  rb_hash_aset(h, ID2SYM(rb_intern("rest_keywords")), arg_rest_keywords);
1653
+ rb_hash_aset(h, ID2SYM(rb_intern("forwarding")), arg_forwarding);
1652
1654
  rb_hash_aset(h, ID2SYM(rb_intern("return_type")), arg_return_type);
1653
1655
  return CLASS_NEW_INSTANCE(RBS_Types_Function, 1, &h);
1654
1656
  }
1657
+ case RBS_TYPES_FUNCTION_FORWARDING_PARAM: {
1658
+ rbs_types_function_forwarding_param_t *node = (rbs_types_function_forwarding_param_t *) instance;
1659
+
1660
+ // Compute child VALUEs into locals variables first, before any recursion into `rbs_struct_to_ruby_value()`.
1661
+ VALUE arg_location = rbs_location_range_to_ruby_location(ctx, node->base.location);
1662
+
1663
+ // Claim the shared kwargs hash, clear it, fill it, and hand it to `.new`.
1664
+ // Must not recurse between `rb_hash_clear()` and `CLASS_NEW_INSTANCE()`.
1665
+ VALUE h = ctx.reusable_kwargs_hash;
1666
+ rb_hash_clear(h);
1667
+ rb_hash_aset(h, ID2SYM(rb_intern("location")), arg_location);
1668
+ return CLASS_NEW_INSTANCE(RBS_Types_Function_ForwardingParam, 1, &h);
1669
+ }
1655
1670
  case RBS_TYPES_FUNCTION_PARAM: {
1656
1671
  rbs_types_function_param_t *node = (rbs_types_function_param_t *) instance;
1657
1672
 
@@ -79,6 +79,7 @@ VALUE RBS_Types_Block;
79
79
  VALUE RBS_Types_ClassInstance;
80
80
  VALUE RBS_Types_ClassSingleton;
81
81
  VALUE RBS_Types_Function;
82
+ VALUE RBS_Types_Function_ForwardingParam;
82
83
  VALUE RBS_Types_Function_Param;
83
84
  VALUE RBS_Types_Interface;
84
85
  VALUE RBS_Types_Intersection;
@@ -173,6 +174,7 @@ void rbs__init_constants(void) {
173
174
  IMPORT_CONSTANT(RBS_Types_ClassInstance, RBS_Types, "ClassInstance");
174
175
  IMPORT_CONSTANT(RBS_Types_ClassSingleton, RBS_Types, "ClassSingleton");
175
176
  IMPORT_CONSTANT(RBS_Types_Function, RBS_Types, "Function");
177
+ IMPORT_CONSTANT(RBS_Types_Function_ForwardingParam, RBS_Types_Function, "ForwardingParam");
176
178
  IMPORT_CONSTANT(RBS_Types_Function_Param, RBS_Types_Function, "Param");
177
179
  IMPORT_CONSTANT(RBS_Types_Interface, RBS_Types, "Interface");
178
180
  IMPORT_CONSTANT(RBS_Types_Intersection, RBS_Types, "Intersection");
@@ -87,6 +87,7 @@ extern VALUE RBS_Types_Block;
87
87
  extern VALUE RBS_Types_ClassInstance;
88
88
  extern VALUE RBS_Types_ClassSingleton;
89
89
  extern VALUE RBS_Types_Function;
90
+ extern VALUE RBS_Types_Function_ForwardingParam;
90
91
  extern VALUE RBS_Types_Function_Param;
91
92
  extern VALUE RBS_Types_Interface;
92
93
  extern VALUE RBS_Types_Intersection;
@@ -146,44 +146,80 @@ static VALUE parse_type_try(VALUE a) {
146
146
  return rbs_struct_to_ruby_value(ctx, type);
147
147
  }
148
148
 
149
- static void validate_position_range(int start_pos, int end_pos) {
149
+ /**
150
+ * `end_pos` may point past the end of the buffer: clamping with a large
151
+ * number instead of measuring the buffer is ordinary, and the lexer stops at
152
+ * the end on its own.
153
+ * */
154
+ static void validate_position_range(VALUE string, int start_pos, int end_pos) {
150
155
  if (start_pos < 0 || end_pos < 0) {
151
156
  rb_raise(rb_eArgError, "negative position range: %d...%d", start_pos, end_pos);
152
157
  }
153
158
  if (start_pos > end_pos) {
154
159
  rb_raise(rb_eArgError, "invalid position range: %d...%d", start_pos, end_pos);
155
160
  }
161
+
162
+ long size = RSTRING_LEN(string);
163
+ if ((long) start_pos > size) {
164
+ rb_raise(rb_eArgError, "position range starts past the end of the buffer: %d...%d, buffer is %ld bytes", start_pos, end_pos, size);
165
+ }
156
166
  }
157
167
 
158
168
  static rbs_lexer_t *alloc_lexer_from_buffer(rbs_allocator_t *allocator, VALUE string, rb_encoding *encoding, int start_pos, int end_pos) {
159
- validate_position_range(start_pos, end_pos);
169
+ validate_position_range(string, start_pos, end_pos);
160
170
 
161
171
  const char *encoding_name = rb_enc_name(encoding);
162
172
 
163
- return rbs_lexer_new(
173
+ rbs_lexer_t *lexer = rbs_lexer_new(
164
174
  allocator,
165
175
  rbs_string_from_ruby_string(string),
166
176
  rbs_encoding_find((const uint8_t *) encoding_name, (const uint8_t *) (encoding_name + strlen(encoding_name))),
167
177
  start_pos,
168
178
  end_pos
169
179
  );
180
+
181
+ if (lexer == NULL) {
182
+ rb_raise(rb_eArgError, "position range starts inside a character: %d...%d", start_pos, end_pos);
183
+ }
184
+
185
+ return lexer;
170
186
  }
171
187
 
172
- static rbs_parser_t *alloc_parser_from_buffer(VALUE buffer, int start_pos, int end_pos) {
173
- validate_position_range(start_pos, end_pos);
188
+ // Build the parser options from the arguments the `_parse_*` entry points
189
+ // receive. The optional syntax these enable is not part of the public
190
+ // `RBS::Parser` API, so only the private entry points pass them through.
191
+ static rbs_parser_options_t parser_options(VALUE enable_forwarding_params) {
192
+ return (rbs_parser_options_t) {
193
+ .enable_forwarding_params = RB_TEST(enable_forwarding_params),
194
+ };
195
+ }
174
196
 
197
+ static rbs_parser_t *alloc_parser_from_buffer_with_options(VALUE buffer, int start_pos, int end_pos, rbs_parser_options_t options) {
175
198
  VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
176
199
  StringValue(string);
177
200
 
201
+ validate_position_range(string, start_pos, end_pos);
202
+
178
203
  rb_encoding *encoding = rb_enc_get(string);
179
204
  const char *encoding_name = rb_enc_name(encoding);
180
205
 
181
- return rbs_parser_new(
206
+ rbs_parser_t *parser = rbs_parser_new_with_options(
182
207
  rbs_string_from_ruby_string(string),
183
208
  rbs_encoding_find((const uint8_t *) encoding_name, (const uint8_t *) (encoding_name + strlen(encoding_name))),
184
209
  start_pos,
185
- end_pos
210
+ end_pos,
211
+ options
186
212
  );
213
+
214
+ if (parser == NULL) {
215
+ rb_raise(rb_eArgError, "position range starts inside a character: %d...%d", start_pos, end_pos);
216
+ }
217
+
218
+ return parser;
219
+ }
220
+
221
+ static rbs_parser_t *alloc_parser_from_buffer(VALUE buffer, int start_pos, int end_pos) {
222
+ return alloc_parser_from_buffer_with_options(buffer, start_pos, end_pos, (rbs_parser_options_t) { 0 });
187
223
  }
188
224
 
189
225
  static VALUE rbsparser_parse_type(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE variables, VALUE require_eof, VALUE void_allowed, VALUE self_allowed, VALUE classish_allowed) {
@@ -232,12 +268,12 @@ static VALUE parse_method_type_try(VALUE a) {
232
268
  return rbs_struct_to_ruby_value(ctx, (rbs_node_t *) method_type);
233
269
  }
234
270
 
235
- static VALUE rbsparser_parse_method_type(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE variables, VALUE require_eof) {
271
+ static VALUE rbsparser_parse_method_type(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE variables, VALUE require_eof, VALUE enable_forwarding_params) {
236
272
  VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
237
273
  StringValue(string);
238
274
  rb_encoding *encoding = rb_enc_get(string);
239
275
 
240
- rbs_parser_t *parser = alloc_parser_from_buffer(buffer, FIX2INT(start_pos), FIX2INT(end_pos));
276
+ rbs_parser_t *parser = alloc_parser_from_buffer_with_options(buffer, FIX2INT(start_pos), FIX2INT(end_pos), parser_options(enable_forwarding_params));
241
277
  declare_type_variables(parser, variables, buffer);
242
278
  struct parse_method_type_arg arg = {
243
279
  .buffer = buffer,
@@ -271,12 +307,12 @@ static VALUE parse_signature_try(VALUE a) {
271
307
  return rbs_struct_to_ruby_value(ctx, (rbs_node_t *) signature);
272
308
  }
273
309
 
274
- static VALUE rbsparser_parse_signature(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos) {
310
+ static VALUE rbsparser_parse_signature(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE enable_forwarding_params) {
275
311
  VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
276
312
  StringValue(string);
277
313
  rb_encoding *encoding = rb_enc_get(string);
278
314
 
279
- rbs_parser_t *parser = alloc_parser_from_buffer(buffer, FIX2INT(start_pos), FIX2INT(end_pos));
315
+ rbs_parser_t *parser = alloc_parser_from_buffer_with_options(buffer, FIX2INT(start_pos), FIX2INT(end_pos), parser_options(enable_forwarding_params));
280
316
  struct parse_signature_arg arg = {
281
317
  .buffer = buffer,
282
318
  .encoding = encoding,
@@ -364,12 +400,12 @@ static VALUE parse_method_type_to_bytes_try(VALUE a) {
364
400
  return serialized_node_to_string(parser, (rbs_node_t *) method_type);
365
401
  }
366
402
 
367
- static VALUE rbsparser_parse_method_type_to_bytes(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE variables, VALUE require_eof) {
403
+ static VALUE rbsparser_parse_method_type_to_bytes(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE variables, VALUE require_eof, VALUE enable_forwarding_params) {
368
404
  VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
369
405
  StringValue(string);
370
406
  rb_encoding *encoding = rb_enc_get(string);
371
407
 
372
- rbs_parser_t *parser = alloc_parser_from_buffer(buffer, FIX2INT(start_pos), FIX2INT(end_pos));
408
+ rbs_parser_t *parser = alloc_parser_from_buffer_with_options(buffer, FIX2INT(start_pos), FIX2INT(end_pos), parser_options(enable_forwarding_params));
373
409
  declare_type_variables(parser, variables, buffer);
374
410
  struct parse_method_type_arg arg = {
375
411
  .buffer = buffer,
@@ -397,12 +433,12 @@ static VALUE parse_signature_to_bytes_try(VALUE a) {
397
433
  return serialized_node_to_string(parser, (rbs_node_t *) signature);
398
434
  }
399
435
 
400
- static VALUE rbsparser_parse_signature_to_bytes(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos) {
436
+ static VALUE rbsparser_parse_signature_to_bytes(VALUE self, VALUE buffer, VALUE start_pos, VALUE end_pos, VALUE enable_forwarding_params) {
401
437
  VALUE string = rb_funcall(buffer, rb_intern("content"), 0);
402
438
  StringValue(string);
403
439
  rb_encoding *encoding = rb_enc_get(string);
404
440
 
405
- rbs_parser_t *parser = alloc_parser_from_buffer(buffer, FIX2INT(start_pos), FIX2INT(end_pos));
441
+ rbs_parser_t *parser = alloc_parser_from_buffer_with_options(buffer, FIX2INT(start_pos), FIX2INT(end_pos), parser_options(enable_forwarding_params));
406
442
  struct parse_signature_arg arg = {
407
443
  .buffer = buffer,
408
444
  .encoding = encoding,
@@ -587,11 +623,11 @@ void rbs__init_parser(void) {
587
623
  rb_gc_register_mark_object(EMPTY_HASH);
588
624
 
589
625
  rb_define_singleton_method(RBS_Parser, "_parse_type", rbsparser_parse_type, 8);
590
- rb_define_singleton_method(RBS_Parser, "_parse_method_type", rbsparser_parse_method_type, 5);
591
- rb_define_singleton_method(RBS_Parser, "_parse_signature", rbsparser_parse_signature, 3);
626
+ rb_define_singleton_method(RBS_Parser, "_parse_method_type", rbsparser_parse_method_type, 6);
627
+ rb_define_singleton_method(RBS_Parser, "_parse_signature", rbsparser_parse_signature, 4);
592
628
  rb_define_singleton_method(RBS_Parser, "_parse_type_to_bytes", rbsparser_parse_type_to_bytes, 8);
593
- rb_define_singleton_method(RBS_Parser, "_parse_method_type_to_bytes", rbsparser_parse_method_type_to_bytes, 5);
594
- rb_define_singleton_method(RBS_Parser, "_parse_signature_to_bytes", rbsparser_parse_signature_to_bytes, 3);
629
+ rb_define_singleton_method(RBS_Parser, "_parse_method_type_to_bytes", rbsparser_parse_method_type_to_bytes, 6);
630
+ rb_define_singleton_method(RBS_Parser, "_parse_signature_to_bytes", rbsparser_parse_signature_to_bytes, 4);
595
631
  rb_define_singleton_method(RBS_Parser, "_parse_type_params", rbsparser_parse_type_params, 4);
596
632
  rb_define_singleton_method(RBS_Parser, "_parse_inline_leading_annotation", rbsparser_parse_inline_leading_annotation, 4);
597
633
  rb_define_singleton_method(RBS_Parser, "_parse_inline_trailing_annotation", rbsparser_parse_inline_trailing_annotation, 4);