Update Rust parser to RBS 4.1.2 - #90
Closed
soutaro wants to merge 253 commits into
Closed
Conversation
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](actions/github-script@v8...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Parser.parse_inline_leading_annotation and parse_inline_trailing_annotation were passing character offsets directly to the C parser, which expects byte offsets since ruby#2863. With ASCII-only input the two coincide so the bug was hidden, but multibyte content (e.g. a Japanese comment preceding the annotation) caused the C parser to start at an invalid byte position and emit a parsing error. Apply the existing byte_range helper to convert character offsets to byte offsets, matching the pattern used by parse_type, parse_method_type, and other parse_* methods. The existing tests for parse_inline_*_annotation used ASCII-only input which would not catch this class of bug, so multibyte regression tests are added.
- Remove outdated method limitations (singleton methods, splat/block params) - Add note that singleton methods (`def self.NAME`) are supported - Document trailing `#:` annotation for method return types - Fix constant type inference: Symbol literals infer to `Symbol`, not the singleton symbol type; add `nil` to the list - Add Skip Annotation section for `@rbs skip` - Add limitations: `class << self`, top-level def, non-self receiver, top-level attr_* - Fix typos: "hyphones" -> "hyphens", "infered" -> "inferred" Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tidy `ast_translation.c`
Update docs/inline.md to match current inline parser behavior
Pins the default GITHUB_TOKEN to contents: read on the 6 workflows in .github/workflows/ that don't actually need any write scope: - c-check.yml: C-extension build check. - milestone.yml: PR milestone enforcer. The github-script step only reads context.payload.pull_request and calls core.setFailed / core.info; no github.rest.* API call. - ruby.yml: Ruby test matrix. - rust.yml: Rust parser build. - typecheck.yml: steep typecheck. - windows.yml: Windows test job. bundle-update.yml and comments.yml are intentionally left implicit: - bundle-update.yml does git push, gh pr create, gh pr merge --auto. - comments.yml needs comment-write scope. Motivation: CVE-2025-30066 (March 2025 tj-actions/changed-files compromise) exfiltrated GITHUB_TOKEN from workflow logs. Per-workflow caps bound runtime authority irrespective of repo or org default, give drift protection if the default ever widens, and are credited per-file by the OpenSSF Scorecard Token-Permissions check. YAML validated locally with yaml.safe_load. Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
Cache canonical TypeName / Namespace instances behind two
positional factory methods, and route every allocation site —
Ruby helpers, the C parser, the resolver, and the environment
— through them. Repeated construction of structurally equal
values returns the same object instead of allocating fresh.
Namespace[path, absolute]
TypeName[namespace, name]
Namespace interns into a per-`absolute` trie of nested Hashes
keyed on path Symbols. TypeName interns into a two-level Hash
keyed by canonical Namespace identity and name Symbol. Both
fast paths are lock-free; cache misses take a mutex.
The C parser calls `Namespace[]` / `TypeName[]` via
`rb_funcallv`; an in-C trie walk that read intern internals
through `rb_hash_lookup` was identical in wall time on Ruby 4.0+
and was dropped to avoid coupling the parser to the cache layout.
Measured against the kaigionrails/conference-app project's RBS
collection (3,903 type names), 1 warmup + 5 runs, median:
Ruby 3.4.9 master 1.247s wall / 0.63s user
branch 1.158s wall / 0.57s user (-7% wall, -10% user)
Ruby 4.0.4 master 1.175s wall / 0.57s user
branch 1.146s wall / 0.55s user (-2.5% wall, -4% user)
Live RBS::Namespace 51,436 → 6,904; RBS::TypeName 78,493 → 22,819
after `rbs list` on Ruby 4.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-perms-readonly ci: declare workflow-level `contents: read` on 6 test/lint workflows
Add the 4.0.3 section to the changelog
A backport is a cherry-pick, so the only pull request its commit is associated with is the one that carried the backport. That pull request says nothing about the change, and every commit it brought over resolves to it, so a release branch's changelog credits all of its entries to one number -- 4.0.3 lists three, all of them ruby#3005. `git cherry-pick -x` records the commit a cherry-pick was made from, so follow it: look the recorded origin up instead of the commit, and the changelog names the pull request the change was written and reviewed in. A commit backported twice carries one line per hop, so the first one, where the change started, is the one used. An origin that leads nowhere -- a commit cherry-picked from a fork, or one that reached the default branch without a pull request -- falls back to the commit in this history, which is at least the backport that brought it here. Cherry-picks made without `-x` record nothing to follow and behave as before. The commit messages are read with `%B`, so the output is forced to UTF-8: unlike the SHAs read until now, a message can hold anything, and splitting one that is not ASCII fails under `LANG=C`, as in CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvSmN7ats6HGEyeT7VEbau
…-picks Attribute a backported commit to the pull request it came from
Pushing a tag needs credentials that a cloud development environment does not have, so the tag is created by `Release gems` rather than by hand before it. The workflow now takes the commit being released and the version that commit declares. Everything is built from the commit, so the run no longer depends on where `master` is when it starts, and the version is the same fact stated a second time: the run stops before anything is built unless it matches `RBS::VERSION` at that commit and the section CHANGELOG.md starts with. Releasing the wrong commit, or the right one under the wrong name, is a failed run rather than a gem to yank. The tag is created once both gems are known to build and run, and before anything is published, so the reversible step still comes before the irreversible one. What it names is decided by the checkout, so nothing rests on it existing first, which is what let it move after the build. `github.ref_type == 'tag'` used to be what separated a release from a build, and a branch dispatch was the dry run. The ref no longer decides anything, so `dry_run` is an explicit input: it builds and checks both gems and stops before the tag. `rake gem:check_release[X.Y.Z]` and `rake gem:tag` are the two steps the workflow runs, so both are also available locally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvSmN7ats6HGEyeT7VEbau
A patch release can be cut from a release branch, which is not an ancestor of the default branch, so requiring one rejected a release that is perfectly normal. What is left is that some branch contains the commit -- a commit no branch leads to is one that cannot be found again -- and the branches that do are printed, so the log says where the release came from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvSmN7ats6HGEyeT7VEbau
Create the release tag in the workflow, from a commit and a version
A dev release cut from `master` after 4.1.1, for people who need one of the changes since it early. `.dev.N` is a prerelease, and it is not written up in CHANGELOG.md nor announced with a GitHub release, so it does not disturb the 4.1.2 cycle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhJnDiMKwunqnLpuXzRY7v
Version 4.1.2.dev.1
rdoc 8 needs `rbs >= 4.0.0`, which resolved to the C-extension gem when the pin went in. `gem install rdoc` now picks up `rbs-4.1.1-java`, so the pin is no longer needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhJnDiMKwunqnLpuXzRY7v
Unpin rdoc on the JRuby CI
A bare `X.Y.0.dev` means `X.Y.0` is being developed; any other version means the one after it is. So a release needs no follow-up bump, and starting a new minor is the only version change made by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhJnDiMKwunqnLpuXzRY7v
`rake release` published from a working copy and built no `-java` gem, and `release:note` drafted the GitHub release that `gem:gh_release` now publishes. README pointed at both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhJnDiMKwunqnLpuXzRY7v
`Dockerfile.jruby` was not mentioned in the wasm README. Chicory and ASM are pinned once, in `rbs.gemspec`, not in the Dockerfile as docs/release.md claimed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PhJnDiMKwunqnLpuXzRY7v
Update the release docs, and remove the release path they replaced
`GC.enable` returns whether GC was *disabled* before the call (true when the call actually re-enabled GC), not whether it was enabled. The ensure clause `GC.disable unless was_enabled` therefore inverted the restore: in the common case where GC is enabled when the test starts, GC.enable returns false and the ensure clause disables GC permanently. Every test that runs after GC_test then executes with GC turned off, so the whole stdlib test process accumulates garbage until exit. On the ruby/ruby Windows CI runners this reaches the process memory limit and the suite dies with NoMemoryError (deterministically), and on all platforms it silently changes what later tests exercise. Rename the variable to what the value actually means and restore the previous state with `GC.disable if was_disabled`, mirroring test_disable. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fix GC_test#test_enable leaving GC disabled for the rest of the suite
`GC.stress = true` runs a full GC on every allocation, and each `assert_send_type` allocates heavily while parsing the method type and type checking the call trace. Since ruby#3059 stopped `test_enable` from leaking a disabled GC into the rest of the suite, stress mode is finally live during this test, and the five assertions between `GC.stress = 0` and `GC.stress = false` turn the whole stdlib suite from 193s into 827s -- 647s of it inside this one method. The cost scales with heap size, so it is far worse in the full suite than when the file runs alone. The assertions only check the types of the return values, so no GC needs to actually run. Disable GC for the duration and restore both the stress mode and the previous enabled state afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NtDw9t8cMiaqT2s8wwSsDt
…nce-424sfy Keep GC disabled during GC_test#test_stress_and_stress=
The 4.1.2 release proper, cut from `master` after 4.1.1 and the `4.1.2.dev.1` release in between. `Gemfile.lock` is regenerated with the bump, and CHANGELOG.md gains the 4.1.2 section. The changelog covers the whole cycle since 4.1.1, the `.dev.1` release included, which is how a release proper is written up. Both entries are the stdlib test suite, which is excluded from the gem's files, so nothing in this release reaches the gem. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QUAgW2m9wsYE1cLM1fFbw
`gem:changelog` and `gem:changelog:json` go through `gh`, which a Claude Code on the web session cannot reach: `api.github.com` is blocked at the agent proxy for anything the shell does, so `gh` is absent, installing it does not help, and rewriting the tasks against REST or Net::HTTP would be refused the same way. The rest of the release is unaffected -- the other tasks read git, or run on a runner. Document the route that does work there, the GitHub MCP server, as the three steps the task itself takes: resolve the base tag, list the commits, then match the pull requests by `head.sha`. That intersection is what the GraphQL `associatedPullRequests` query answers. The trap it warns about is the obvious shortcut of reading the numbers out of `Merge pull request #N` subjects, which looks like it works and loses pull requests silently: the path filter the task uses drops the merge commits while keeping the commits they merged, and five of the eight numbers of the 4.1.2 cycle went with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014QUAgW2m9wsYE1cLM1fFbw
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QHHu1wodDoUBPi2Cs7yhCn
…-io9eyx Start 4.2 development
Say how the changelog is assembled without `gh`
`NODISCARD` is a generic name that can easily collide with macros defined by other headers embedding the RBS parser. Prefix it with `RBS_` to match the other public macros in `include/rbs/defines.h`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SesqemYtV3tnKoNGYuP1JW
…tzvbf7 Rename NODISCARD macro to RBS_NODISCARD
`rake wasm:build` compiled the parser without `NDEBUG`, so every `RBS_ASSERT`
survived into `rbs_parser.wasm`. The MRI extension already drops them
(ext/rbs_extension/extconf.rb), and the assertions that matter sit on the
hottest paths there are: two per character in `rbs_encoding.c`, one per
character in `rbs_skip`, and five around the constant pool. Each one is an
out-of-line varargs call to `rbs_assert_impl`, which the compiler cannot inline
away, so the cost is paid on every character lexed.
Parsing core/ + stdlib/ (250 files, 4.3MB) through a WASI host, best of 10:
assert build 75.7 ms
NDEBUG build 61.9 ms (-18%)
Measured the same on wasmtime and on V8. The serialized ASTs for all 250 files
are byte-identical between the two builds.
`DEBUG=1 rake wasm:build` keeps the assertions, matching the DEBUG convention
extconf.rb and the prepare_bench / prepare_profiling tasks already use.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012uu4EqN6azBeVjM9F71TUp
Build the wasm module with -DNDEBUG
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updates the RBS C parser pinned by the Rust crates from v4.0.2 to v4.1.2.
v4.1 adds ModuleSelfAnnotation, so this also adds its node-kind label for Rust diagnostic paths.