rbs 4.1.0 → 4.1.1.dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0cb4ca04de969abdc9d7f58e67fb98a0ce0d39e6669dab1be23241ba08a1b883
4
- data.tar.gz: c758f606ee5737b64c45fd4da9c99f6522f1a62a0b8a37b4023d37fcb69b6d59
3
+ metadata.gz: 4c6080d3cf9cb3af2188509104f6b43f59f47a08313cc82ac9c1d9e052dc1dc9
4
+ data.tar.gz: 131ca181e7e1516b8b4b938e9fce97c97ab4c3ad2b73b47d3f0e4e42996797ac
5
5
  SHA512:
6
- metadata.gz: 26dbbd5af3941363c80a282d63dfda59d0aba521bea2c75e45c3dd58336a18920d859ef426b0c8bab67535be7e3651f8f2f83a78ed068477dd5ccf8adb09df4c
7
- data.tar.gz: 0a5c85aa87e7ddb72519953952bb0bec098c8322cd379fb0e5e8dfb6ac0b1a8f4df70ee225cb3361b719d0b506b5d9eff0c43db08ce235d7ac233adb320d9d8a
6
+ metadata.gz: 1b6c4bb23f7b593523cad221f31bfdd4d3399e8b93aac56813e2731680a4965d5571ce85ddbded21cd5353993f53719e2f8de91e1d4be7932ec5116c0abb3643
7
+ data.tar.gz: 95f2e854dc6e2f9115f258809110ec920468c6e1aadedfc7b98a13decb4c501bc0fd59958e8ffb06077bf34faf32a6d7a0310f8b2fc56f5b7a6477b521fca949
@@ -21,4 +21,4 @@ updates:
21
21
  schedule:
22
22
  interval: 'weekly'
23
23
  labels:
24
- - 'no-milestone'
24
+ - 'skip-changelog'
@@ -58,6 +58,6 @@ jobs:
58
58
  --body "Automated weekly bundle update" \
59
59
  --head "$(git rev-parse --abbrev-ref HEAD)" \
60
60
  --base "${{ github.event.repository.default_branch }}" \
61
- --label "no-milestone"
61
+ --label "skip-changelog"
62
62
 
63
63
  gh pr merge --auto --merge "$(git rev-parse --abbrev-ref HEAD)"
@@ -0,0 +1,164 @@
1
+ name: Release gems
2
+
3
+ # Builds, publishes, and announces a release. Dispatch it against the `vX.Y.Z` tag
4
+ # of the release: the tag is created first so that everything published afterwards
5
+ # traces back to an immutable ref, and so the reversible step comes before the
6
+ # irreversible one.
7
+ #
8
+ # Dispatching against a branch builds and verifies the gems and stops there, which
9
+ # is how the build is exercised without releasing anything.
10
+ #
11
+ # | Gem | Platform | Parser |
12
+ # | -------------------- | ------------- | -------------------------------- |
13
+ # | `rbs-X.Y.Z.gem` | `ruby` (MRI) | C extension, compiled on install |
14
+ # | `rbs-X.Y.Z-java.gem` | `java` (JRuby)| `rbs_parser.wasm`, prebuilt here |
15
+ #
16
+ # See docs/release.md. The `java` gem is built on CRuby: the platform comes from
17
+ # `RBS_PLATFORM`, not from the engine running `gem build`. JRuby is only needed to
18
+ # run the result, which the workflow does before it would publish anything.
19
+ #
20
+ # One job on purpose. Tagging, pushing the gems, and opening the GitHub release
21
+ # all belong to a single release, and keeping them in one place keeps their order
22
+ # readable -- the tag is created before anything is published, so the reversible
23
+ # step comes before the irreversible one.
24
+
25
+ on:
26
+ workflow_dispatch:
27
+
28
+ permissions:
29
+ contents: read
30
+
31
+ env:
32
+ # Keep in sync with .github/workflows/wasm.yml and .github/workflows/jruby.yml.
33
+ WASI_SDK_VERSION: "33"
34
+ WASI_SDK_RELEASE: "33.0"
35
+
36
+ jobs:
37
+ release:
38
+ name: release
39
+ runs-on: ubuntu-latest
40
+ permissions:
41
+ contents: write # publish the GitHub release
42
+ id-token: write # trusted publishing to RubyGems
43
+ steps:
44
+ # The gemspec takes its file list from `git ls-files`, so both gems are built
45
+ # from the committed state.
46
+ - uses: actions/checkout@v7
47
+ - name: Set up Ruby
48
+ uses: ruby/setup-ruby@v1
49
+ with:
50
+ ruby-version: ruby
51
+ bundler: none
52
+ - name: Update rubygems & bundler
53
+ run: gem update --system
54
+ - name: Install gems
55
+ run: |
56
+ bundle config set --local without libs:profilers
57
+ bundle install --jobs 4 --retry 3
58
+
59
+ - name: Read the version
60
+ id: version
61
+ run: echo "version=$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')" >> "$GITHUB_OUTPUT"
62
+
63
+ # Fail before spending a minute on the build, and before anything is pushed:
64
+ # the tag is what the release is named after, so it has to be the version the
65
+ # tagged commit actually declares.
66
+ - name: Check the tag against RBS::VERSION
67
+ if: github.ref_type == 'tag'
68
+ run: |
69
+ if [ "${{ github.ref_name }}" != "v${{ steps.version.outputs.version }}" ]; then
70
+ echo "::error::tag ${{ github.ref_name }} does not match RBS::VERSION ${{ steps.version.outputs.version }}"
71
+ exit 1
72
+ fi
73
+
74
+ - name: Build the ruby gem
75
+ run: |
76
+ mkdir -p pkg
77
+ gem build rbs.gemspec -o "pkg/rbs-${{ steps.version.outputs.version }}.gem"
78
+
79
+ # `rake wasm:jruby_setup` compiles src/**/*.c to WebAssembly and copies the
80
+ # result to lib/rbs/wasm/, where the gemspec picks it up. clang runs as a
81
+ # subprocess, so this works on CRuby.
82
+ - name: Install the WASI SDK
83
+ run: |
84
+ url="https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_VERSION}/wasi-sdk-${WASI_SDK_RELEASE}-x86_64-linux.tar.gz"
85
+ mkdir -p "$HOME/wasi-sdk"
86
+ curl -sSL "$url" | tar xz --strip-components=1 -C "$HOME/wasi-sdk"
87
+ echo "WASI_SDK_PATH=$HOME/wasi-sdk" >> "$GITHUB_ENV"
88
+ - name: Build rbs_parser.wasm
89
+ run: bundle exec rake wasm:jruby_setup
90
+
91
+ - name: Build the java gem
92
+ env:
93
+ RBS_PLATFORM: java
94
+ run: gem build rbs.gemspec -o "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
95
+
96
+ # `git ls-files` vouches for everything else, but rbs_parser.wasm is a build
97
+ # artifact, so the java gem is the one that can come out quietly wrong.
98
+ - name: Check the built gems
99
+ run: |
100
+ ruby -rrubygems/package -e '
101
+ ruby_gem, java_gem = ARGV.map { Gem::Package.new(_1).spec }
102
+
103
+ raise "unexpected platform: #{ruby_gem.platform}" unless ruby_gem.platform.to_s == "ruby"
104
+ raise "the C extension is not declared" if ruby_gem.extensions.empty?
105
+
106
+ raise "unexpected platform: #{java_gem.platform}" unless java_gem.platform.to_s == "java"
107
+ raise "rbs_parser.wasm is missing" unless java_gem.files.include?("lib/rbs/wasm/rbs_parser.wasm")
108
+ raise "the java gem must not declare an extension" unless java_gem.extensions.empty?
109
+
110
+ [ruby_gem, java_gem].each { puts "#{_1.full_name}: #{_1.files.size} files" }
111
+ ' "pkg/rbs-${{ steps.version.outputs.version }}.gem" \
112
+ "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
113
+
114
+ # The checks above cannot tell whether rbs_parser.wasm actually runs. Install
115
+ # the gem the way a user would -- jar-dependencies fetches Chicory and ASM
116
+ # from Maven during the install -- and parse something with it, so the
117
+ # WebAssembly runtime is exercised end to end before anything is published.
118
+ - name: Set up JRuby
119
+ uses: ruby/setup-ruby@v1
120
+ with:
121
+ ruby-version: jruby
122
+ bundler: none
123
+ - name: Check the java gem on JRuby
124
+ run: |
125
+ gem install "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
126
+ ruby -e '
127
+ require "rbs"
128
+ _, _, decls = RBS::Parser.parse_signature("class Foo end")
129
+ names = decls.map { _1.name.to_s }
130
+ raise "parsed #{names.inspect}, expected [\"Foo\"]" unless names == ["Foo"]
131
+ puts "#{RUBY_ENGINE} #{RUBY_VERSION}: rbs #{RBS::VERSION} parses through the WebAssembly runtime"
132
+ '
133
+
134
+ - name: Switch back to CRuby
135
+ uses: ruby/setup-ruby@v1
136
+ with:
137
+ ruby-version: ruby
138
+ bundler: none
139
+
140
+ # Uploaded before publishing, so a failed push still leaves the gems behind.
141
+ - uses: actions/upload-artifact@v7
142
+ with:
143
+ name: gems
144
+ path: pkg/*.gem
145
+ if-no-files-found: error
146
+
147
+ # Everything below runs only for a release tag.
148
+ - name: Configure RubyGems credentials
149
+ if: github.ref_type == 'tag'
150
+ # No floating major tag on this action, so the exact release is pinned.
151
+ uses: rubygems/configure-rubygems-credentials@v2.1.0
152
+
153
+ - name: Push the gems
154
+ if: github.ref_type == 'tag'
155
+ run: |
156
+ gem push "pkg/rbs-${{ steps.version.outputs.version }}.gem"
157
+ gem push "pkg/rbs-${{ steps.version.outputs.version }}-java.gem"
158
+
159
+ # Last, so that a failed push never announces a release that has no gems.
160
+ - name: Publish the GitHub release
161
+ if: github.ref_type == 'tag'
162
+ env:
163
+ GH_TOKEN: ${{ github.token }}
164
+ run: bundle exec rake gem:gh_release
data/Rakefile CHANGED
@@ -55,8 +55,15 @@ task :confirm_lexer => :lexer do
55
55
  end
56
56
 
57
57
  task :confirm_templates => :templates do
58
- puts "Testing if generated code under include and src is updated with respect to templates"
59
- sh "git diff --exit-code -- include src"
58
+ puts "Testing if generated code is updated with respect to templates"
59
+
60
+ # Every template generates the file it is named after: `templates/<path>.erb` generates `<path>`.
61
+ generated = Dir.glob("templates/**/*.erb").sort.map { _1.delete_prefix("templates/").delete_suffix(".erb") }
62
+
63
+ missing = generated.reject { File.exist?(_1) }
64
+ raise "Templates without a generated file: #{missing.join(", ")}. Is the `templates` task missing an entry?" unless missing.empty?
65
+
66
+ sh "git diff --exit-code -- #{generated.join(" ")}"
60
67
  end
61
68
 
62
69
  # Task to format C code using clang-format
@@ -147,6 +154,9 @@ end
147
154
  rule %r{^include/(.*)\.c} => 'templates/%X.c.erb' do |t|
148
155
  puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
149
156
  end
157
+ rule %r{^ext/(.*)\.c} => 'templates/%X.c.erb' do |t|
158
+ puts "⚠️⚠️⚠️ #{t.name} is older than #{t.source}. You may need to run `rake templates` ⚠️⚠️⚠️"
159
+ end
150
160
 
151
161
  task :annotate do
152
162
  sh "bin/generate_docs.sh"
@@ -495,46 +505,329 @@ NOTES
495
505
  end
496
506
 
497
507
 
498
- desc "Generate changelog template from GH pull requests"
499
- task :changelog do
500
- major, minor, patch, _pre = RBS::VERSION.split(".", 4)
501
- major = major.to_i
502
- minor = minor.to_i
503
- patch = patch.to_i
508
+ # Pull requests with one of these labels are omitted from the changelog.
509
+ CHANGELOG_SKIP_LABELS = ["skip-changelog"]
504
510
 
505
- if patch == 0
506
- milestone = "RBS #{major}.#{minor}"
507
- else
508
- milestone = "RBS #{major}.#{minor}.x"
509
- end
511
+ # Resolves the commit-ish the changelog starts from.
512
+ #
513
+ # `version` is a version number, a tag, or any commit-ish. When it is omitted, the latest tag
514
+ # matching `tag_glob` is used, skipping the ones matching `exclude_globs`.
515
+ #
516
+ def resolve_changelog_base(version, tag_glob:, exclude_globs: [])
517
+ require "open3"
518
+
519
+ from =
520
+ if version
521
+ # `4.1.0` and `v4.1.0` both mean the tag `v4.1.0`, while `master` or a SHA is used as is.
522
+ version.match?(/\A\d/) ? "v#{version}" : version
523
+ else
524
+ command = ["git", "describe", "--tags", "--match", tag_glob, "--abbrev=0"]
525
+ exclude_globs.each { |glob| command.push("--exclude", glob) }
510
526
 
511
- puts "🔍 Finding pull requests that is associated to milestone `#{milestone}`..."
527
+ output, status = Open3.capture2(*command)
528
+ raise "🚨 Cannot detect the latest tag matching `#{tag_glob}`. Give the previous version explicitly." unless status.success?
529
+ output.chomp
530
+ end
531
+
532
+ _, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{from}^{commit}")
533
+ raise "🚨 No such commit-ish: `#{from}`" unless status.success?
512
534
 
513
- command = [
514
- "gh",
515
- "pr",
516
- "list",
517
- "--limit=10000",
518
- "--json",
519
- "url,title,number",
520
- "--search" ,
521
- "milestone:\"#{milestone}\" is:merged sort:updated-desc -label:Released"
522
- ]
535
+ from
536
+ end
523
537
 
538
+ # Runs a GraphQL query against the repository of the working directory.
539
+ #
540
+ # `body` is the selection set inside `repository`, so a query can use the `$owner` and `$name`
541
+ # variables. Returns the contents of `data.repository`.
542
+ #
543
+ def changelog_graphql(body)
524
544
  require "open3"
545
+ require "json"
546
+
547
+ @changelog_repository ||=
548
+ begin
549
+ output, status = Open3.capture2("gh", "repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner")
550
+ raise status.inspect unless status.success?
551
+ output.chomp.split("/", 2)
552
+ end
553
+ owner, name = @changelog_repository
554
+
555
+ query = <<~GRAPHQL
556
+ query($owner: String!, $name: String!) {
557
+ repository(owner: $owner, name: $name) {
558
+ #{body}
559
+ }
560
+ }
561
+ GRAPHQL
562
+
563
+ output, status = Open3.capture2(
564
+ "gh", "api", "graphql",
565
+ "-f", "query=#{query}",
566
+ "-f", "owner=#{owner}",
567
+ "-f", "name=#{name}",
568
+ binmode: true
569
+ )
570
+ raise status.inspect unless status.success?
571
+
572
+ # GitHub always answers in UTF-8, while the default external encoding follows the locale. Without
573
+ # this, a pull request body with an emoji fails to parse under `LANG=C`, as in GitHub Actions.
574
+ JSON.parse(output.force_encoding(Encoding::UTF_8), symbolize_names: true).dig(:data, :repository)
575
+ end
576
+
577
+ # Lists the commits between `from` and `HEAD`, newest first.
578
+ #
579
+ # Giving `paths` limits the commits to the ones touching the paths.
580
+ #
581
+ def changelog_commits(from, paths: [])
582
+ require "open3"
583
+
584
+ command = ["git", "log", "--format=%H", "#{from}..HEAD"]
585
+ # `--simplify-merges` keeps the default history simplification from following only one parent of
586
+ # a merge commit, which can drop the other side. Note that `--full-history` alone is wrong here:
587
+ # it also lists merge commits that do not touch the paths, bringing back the excluded pull
588
+ # requests. The two flags produce the same commits as the default mode for this repository today.
589
+ command.push("--full-history", "--simplify-merges", "--", *paths) unless paths.empty?
590
+
525
591
  output, status = Open3.capture2(*command)
526
592
  raise status.inspect unless status.success?
527
593
 
528
- require "json"
529
- json = JSON.parse(output, symbolize_names: true)
594
+ output.lines.map(&:chomp).reject(&:empty?)
595
+ end
530
596
 
531
- unless json.empty?
532
- puts
533
- json.each do |line|
534
- puts "* #{line[:title]} ([##{line[:number]}](#{line[:url]}))"
597
+ # Finds the pull requests the commits came from, keeping the order of `commits`.
598
+ #
599
+ # Returns the pull requests for the changelog and the ones omitted by `skip_labels`.
600
+ #
601
+ def changelog_pull_requests(commits, skip_labels: CHANGELOG_SKIP_LABELS)
602
+ pull_requests = {}
603
+ skipped = {}
604
+
605
+ commits.each_slice(50) do |slice|
606
+ # Ask GitHub which pull requests the commits came from, so that any merge strategy -- merge
607
+ # commit, squash, or rebase -- is handled without parsing commit messages.
608
+ aliases = slice.map.with_index do |commit, index|
609
+ <<~GRAPHQL
610
+ c#{index}: object(oid: "#{commit}") {
611
+ ... on Commit {
612
+ associatedPullRequests(first: 10) {
613
+ nodes {
614
+ number title url merged
615
+ labels(first: 100) { nodes { name } }
616
+ }
617
+ }
618
+ }
619
+ }
620
+ GRAPHQL
535
621
  end
622
+
623
+ changelog_graphql(aliases.join("\n")).each_value do |commit|
624
+ next unless commit
625
+
626
+ commit.dig(:associatedPullRequests, :nodes).each do |pr|
627
+ next unless pr[:merged]
628
+
629
+ pr = { number: pr[:number], title: pr[:title], url: pr[:url], labels: pr.dig(:labels, :nodes).map { |label| label[:name] } }
630
+ if (pr[:labels] & skip_labels).empty?
631
+ pull_requests[pr[:number]] ||= pr
632
+ else
633
+ skipped[pr[:number]] ||= pr
634
+ end
635
+ end
636
+ end
637
+ end
638
+
639
+ [pull_requests.values, skipped.values]
640
+ end
641
+
642
+ # Fetches the details that help classifying the pull requests: the changed files and the body.
643
+ #
644
+ def changelog_pull_request_details(pull_requests)
645
+ pull_requests.each_slice(50).flat_map do |slice|
646
+ aliases = slice.map do |pr|
647
+ <<~GRAPHQL
648
+ p#{pr[:number]}: pullRequest(number: #{pr[:number]}) {
649
+ body
650
+ author { login }
651
+ files(first: 100) {
652
+ nodes { path }
653
+ pageInfo { hasNextPage }
654
+ }
655
+ }
656
+ GRAPHQL
657
+ end
658
+
659
+ details = changelog_graphql(aliases.join("\n"))
660
+
661
+ slice.map do |pr|
662
+ detail = details[:"p#{pr[:number]}"] or next pr
663
+
664
+ pr.merge(
665
+ author: detail.dig(:author, :login),
666
+ # The body is a hint for writing the changelog, not a copy source. Keep it short.
667
+ body: detail[:body].to_s.strip.slice(0, 1000),
668
+ files: detail.dig(:files, :nodes).map { |file| file[:path] },
669
+ files_truncated: detail.dig(:files, :pageInfo, :hasNextPage)
670
+ )
671
+ end
672
+ end
673
+ end
674
+
675
+ # Reports the pull requests omitted by their label, so that they do not disappear silently.
676
+ #
677
+ def warn_skipped_pull_requests(skipped, skip_labels)
678
+ return if skipped.empty?
679
+
680
+ numbers = skipped.map { |pr| "##{pr[:number]}" }
681
+ numbers = numbers.take(20).push("and #{numbers.size - 20} more") if numbers.size > 20
682
+
683
+ $stderr.puts
684
+ $stderr.puts " (⏭️ Skipped #{skipped.size} pull request(s) labeled #{skip_labels.map { |label| "`#{label}`" }.join(" or ")}: #{numbers.join(", ")})"
685
+ end
686
+
687
+ # Prints the changelog template listing the pull requests merged between `from` and `HEAD`.
688
+ #
689
+ # The changelog goes to STDOUT and everything else goes to STDERR, so that the output can be
690
+ # piped to another command: `rake gem:changelog | pbcopy`
691
+ #
692
+ def print_changelog(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
693
+ $stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
694
+
695
+ commits = changelog_commits(from, paths: paths)
696
+ if commits.empty?
697
+ $stderr.puts " (🤔 There is no commit after `#{from}`.)"
698
+ return
699
+ end
700
+
701
+ pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
702
+
703
+ if pull_requests.empty?
704
+ $stderr.puts " (🤔 No pull request is associated to the commits after `#{from}`.)"
536
705
  else
537
- puts " (🤑 There is no *unreleased* pull request associated to the milestone.)"
706
+ $stderr.puts
707
+ pull_requests.each do |pr|
708
+ puts "* #{pr[:title]} ([##{pr[:number]}](#{pr[:url]}))"
709
+ end
710
+ $stdout.flush
711
+ end
712
+
713
+ warn_skipped_pull_requests(skipped, skip_labels)
714
+ end
715
+
716
+ # Prints the same pull requests as `print_changelog` as JSON, with the details that help
717
+ # classifying them into the sections of CHANGELOG.md.
718
+ #
719
+ # This is the input for the release automation, so it always prints a valid JSON document.
720
+ #
721
+ def print_changelog_json(from, paths: [], skip_labels: CHANGELOG_SKIP_LABELS)
722
+ require "json"
723
+
724
+ $stderr.puts "🔍 Finding pull requests merged between `#{from}` and `HEAD`..."
725
+
726
+ commits = changelog_commits(from, paths: paths)
727
+ pull_requests, skipped = changelog_pull_requests(commits, skip_labels: skip_labels)
728
+ pull_requests = changelog_pull_request_details(pull_requests)
729
+
730
+ $stderr.puts " (📋 #{pull_requests.size} pull request(s))"
731
+
732
+ puts JSON.pretty_generate(
733
+ {
734
+ from: from,
735
+ to: "HEAD",
736
+ pull_requests: pull_requests,
737
+ skipped: skipped
738
+ }
739
+ )
740
+ $stdout.flush
741
+
742
+ warn_skipped_pull_requests(skipped, skip_labels)
743
+ end
744
+
745
+ namespace :gem do
746
+ # The gem is developed in the whole repository except the Rust crate, which has its own release
747
+ # cycle. Note that this is an *exclusion*, not a list of the directories shipped in the gem:
748
+ # changes in `test/` or `.github/` are part of the gem's changelog too.
749
+ # A constant defined in a `namespace` block is a top-level constant, so it needs the prefix.
750
+ GEM_CHANGELOG_PATHS = [".", ":(exclude)rust"]
751
+
752
+ # The tags a release proper starts *after*, rather than at.
753
+ GEM_PRERELEASE_TAGS = ["v*.pre*", "v*.dev*"]
754
+
755
+ # Where the changelog of the release being prepared starts, derived from `RBS::VERSION`:
756
+ #
757
+ # * `X.Y.Z.pre.N` documents what changed since `X.Y.Z.pre.N-1`, so it starts from the latest tag.
758
+ # * `X.Y.Z` documents the whole cycle, the prereleases included, so it skips the prerelease tags
759
+ # in between and starts from the previous release proper.
760
+ #
761
+ # This is the step that is easy to get wrong by hand: on a release proper the latest tag is a
762
+ # prerelease, so the obvious default would produce only the tail of the cycle. Passing a version
763
+ # explicitly overrides all of it.
764
+ #
765
+ def changelog_base(version)
766
+ excluded = Gem::Version.new(RBS::VERSION).prerelease? ? [] : GEM_PRERELEASE_TAGS
767
+ resolve_changelog_base(version, tag_glob: "v*", exclude_globs: excluded)
768
+ end
769
+
770
+ desc "Generate changelog template from GH pull requests merged since the previous release"
771
+ task :changelog, [:version] do |_task, args|
772
+ print_changelog(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
773
+ end
774
+
775
+ namespace :changelog do
776
+ desc "Print the pull requests of `gem:changelog` as JSON, with the changed files and body of each"
777
+ task :json, [:version] do |_task, args|
778
+ print_changelog_json(changelog_base(args[:version]), paths: GEM_CHANGELOG_PATHS)
779
+ end
780
+ end
781
+
782
+ desc "Publish the GitHub release for RBS::VERSION, unless it is a `.dev.` version"
783
+ task :gh_release do
784
+ require "open3"
785
+
786
+ version = Gem::Version.new(RBS::VERSION)
787
+ major, minor, *_ = RBS::VERSION.split(".")
788
+ tag = "v#{RBS::VERSION}"
789
+
790
+ # There are three kinds of release: `X.Y.Z`, `X.Y.Z.pre.N`, and `X.Y.Z.dev.N`.
791
+ # The `.dev.N` ones are cut from the development line for people who need a
792
+ # specific change early; they are not written up in the changelog, so there are
793
+ # no notes to publish and nothing worth announcing.
794
+ if version.segments.include?("dev")
795
+ puts "⏭️ #{RBS::VERSION} is a dev release, so there is no GitHub release to publish."
796
+ next
797
+ end
798
+
799
+ # The release is created against an existing tag, so that the artifacts and the
800
+ # notes describe a commit that is already immutable.
801
+ _, status = Open3.capture2("git", "rev-parse", "--verify", "--quiet", "#{tag}^{commit}")
802
+ raise "🚨 No such tag: `#{tag}`. Tag the release before creating the GitHub release." unless status.success?
803
+
804
+ # The topmost section of the changelog is this release, minus its own heading.
805
+ # The encoding is explicit because the default external encoding follows the
806
+ # locale, and the changelog is not ASCII.
807
+ content = File.read(File.join(__dir__, "CHANGELOG.md"), encoding: Encoding::UTF_8)
808
+ section = content.scan(/^## \d.*?(?=^## \d)/m)[0] or raise "🚨 Cannot find a release section in CHANGELOG.md"
809
+ heading, _, body = section.partition("\n")
810
+ heading.include?(RBS::VERSION) or raise "🚨 CHANGELOG.md starts with `#{heading.strip}`, which is not #{RBS::VERSION}"
811
+
812
+ notes = <<~NOTES
813
+ [Release note](https://github.com/ruby/rbs/wiki/Release-Note-#{major}.#{minor})
814
+
815
+ #{body.strip}
816
+ NOTES
817
+
818
+ # Published rather than drafted: the notes are the changelog section that was
819
+ # already reviewed in the release pull request, so there is nothing left to edit.
820
+ command = [
821
+ "gh", "release", "create", tag,
822
+ "--title=#{RBS::VERSION}",
823
+ "--notes=#{notes}"
824
+ ]
825
+ command << "--prerelease" if version.prerelease?
826
+
827
+ output, status = Open3.capture2(*command)
828
+ raise "🚨 `gh release create` failed: #{status.inspect}" unless status.success?
829
+
830
+ puts "📝 Released #{tag}: #{output.chomp}"
538
831
  end
539
832
  end
540
833
 
data/config.yml CHANGED
@@ -769,6 +769,7 @@ nodes:
769
769
  c_type: rbs_location_range
770
770
  - name: type_name
771
771
  c_type: rbs_type_name
772
+ optional: true # NULL when the name is left out, to be inferred from the Ruby code
772
773
  - name: type_name_location
773
774
  c_type: rbs_location_range
774
775
  optional: true
@@ -781,6 +782,7 @@ nodes:
781
782
  c_type: rbs_location_range
782
783
  - name: type_name
783
784
  c_type: rbs_type_name
785
+ optional: true # NULL when the name is left out, to be inferred from the Ruby code
784
786
  - name: type_name_location
785
787
  c_type: rbs_location_range
786
788
  optional: true
data/docs/release.md CHANGED
@@ -1,69 +1,151 @@
1
1
  # Releasing RBS
2
2
 
3
+ A release is a pull request, a tag, and one workflow run. Everything that leaves
4
+ the repository — both gems and the GitHub release — is produced by the `Release
5
+ 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
+ ### 1. Prepare the release
40
+
41
+ Open a pull request that carries everything the release needs:
42
+
43
+ - `lib/rbs/version.rb` — set `RBS::VERSION` to the version being released.
44
+ - `Gemfile.lock` — run `bundle install` after the bump; the lockfile records the version too.
45
+ - `CHANGELOG.md` — add a section for the new version, directly under the `# CHANGELOG` heading.
46
+ Sections are newest first.
25
47
 
26
- Once the version is bumped and committed, run on CRuby:
48
+ Label the pull request `skip-changelog`. It carries no change of its own, and without the label it
49
+ shows up in the next release's list — that is why 4.1.0's changelog contains a `Version 4.1.0`
50
+ entry.
51
+
52
+ `rake gem:changelog` lists the pull requests merged since the last release, already formatted:
27
53
 
28
54
  ```console
29
- $ bundle exec rake release
55
+ $ bundle exec rake gem:changelog | pbcopy
30
56
  ```
31
57
 
32
- This re-builds the `ruby`-platform gem and then:
58
+ Where it starts follows `RBS::VERSION`, so bump the version first: a prerelease starts from the
59
+ latest tag, and a release proper skips the prerelease tags and starts from the previous release
60
+ proper. Pass a version to override it (`rake 'gem:changelog[4.1.0]'`). Only the list goes to
61
+ STDOUT, so it pipes cleanly. Pull requests labeled `skip-changelog` are left out and reported on
62
+ STDERR, and pull requests that only touch `rust/` are left out because the crates have their own
63
+ release cycle.
33
64
 
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.
65
+ On a release proper, the `X.Y.Z.pre.N` sections above the previous release are replaced by the one
66
+ section being written their pull requests are in it, and the notes they were published with stay
67
+ on their own GitHub releases.
39
68
 
40
- ### 2. Build and push the `java` gem
69
+ Sort the list into the sections below. `rake gem:changelog:json` prints the same pull requests with
70
+ the changed files, labels, and body of each, which is what the sorting is based on.
41
71
 
42
- The `java` gem is not built by `rake release`, so build and push it manually:
72
+ ```markdown
73
+ ## X.Y.Z (YYYY-MM-DD)
43
74
 
44
- ```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 .
75
+ ### Signature updates
76
+
77
+ ### Language updates
47
78
 
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
79
+ ### Library changes
52
80
 
53
- $ gem push pkg/rbs-X.Y.Z-java.gem
81
+ #### rbs prototype
82
+
83
+ #### rbs collection
84
+
85
+ ### Miscellaneous
54
86
  ```
55
87
 
56
- Optionally confirm it installs and runs on JRuby before pushing:
88
+ The sections always appear in this order; delete the ones that end up empty, which is most of them
89
+ on a small release. Two things scale with the size of the release:
90
+
91
+ - **Summary paragraphs**, above the first section. A patch release usually has none, 4.1.0 has four
92
+ paragraphs, and 4.0.0 has nine.
93
+ - **A list of the types whose signatures changed**, as the first line of `### Signature updates`,
94
+ written as `**Updated classes/modules/methods:**` followed by the names in backticks. Used on
95
+ `X.Y.0` releases only.
96
+
97
+ The date is the day the gem is released, matching the `vX.Y.Z` tag — not the day this pull request
98
+ is opened. Fix it up before step 2 if the pull request sat for a few days.
99
+
100
+ ### 2. Tag the release
101
+
102
+ Once the pull request is merged, tag the merge commit and push the tag:
57
103
 
58
104
  ```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{ })"'
105
+ $ git switch master && git pull
106
+ $ git tag "v$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')"
107
+ $ git push origin --tags
61
108
  ```
62
109
 
110
+ The tag comes before anything is published, so that the gems and the release notes describe a
111
+ commit that is already immutable — and because a tag can be deleted, while a version pushed to
112
+ RubyGems can only be yanked.
113
+
114
+ ### 3. Run the `Release gems` workflow against the tag
115
+
116
+ Dispatch [`release-gems.yml`](../.github/workflows/release-gems.yml) from the Actions tab, picking
117
+ the `vX.Y.Z` tag — **not** a branch — in the ref selector. The trusted publisher has no branch
118
+ condition, so the ref you pick is what decides what gets published; the workflow refuses to run
119
+ unless the tag matches `RBS::VERSION`.
120
+
121
+ It then:
122
+
123
+ - builds `rbs-X.Y.Z.gem`,
124
+ - compiles `rbs_parser.wasm` and builds `rbs-X.Y.Z-java.gem`,
125
+ - checks both: platforms, the C extension on one and its absence on the other, and that the wasm
126
+ module made it into the `java` gem,
127
+ - installs the `java` gem on JRuby and parses with it, so the WebAssembly runtime is exercised
128
+ before anything is published,
129
+ - uploads both gems as an artifact,
130
+ - pushes both to RubyGems through trusted publishing,
131
+ - publishes the GitHub release with the notes from CHANGELOG.md, skipping this last step for
132
+ `.dev.N` versions.
133
+
134
+ Dispatching against a branch runs everything up to the artifact and stops, which is how the build
135
+ is exercised without releasing.
136
+
137
+ ### 4. Start the next development cycle
138
+
139
+ Open another pull request setting `RBS::VERSION` to the next prerelease (`4.1.1` → `4.1.2.pre`),
140
+ with `Gemfile.lock` regenerated, labeled `skip-changelog` like the release pull request itself.
141
+ Without it the version on `master` keeps claiming to be the released version for the whole
142
+ development period, and `rake gem:changelog` reads that version to decide where the next changelog
143
+ starts.
144
+
63
145
  ## Notes
64
146
 
65
147
  - Prereleases (`X.Y.Z.pre.N`) are only installed with `gem install rbs --pre`;
66
148
  a plain `gem install rbs` is unaffected. On JRuby, `gem install rbs [--pre]`
67
149
  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.
150
+ - `Dockerfile.jruby` pins the WASI SDK / Chicory / ASM versions to match the
151
+ `wasm`, `jruby`, and `release-gems` workflows. Keep them in sync when bumping.
data/include/rbs/ast.h CHANGED
@@ -583,7 +583,7 @@ typedef struct rbs_ast_ruby_annotations_class_alias_annotation {
583
583
 
584
584
  rbs_location_range prefix_location;
585
585
  rbs_location_range keyword_location;
586
- struct rbs_type_name *RBS_NONNULL type_name;
586
+ struct rbs_type_name *RBS_NULLABLE type_name;
587
587
  rbs_location_range type_name_location;
588
588
  } rbs_ast_ruby_annotations_class_alias_annotation_t;
589
589
 
@@ -631,7 +631,7 @@ typedef struct rbs_ast_ruby_annotations_module_alias_annotation {
631
631
 
632
632
  rbs_location_range prefix_location;
633
633
  rbs_location_range keyword_location;
634
- struct rbs_type_name *RBS_NONNULL type_name;
634
+ struct rbs_type_name *RBS_NULLABLE type_name;
635
635
  rbs_location_range type_name_location;
636
636
  } rbs_ast_ruby_annotations_module_alias_annotation_t;
637
637
 
@@ -998,12 +998,12 @@ rbs_ast_members_prepend_t *RBS_NONNULL rbs_ast_members_prepend_new(rbs_allocator
998
998
  rbs_ast_members_private_t *RBS_NONNULL rbs_ast_members_private_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location);
999
999
  rbs_ast_members_public_t *RBS_NONNULL rbs_ast_members_public_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location);
1000
1000
  rbs_ast_ruby_annotations_block_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_block_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range ampersand_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_location_range question_location, rbs_location_range type_location, rbs_node_t *RBS_NONNULL type_, rbs_location_range comment_location);
1001
- rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NONNULL type_name, rbs_location_range type_name_location);
1001
+ rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location);
1002
1002
  rbs_ast_ruby_annotations_colon_method_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_colon_method_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *RBS_NONNULL annotations, rbs_node_t *RBS_NONNULL method_type);
1003
1003
  rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_double_splat_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star2_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL param_type, rbs_location_range comment_location);
1004
1004
  rbs_ast_ruby_annotations_instance_variable_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_instance_variable_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_ast_symbol_t *RBS_NONNULL ivar_name, rbs_location_range ivar_name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL type, rbs_location_range comment_location);
1005
1005
  rbs_ast_ruby_annotations_method_types_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *RBS_NONNULL overloads, rbs_location_range_list_t *RBS_NONNULL vertical_bar_locations, rbs_location_range dot3_location);
1006
- rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NONNULL type_name, rbs_location_range type_name_location);
1006
+ rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location);
1007
1007
  rbs_ast_ruby_annotations_module_self_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_self_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_location_range colon_location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range open_bracket_location, rbs_location_range close_bracket_location, rbs_location_range_list_t *RBS_NONNULL args_comma_locations, rbs_location_range comment_location);
1008
1008
  rbs_ast_ruby_annotations_node_type_assertion_t *RBS_NONNULL rbs_ast_ruby_annotations_node_type_assertion_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_t *RBS_NONNULL type);
1009
1009
  rbs_ast_ruby_annotations_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_param_type_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *RBS_NONNULL param_type, rbs_location_range comment_location);
data/lib/rbs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RBS
4
- VERSION = "4.1.0"
4
+ VERSION = "4.1.1.dev.1"
5
5
  end
data/src/ast.c CHANGED
@@ -913,7 +913,7 @@ rbs_ast_ruby_annotations_block_param_type_annotation_t *RBS_NONNULL rbs_ast_ruby
913
913
  return instance;
914
914
  }
915
915
  #line 140 "templates/src/ast.c.erb"
916
- rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NONNULL type_name, rbs_location_range type_name_location) {
916
+ rbs_ast_ruby_annotations_class_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location) {
917
917
  rbs_ast_ruby_annotations_class_alias_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_class_alias_annotation_t);
918
918
 
919
919
  *instance = (rbs_ast_ruby_annotations_class_alias_annotation_t) {
@@ -1001,7 +1001,7 @@ rbs_ast_ruby_annotations_method_types_annotation_t *RBS_NONNULL rbs_ast_ruby_ann
1001
1001
  return instance;
1002
1002
  }
1003
1003
  #line 140 "templates/src/ast.c.erb"
1004
- rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NONNULL type_name, rbs_location_range type_name_location) {
1004
+ rbs_ast_ruby_annotations_module_alias_annotation_t *RBS_NONNULL rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *RBS_NULLABLE type_name, rbs_location_range type_name_location) {
1005
1005
  rbs_ast_ruby_annotations_module_alias_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_module_alias_annotation_t);
1006
1006
 
1007
1007
  *instance = (rbs_ast_ruby_annotations_module_alias_annotation_t) {
data/src/parser.c CHANGED
@@ -405,18 +405,21 @@ NODISCARD
405
405
  static bool parse_keyword_key(rbs_parser_t *parser, rbs_ast_symbol_t **key) {
406
406
  rbs_parser_advance(parser);
407
407
 
408
- rbs_location_range symbol_range = rbs_location_range_current_token(parser);
408
+ rbs_range_t symbol_range = parser->current_token.range;
409
409
 
410
410
  if (parser->next_token.type == pQUESTION) {
411
+ // The `?` is part of the key, so it is part of the location too.
412
+ symbol_range.end = parser->next_token.range.end;
413
+
411
414
  *key = rbs_ast_symbol_new(
412
415
  ALLOCATOR(),
413
- symbol_range,
416
+ RBS_RANGE_LEX2AST(symbol_range),
414
417
  &parser->constant_pool,
415
418
  intern_token_start_end(parser, parser->current_token, parser->next_token)
416
419
  );
417
420
  rbs_parser_advance(parser);
418
421
  } else {
419
- *key = rbs_ast_symbol_new(ALLOCATOR(), symbol_range, &parser->constant_pool, INTERN_TOKEN(parser, parser->current_token));
422
+ *key = rbs_ast_symbol_new(ALLOCATOR(), RBS_RANGE_LEX2AST(symbol_range), &parser->constant_pool, INTERN_TOKEN(parser, parser->current_token));
420
423
  }
421
424
 
422
425
  return true;
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbs
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.1.1.dev.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
@@ -70,7 +70,7 @@ files:
70
70
  - ".github/workflows/comments.yml"
71
71
  - ".github/workflows/dependabot.yml"
72
72
  - ".github/workflows/jruby.yml"
73
- - ".github/workflows/milestone.yml"
73
+ - ".github/workflows/release-gems.yml"
74
74
  - ".github/workflows/ruby.yml"
75
75
  - ".github/workflows/rust.yml"
76
76
  - ".github/workflows/truffleruby.yml"
@@ -649,7 +649,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
649
649
  - !ruby/object:Gem::Version
650
650
  version: '0'
651
651
  requirements: []
652
- rubygems_version: 4.0.3
652
+ rubygems_version: 4.0.17
653
653
  specification_version: 4
654
654
  summary: Type signature for Ruby.
655
655
  test_files: []
@@ -1,91 +0,0 @@
1
- name: Check milestone
2
-
3
- on:
4
- pull_request:
5
- types: [opened, edited, labeled, unlabeled, milestoned, demilestoned, synchronize]
6
- merge_group: {}
7
-
8
- permissions:
9
- contents: read
10
-
11
- jobs:
12
- check:
13
- runs-on: ubuntu-latest
14
-
15
- steps:
16
- # The milestone is a property of the pull request, so there is nothing to
17
- # check once it enters the merge queue. This job still has to run there
18
- # because `check` is a required status check -- it just reports success
19
- # without doing anything.
20
- - uses: actions/checkout@v7
21
- if: github.event_name == 'pull_request'
22
-
23
- - name: Extract RBS::Version
24
- id: version
25
- if: github.event_name == 'pull_request'
26
- run: |
27
- # Extract version string from lib/rbs/version.rb
28
- version=$(ruby -e 'load "lib/rbs/version.rb"; print RBS::VERSION')
29
- echo "version=$version" >> "$GITHUB_OUTPUT"
30
-
31
- # Parse major.minor.patch
32
- IFS='.' read -r major minor patch _rest <<< "$version"
33
- echo "major=$major" >> "$GITHUB_OUTPUT"
34
- echo "minor=$minor" >> "$GITHUB_OUTPUT"
35
- echo "patch=$patch" >> "$GITHUB_OUTPUT"
36
-
37
- echo "RBS::VERSION = $version (major=$major, minor=$minor, patch=$patch)"
38
-
39
- - name: Check milestone
40
- if: github.event_name == 'pull_request'
41
- uses: actions/github-script@v9
42
- with:
43
- script: |
44
- const pr = context.payload.pull_request;
45
- const milestone = pr.milestone;
46
- const labels = pr.labels.map(l => l.name);
47
-
48
- const version = '${{ steps.version.outputs.version }}';
49
- const major = '${{ steps.version.outputs.major }}';
50
- const minor = '${{ steps.version.outputs.minor }}';
51
- const patch = parseInt('${{ steps.version.outputs.patch }}', 10);
52
-
53
- if (!milestone) {
54
- if (labels.includes('no-milestone')) {
55
- core.info('No milestone set, but no-milestone label is present — OK');
56
- return;
57
- }
58
- core.setFailed(
59
- 'No milestone set. Add a milestone or add the "no-milestone" label.'
60
- );
61
- return;
62
- }
63
-
64
- if (labels.includes('no-milestone')) {
65
- core.setFailed(
66
- 'Milestone is set but "no-milestone" label is present. Remove the label or the milestone.'
67
- );
68
- return;
69
- }
70
-
71
- const milestoneName = milestone.title;
72
- core.info(`Milestone: "${milestoneName}", RBS::VERSION: ${version}`);
73
-
74
- // Expected milestone based on version:
75
- // patch == 0 → "RBS major.minor" (e.g. "RBS 4.0")
76
- // patch >= 1 → "RBS major.minor.x" (e.g. "RBS 4.0.x")
77
- let expectedMilestone;
78
- if (patch === 0) {
79
- expectedMilestone = `RBS ${major}.${minor}`;
80
- } else {
81
- expectedMilestone = `RBS ${major}.${minor}.x`;
82
- }
83
-
84
- if (milestoneName !== expectedMilestone) {
85
- core.setFailed(
86
- `Milestone "${milestoneName}" does not match RBS::VERSION ${version}. ` +
87
- `Expected milestone: "${expectedMilestone}"`
88
- );
89
- } else {
90
- core.info(`Milestone "${milestoneName}" matches RBS::VERSION ${version}`);
91
- }