From 5b00c057b9c1fbafedf35b8469d74a409a49034e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 6 Apr 2026 10:53:22 +0900 Subject: [PATCH 01/16] Manually cherry pick from https://github.com/ruby/rubygems/pull/9416/changes/7cd3b9a08a4e1ad47170eafe026932d1492fdfa1 --- spec/bundler/commands/newgem_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 0053e0d8459cf9..5df3b58202791c 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -1741,8 +1741,9 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/build.rs")).to exist end - it "includes rake-compiler constraint" do + it "includes rake-compiler and rb_sys gems constraint" do expect(bundled_app("#{gem_name}/Gemfile").read).to include('gem "rake-compiler"') + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec").read).to include('spec.add_dependency "rb_sys"') end it "depends on compile task for build" do From 3ef41a526f383a1bd50bd21dd631da0c50208df2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:17:15 +0000 Subject: [PATCH 02/16] Bump taiki-e/install-action Bumps the github-actions group with 1 update in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action). Updates `taiki-e/install-action` from 2.71.2 to 2.73.0 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/d858f8113943481093e02986a7586a4819a3bfd6...7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.73.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 665c7cc0c35007..50ab433c7d06d3 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -93,7 +93,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@d858f8113943481093e02986a7586a4819a3bfd6 # v2.71.2 + - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index fd7605367ee83d..6f29b89a27c165 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -119,7 +119,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@d858f8113943481093e02986a7586a4819a3bfd6 # v2.71.2 + - uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} From 995eba7653854d0a20a0d1e6fe1b0b3cd186378a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 6 Apr 2026 17:28:11 +0900 Subject: [PATCH 03/16] Fix pinned gems being removed from bundled_gems by update script Using `next` in `-p` mode skips the implicit `print $_`, causing pinned gem lines (e.g. rbs) to be silently dropped from the output. Replace `next` with an `unless` guard so the line is always printed. Co-Authored-By: Claude Opus 4.6 (1M context) --- tool/update-bundled_gems.rb | 53 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/tool/update-bundled_gems.rb b/tool/update-bundled_gems.rb index c25ec6e08a10a0..565a522aa04776 100755 --- a/tool/update-bundled_gems.rb +++ b/tool/update-bundled_gems.rb @@ -12,33 +12,34 @@ output.print date.strftime("latest_date=%F") if date } if gem = $F[0] - next if pinned.include?(gem) - ver = Gem::Version.new($F[1]) - (gem, src), = Gem::SpecFetcher.fetcher.detect(:latest) {|s| - s.platform == "ruby" && s.name == gem - } - if gem.version > ver - gem = src.fetch_spec(gem) - if ENV["UPDATE_BUNDLED_GEMS_ALL"] - uri = gem.metadata["source_code_uri"] || gem.homepage - uri = uri.sub(%r[\Ahttps://github\.com/[^/]+/[^/]+\K/tree/.*], "").chomp(".git") - else - uri = $F[2] - end - if (!date or gem.date && gem.date > date) and gem.date.to_i != 315_619_200 - # DEFAULT_SOURCE_DATE_EPOCH is meaningless - date = gem.date - end - if $F[3] - if $F[3].include?($F[1]) - $F[3][$F[1]] = gem.version.to_s - elsif Gem::Version.new($F[1]) != gem.version and /\A\h+\z/ =~ $F[3] - $F[3..-1] = [] + unless pinned.include?(gem) + ver = Gem::Version.new($F[1]) + (gem, src), = Gem::SpecFetcher.fetcher.detect(:latest) {|s| + s.platform == "ruby" && s.name == gem + } + if gem.version > ver + gem = src.fetch_spec(gem) + if ENV["UPDATE_BUNDLED_GEMS_ALL"] + uri = gem.metadata["source_code_uri"] || gem.homepage + uri = uri.sub(%r[\Ahttps://github\.com/[^/]+/[^/]+\K/tree/.*], "").chomp(".git") + else + uri = $F[2] + end + if (!date or gem.date && gem.date > date) and gem.date.to_i != 315_619_200 + # DEFAULT_SOURCE_DATE_EPOCH is meaningless + date = gem.date + end + if $F[3] + if $F[3].include?($F[1]) + $F[3][$F[1]] = gem.version.to_s + elsif Gem::Version.new($F[1]) != gem.version and /\A\h+\z/ =~ $F[3] + $F[3..-1] = [] + end end + f = [gem.name, gem.version.to_s, uri, *$F[3..-1]] + $_.gsub!(/\S+\s*(?=\s|$)/) {|s| (f.shift || "").ljust(s.size)} + $_ = [$_, *f].join(" ") unless f.empty? + $_.rstrip! end - f = [gem.name, gem.version.to_s, uri, *$F[3..-1]] - $_.gsub!(/\S+\s*(?=\s|$)/) {|s| (f.shift || "").ljust(s.size)} - $_ = [$_, *f].join(" ") unless f.empty? - $_.rstrip! end end From ed6e70061f77ef5c581bbb27da2e74d8561b5e81 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 6 Apr 2026 18:22:18 +0900 Subject: [PATCH 04/16] Pin RBS to avoid bigdecimal validation failure (#16664) --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 9d3fcd095e6941..90420d7b971b6a 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -16,7 +16,7 @@ net-imap 0.6.3 https://github.com/ruby/net-imap net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime -rbs 4.0.2 https://github.com/ruby/rbs +rbs 4.0.2 https://github.com/ruby/rbs 36a7e8e38df9efd33db83c3f30f0308bdeb84bd9 typeprof 0.31.1 https://github.com/ruby/typeprof debug 1.11.1 https://github.com/ruby/debug 2897edad6d2c2eeb49ffe915192c54572dbe6c82 racc 1.8.1 https://github.com/ruby/racc From e0a7dad99d6f2e85528ca669ace9a9a3d171c843 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 6 Apr 2026 09:23:46 +0000 Subject: [PATCH 05/16] Update bundled gems list as of 2026-04-06 --- NEWS.md | 28 ++++++++++++++++++---------- gems/bundled_gems | 4 ++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index 920c6d181ddfea..bf49b0358f0711 100644 --- a/NEWS.md +++ b/NEWS.md @@ -70,11 +70,11 @@ releases. * RubyGems 4.1.0.dev * bundler 4.1.0.dev * json 2.19.3 - * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2] + * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1], [v2.19.2][json-v2.19.2], [v2.19.3][json-v2.19.3] * openssl 4.0.1 * 4.0.0 to [v4.0.1][openssl-v4.0.1] * prism 1.9.0 - * 1.8.0 to [v1.8.1][prism-v1.8.1], [v1.9.0][prism-v1.9.0] + * 1.8.1 to [v1.9.0][prism-v1.9.0] * resolv 0.7.1 * 0.7.0 to [v0.7.1][resolv-v0.7.1] * stringio 3.2.1.dev @@ -88,20 +88,22 @@ releases. ### The following bundled gems are updated. -* minitest 6.0.2 +* minitest 6.0.3 * test-unit 3.7.7 * 3.7.5 to [3.7.6][test-unit-3.7.6], [3.7.7][test-unit-3.7.7] * net-imap 0.6.3 * 0.6.2 to [v0.6.3][net-imap-v0.6.3] -* rbs 4.0.0 - * 3.10.0 to [v3.10.1][rbs-v3.10.1], [v3.10.2][rbs-v3.10.2], [v3.10.3][rbs-v3.10.3], [v4.0.0.dev.5][rbs-v4.0.0.dev.5], [v4.0.0][rbs-v4.0.0] +* rbs 4.0.2 + * 3.10.0 to [v3.10.1][rbs-v3.10.1], [v3.10.2][rbs-v3.10.2], [v3.10.3][rbs-v3.10.3], [v3.10.4][rbs-v3.10.4], [v4.0.0.dev.5][rbs-v4.0.0.dev.5], [v4.0.0][rbs-v4.0.0], [v4.0.2][rbs-v4.0.2] * mutex_m 0.3.0 +* bigdecimal 4.1.1 + * 4.0.1 to [v4.1.0][bigdecimal-v4.1.0], [v4.1.1][bigdecimal-v4.1.1] * resolv-replace 0.2.0 * 0.1.1 to [v0.2.0][resolv-replace-v0.2.0] * syslog 0.4.0 * 0.3.0 to [v0.4.0][syslog-v0.4.0] -* repl_type_completor 0.1.14 - * 0.1.12 to [v0.1.13][repl_type_completor-v0.1.13], [v0.1.14][repl_type_completor-v0.1.14] +* repl_type_completor 0.1.15 + * 0.1.12 to [v0.1.13][repl_type_completor-v0.1.13], [v0.1.14][repl_type_completor-v0.1.14], [v0.1.15][repl_type_completor-v0.1.15] * pstore 0.2.1 * 0.2.0 to [v0.2.1][pstore-v0.2.1] * rdoc 7.2.0 @@ -167,30 +169,36 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [rdoc-v7.0.1]: https://github.com/ruby/rdoc/releases/tag/v7.0.1 [rdoc-v7.0.2]: https://github.com/ruby/rdoc/releases/tag/v7.0.2 [rdoc-v7.0.3]: https://github.com/ruby/rdoc/releases/tag/v7.0.3 +[prism-v1.8.1]: https://github.com/ruby/prism/releases/tag/v1.8.1 +[zlib-v3.2.3]: https://github.com/ruby/zlib/releases/tag/v3.2.3 +[pstore-v0.2.1]: https://github.com/ruby/pstore/releases/tag/v0.2.1 [json-v2.18.1]: https://github.com/ruby/json/releases/tag/v2.18.1 [json-v2.19.0]: https://github.com/ruby/json/releases/tag/v2.19.0 [json-v2.19.1]: https://github.com/ruby/json/releases/tag/v2.19.1 [json-v2.19.2]: https://github.com/ruby/json/releases/tag/v2.19.2 +[json-v2.19.3]: https://github.com/ruby/json/releases/tag/v2.19.3 [openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1 -[prism-v1.8.1]: https://github.com/ruby/prism/releases/tag/v1.8.1 [prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0 [resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1 [strscan-v3.1.7]: https://github.com/ruby/strscan/releases/tag/v3.1.7 [timeout-v0.6.1]: https://github.com/ruby/timeout/releases/tag/v0.6.1 -[zlib-v3.2.3]: https://github.com/ruby/zlib/releases/tag/v3.2.3 [test-unit-3.7.6]: https://github.com/test-unit/test-unit/releases/tag/3.7.6 [test-unit-3.7.7]: https://github.com/test-unit/test-unit/releases/tag/3.7.7 [net-imap-v0.6.3]: https://github.com/ruby/net-imap/releases/tag/v0.6.3 [rbs-v3.10.1]: https://github.com/ruby/rbs/releases/tag/v3.10.1 [rbs-v3.10.2]: https://github.com/ruby/rbs/releases/tag/v3.10.2 [rbs-v3.10.3]: https://github.com/ruby/rbs/releases/tag/v3.10.3 +[rbs-v3.10.4]: https://github.com/ruby/rbs/releases/tag/v3.10.4 [rbs-v4.0.0.dev.5]: https://github.com/ruby/rbs/releases/tag/v4.0.0.dev.5 [rbs-v4.0.0]: https://github.com/ruby/rbs/releases/tag/v4.0.0 +[rbs-v4.0.2]: https://github.com/ruby/rbs/releases/tag/v4.0.2 +[bigdecimal-v4.1.0]: https://github.com/ruby/bigdecimal/releases/tag/v4.1.0 +[bigdecimal-v4.1.1]: https://github.com/ruby/bigdecimal/releases/tag/v4.1.1 [resolv-replace-v0.2.0]: https://github.com/ruby/resolv-replace/releases/tag/v0.2.0 [syslog-v0.4.0]: https://github.com/ruby/syslog/releases/tag/v0.4.0 [repl_type_completor-v0.1.13]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.13 [repl_type_completor-v0.1.14]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.14 -[pstore-v0.2.1]: https://github.com/ruby/pstore/releases/tag/v0.2.1 +[repl_type_completor-v0.1.15]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.15 [rdoc-v7.1.0]: https://github.com/ruby/rdoc/releases/tag/v7.1.0 [rdoc-v7.2.0]: https://github.com/ruby/rdoc/releases/tag/v7.2.0 [win32ole-v1.9.3]: https://github.com/ruby/win32ole/releases/tag/v1.9.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index 90420d7b971b6a..f9075621666abd 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 6.0.2 https://github.com/minitest/minitest +minitest 6.0.3 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.7 https://github.com/test-unit/test-unit @@ -23,7 +23,7 @@ racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m getoptlong 0.2.1 https://github.com/ruby/getoptlong base64 0.3.0 https://github.com/ruby/base64 -bigdecimal 4.0.1 https://github.com/ruby/bigdecimal +bigdecimal 4.1.1 https://github.com/ruby/bigdecimal observer 0.1.2 https://github.com/ruby/observer abbrev 0.1.2 https://github.com/ruby/abbrev resolv-replace 0.2.0 https://github.com/ruby/resolv-replace From 9dab3c51fd5e18b18838e0da4ee20f313d25ba43 Mon Sep 17 00:00:00 2001 From: OKURA Masafumi Date: Mon, 6 Apr 2026 18:34:36 +0900 Subject: [PATCH 06/16] [DOC] Mention `Hash#default_proc=` affects Proc object from `Hash#to_proc` ```ruby h = {foo: 0, bar: 1, baz: 2} proc = h.to_proc proc.call(:nosuch) # => nil h.default_proc = proc {'wow!'} # This affects `proc` immediately proc.call(:nosuch) # => 'wow!' ``` This behavior is a bit of surprise to me, and I could not find any mention to this behavior in current doc. --- hash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hash.c b/hash.c index 773df7e78d8c7f..79dbd5d8e90f0a 100644 --- a/hash.c +++ b/hash.c @@ -5012,6 +5012,8 @@ hash_proc_call(RB_BLOCK_CALL_FUNC_ARGLIST(key, hash)) * proc.call(:foo) # => 0 * proc.call(:bar) # => 1 * proc.call(:nosuch) # => nil + * h.default_proc = proc { |hash, key| "Missing key: #{key}" } # This affect the existing proc object + * proc.call(:nosuch) # => "Missing key: #{nosuch}" * * Related: see {Methods for Converting}[rdoc-ref:Hash@Methods+for+Converting]. */ From eebd2f7d76554a6f4a63e69007aece8bb5dda55b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Apr 2026 13:41:09 +0900 Subject: [PATCH 07/16] [ruby/resolv] Use early return and remove useless assignments https://github.com/ruby/resolv/commit/451d3f367c --- lib/resolv.rb | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 9720b52c00fef3..a0316147324386 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3281,12 +3281,10 @@ def self.create(arg) when Size return arg when String - scalar = '' - if Regex =~ arg - scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C") - else + unless Regex =~ arg raise ArgumentError.new("not a properly formed Size string: " + arg) end + scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C") return Size.new(scalar) else raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}") @@ -3346,16 +3344,14 @@ def self.create(arg) when Coord return arg when String - coordinates = '' - if Regex =~ arg && $1.to_f < 180 - m = $~ - hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 - coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + - (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") - orientation = m[4][/[NS]/] ? 'lat' : 'lon' - else + unless Regex =~ arg && $1.to_f < 180 raise ArgumentError.new("not a properly formed Coord string: " + arg) end + m = $~ + hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 + coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + + (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") + orientation = m[4][/[NS]/] ? 'lat' : 'lon' return Coord.new(coordinates,orientation) else raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}") @@ -3440,12 +3436,10 @@ def self.create(arg) when Alt return arg when String - altitude = '' - if Regex =~ arg - altitude = [($1.to_f*(1e2))+(1e7)].pack("N") - else + unless Regex =~ arg raise ArgumentError.new("not a properly formed Alt string: " + arg) end + altitude = [($1.to_f*(1e2))+(1e7)].pack("N") return Alt.new(altitude) else raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}") From 53b5f9ea09c482b7aca3ce6538695f94bbbf8822 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Apr 2026 13:55:32 +0900 Subject: [PATCH 08/16] [ruby/resolv] Use `Regexp#match` to achieve matched data This method is available since ruby 1.9.0. https://github.com/ruby/resolv/commit/1e05d06fbd --- lib/resolv.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index a0316147324386..6109278ede6386 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3344,10 +3344,9 @@ def self.create(arg) when Coord return arg when String - unless Regex =~ arg && $1.to_f < 180 + unless (m = Regex.match(arg)) && m[1].to_i < 180 raise ArgumentError.new("not a properly formed Coord string: " + arg) end - m = $~ hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") From 90e2253dcae012c8f528b193ad29d9ce81852164 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Apr 2026 14:14:10 +0900 Subject: [PATCH 09/16] [ruby/resolv] Fix `Resolv::LOC::Coord` hemisphere calculation Fix `Resolv::LOC::Coord.create` hemisphere handling for LOC coordinates. `hemi` was always `1` because `m[4]` matched `([NESW])`. https://github.com/ruby/resolv/commit/043b5bf624 --- lib/resolv.rb | 10 +++++----- test/resolv/test_resource.rb | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 6109278ede6386..ddb14bb3d1b14c 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3347,11 +3347,11 @@ def self.create(arg) unless (m = Regex.match(arg)) && m[1].to_i < 180 raise ArgumentError.new("not a properly formed Coord string: " + arg) end - hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1 - coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) + - (m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N") - orientation = m[4][/[NS]/] ? 'lat' : 'lon' - return Coord.new(coordinates,orientation) + + dir = m[4] + hemi = dir[/[NE]/] ? 1 : -1 + arc = (m[1].to_i * 3_600_000) + (m[2].to_i * 60_000) + (m[3].to_f * 1_000).to_i + return Coord.new([arc * hemi + (2**31)].pack("N"), dir[/[NS]/] ? "lat" : "lon") else raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}") end diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index 434380236e6721..93c9946ccfd798 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -20,10 +20,6 @@ def test_hash assert_equal(@name1.hash, @name2.hash, bug10857) end - def test_coord - Resolv::LOC::Coord.create('1 2 1.1 N') - end - def test_srv_no_compress # Domain name in SRV RDATA should not be compressed issue29 = 'https://github.com/ruby/resolv/issues/29' @@ -33,6 +29,31 @@ def test_srv_no_compress end end +class TestResolvResourceLOC < Test::Unit::TestCase + def test_coord + assert_coord('1 2 1.1 N', 'lat', 0x8038c78c) + assert_coord('42 21 43.952 N', 'lat', 0x89170690) + assert_coord('71 5 6.344 W', 'lon', 0x70bf2dd8) + assert_coord('52 14 05.000 N', 'lat', 0x8b3556c8) + assert_coord('90 0 0.000 N', 'lat', 0x934fd900) + assert_coord('90 0 0.000 S', 'lat', 0x6cb02700) + assert_coord('00 8 50.000 E', 'lon', 0x80081650) + assert_coord('0 8 50.001 E', 'lon', 0x80081651) + assert_coord('32 07 19.000 S', 'lat', 0x791b7d28) + assert_coord('116 02 25.000 E', 'lon', 0x98e64868) + assert_coord('116 02 25.000 W', 'lon', 0x6719b798) + assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 E')} + assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 W')} + end + + private def assert_coord(input, orientation, coordinate) + coord = Resolv::LOC::Coord.create(input) + + assert_equal(orientation, coord.orientation) + assert_equal([coordinate].pack("N"), coord.coordinates) + end +end + class TestResolvResourceCAA < Test::Unit::TestCase def test_caa_roundtrip raw_msg = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x03new\x07example\x03com\x00\x01\x01\x00\x01\x00\x00\x00\x00\x00\x16\x00\x05issueca1.example.net\xC0\x0C\x01\x01\x00\x01\x00\x00\x00\x00\x00\x0C\x80\x03tbsUnknown".b From 6527727cf752971acc7c06175969185d4e7a8222 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Apr 2026 16:16:50 +0900 Subject: [PATCH 10/16] [ruby/resolv] Tighten argument check in `Resolv::LOC::Coord.create` Limit the east-west direction to 180 degrees or less and the north-south direction to 90 degrees or less, respectively. https://github.com/ruby/resolv/commit/8075a1fe76 --- lib/resolv.rb | 20 ++++++++++++++------ test/resolv/test_resource.rb | 4 ++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index ddb14bb3d1b14c..66fd646bc19f47 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3331,7 +3331,10 @@ class Coord # Regular expression LOC Coord must match. - Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ + Regex = /\A0*(\d{1,3})\s([0-5]?\d)\s([0-5]?\d(?:\.\d+)?)\s([NESW])\z/ + + # Bias for the equator/prime meridian, in thousandths of a second of arc. + Bias = 1 << 31 ## # Creates a new LOC::Coord from +arg+ which may be: @@ -3344,14 +3347,19 @@ def self.create(arg) when Coord return arg when String - unless (m = Regex.match(arg)) && m[1].to_i < 180 + unless m = Regex.match(arg) raise ArgumentError.new("not a properly formed Coord string: " + arg) end + arc = (m[1].to_i * 3_600_000) + (m[2].to_i * 60_000) + (m[3].to_f * 1_000).to_i dir = m[4] + lat = dir[/[NS]/] + unless arc <= (lat ? 324_000_000 : 648_000_000) # (lat ? 90 : 180) * 3_600_000 + raise ArgumentError.new("out of range as Coord: #{arg}") + end + hemi = dir[/[NE]/] ? 1 : -1 - arc = (m[1].to_i * 3_600_000) + (m[2].to_i * 60_000) + (m[3].to_f * 1_000).to_i - return Coord.new([arc * hemi + (2**31)].pack("N"), dir[/[NS]/] ? "lat" : "lon") + return new([arc * hemi + Bias].pack("N"), lat ? "lat" : "lon") else raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}") end @@ -3359,10 +3367,10 @@ def self.create(arg) # Internal use; use self.create. def initialize(coordinates,orientation) - unless coordinates.kind_of?(String) + unless coordinates.kind_of?(String) and coordinates.bytesize == 4 raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") end - unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/] + unless orientation == "lon" || orientation == "lat" raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"') end @coordinates = coordinates diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index 93c9946ccfd798..95e457557a7e59 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -42,6 +42,10 @@ def test_coord assert_coord('32 07 19.000 S', 'lat', 0x791b7d28) assert_coord('116 02 25.000 E', 'lon', 0x98e64868) assert_coord('116 02 25.000 W', 'lon', 0x6719b798) + assert_coord('180 00 00.000 E', 'lon', 0xa69fb200) + assert_coord('180 00 00.000 W', 'lon', 0x59604e00) + assert_raise(ArgumentError) {Resolv::LOC::Coord.create('90 0 0.001 N')} + assert_raise(ArgumentError) {Resolv::LOC::Coord.create('90 0 0.001 S')} assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 E')} assert_raise(ArgumentError) {Resolv::LOC::Coord.create('180 0 0.001 W')} end From d6518b19a218f69277c22a379d6c543f00996797 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 5 Apr 2026 19:58:18 +0900 Subject: [PATCH 11/16] [ruby/resolv] Fix subsecond representation in `Resolv::LOC::Coord#to_s` - Leading zeros in fractional part are significant, must not be suppressed. - Use `Integer#divmod` to calculate division and module at once. - Use simple comparison for exact match than regexp. https://github.com/ruby/resolv/commit/0e3109ca3b --- lib/resolv.rb | 23 +++++++++-------------- test/resolv/test_resource.rb | 1 + 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 66fd646bc19f47..bc3f48ea1ef823 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3387,22 +3387,17 @@ def initialize(coordinates,orientation) attr_reader :orientation def to_s # :nodoc: - c = @coordinates.unpack("N").join.to_i - val = (c - (2**31)).abs - fracsecs = (val % 1e3).to_i.to_s - val = val / 1e3 - secs = (val % 60).to_i.to_s - val = val / 60 - mins = (val % 60).to_i.to_s - degs = (val / 60).to_i.to_s - posi = (c >= 2**31) - case posi - when true - hemi = @orientation[/^lat$/] ? "N" : "E" + c, = @coordinates.unpack("N") + val = (c -= Bias).abs + val, fracsecs = val.divmod(1000) + val, secs = val.divmod(60) + degs, mins = val.divmod(60) + hemi = if c.negative? + @orientation == "lon" ? "W" : "S" else - hemi = @orientation[/^lon$/] ? "W" : "S" + @orientation == "lat" ? "N" : "E" end - return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi + format("%d %02d %02d.%03d %s", degs, mins, secs, fracsecs, hemi) end def inspect # :nodoc: diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index 95e457557a7e59..732759adad650f 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -55,6 +55,7 @@ def test_coord assert_equal(orientation, coord.orientation) assert_equal([coordinate].pack("N"), coord.coordinates) + assert_equal(coord, Resolv::LOC::Coord.create(coord.to_s)) end end From b59ae1616058606f4f834db7cebc882e349a2903 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Apr 2026 16:29:57 +0900 Subject: [PATCH 12/16] [ruby/resolv] Tighten argument check in `Resolv::LOC::Size.create` Limit the size to between 0cm and less than 100,000km. https://github.com/ruby/resolv/commit/9d9e0c3044 --- lib/resolv.rb | 9 ++++++--- test/resolv/test_resource.rb | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index bc3f48ea1ef823..d23313c0ea5966 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3268,7 +3268,7 @@ class Size # Regular expression LOC size must match. - Regex = /^(\d+\.*\d*)[m]$/ + Regex = /\A0*(\d{1,8}(?:\.\d+)?)m\z/ ## # Creates a new LOC::Size from +arg+ which may be: @@ -3284,8 +3284,11 @@ def self.create(arg) unless Regex =~ arg raise ArgumentError.new("not a properly formed Size string: " + arg) end - scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C") - return Size.new(scalar) + unless (0.0...1e8) === (scalar = $1.to_f) + raise ArgumentError.new("out of range as Size: #{arg}") + end + str = (scalar * 100).to_i.to_s + return new([(str[0].to_i << 4) + (str.bytesize-1)].pack("C")) else raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}") end diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index 732759adad650f..fd080fb8268eed 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -30,6 +30,23 @@ def test_srv_no_compress end class TestResolvResourceLOC < Test::Unit::TestCase + def test_size_create + assert_size("0.0m", 0, 0) + assert_size("0.01m", 1, 0) + assert_size("0.09m", 9, 0) + assert_size("0.11m", 1, 1) + assert_size("1.0m", 1, 2) + assert_size("1234.56m", 1, 5) + assert_size("12345678.90m", 1, 9) + assert_size("98765432.10m", 9, 9) + assert_raise(ArgumentError) {Resolv::LOC::Size.create("100000000.00m")} + end + + private def assert_size(input, base, power) + size = Resolv::LOC::Size.create(input) + assert_equal([(base << 4) + power], size.scalar.unpack("C")) + end + def test_coord assert_coord('1 2 1.1 N', 'lat', 0x8038c78c) assert_coord('42 21 43.952 N', 'lat', 0x89170690) From 38411cad8be463c853f45e4d5bdfb476c9a6cb16 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Apr 2026 16:30:59 +0900 Subject: [PATCH 13/16] [ruby/resolv] Fix centimeters representation in `Resolv::LOC::Size#to_s` `Size` smaller than 1m was represented as rational. ```ruby Resolv::LOC::Size.create("0.01m").to_s #=> "1/100m" ``` Fix to represent in the proper form for `create`. https://github.com/ruby/resolv/commit/2ff9e0585d --- lib/resolv.rb | 4 ++-- test/resolv/test_resource.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index d23313c0ea5966..6559b387542e99 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3305,8 +3305,8 @@ def initialize(scalar) attr_reader :scalar def to_s # :nodoc: - s = @scalar.unpack("H2").join.to_s - return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m" + s, = @scalar.unpack("C") + return "#{(s >> 4) * (10.0 ** ((s & 0xf) - 2))}m" end def inspect # :nodoc: diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index fd080fb8268eed..5a310f537146d7 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -45,6 +45,7 @@ def test_size_create private def assert_size(input, base, power) size = Resolv::LOC::Size.create(input) assert_equal([(base << 4) + power], size.scalar.unpack("C")) + assert_equal(size, Resolv::LOC::Size.create(size.to_s)) end def test_coord From 380f9477d307422fc0589bfcec9ca48043d6a471 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Apr 2026 16:33:33 +0900 Subject: [PATCH 14/16] [ruby/resolv] Tighten argument check in `Resolv::LOC::Alt.create` Limit the range from -100 km to the upper limit of the 32-bit representation. https://github.com/ruby/resolv/commit/e6dfbe7cb0 --- lib/resolv.rb | 12 +++++++++--- test/resolv/test_resource.rb | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index 6559b387542e99..b7521fde4f8899 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3428,7 +3428,10 @@ class Alt # Regular expression LOC Alt must match. - Regex = /^([+-]*\d+\.*\d*)[m]$/ + Regex = /\A([+-]?0*\d{1,8}(?:\.\d+)?)m\z/ + + # Bias to a base of 100,000m below the WGS 84 reference spheroid. + Bias = 100_000_00 ## # Creates a new LOC::Alt from +arg+ which may be: @@ -3444,8 +3447,11 @@ def self.create(arg) unless Regex =~ arg raise ArgumentError.new("not a properly formed Alt string: " + arg) end - altitude = [($1.to_f*(1e2))+(1e7)].pack("N") - return Alt.new(altitude) + altitude = ($1.to_f * 100).to_i + Bias + unless (0...0x1_0000_0000) === altitude + raise ArgumentError.new("out of raise as Alt: #{arg}") + end + return new([altitude].pack("N")) else raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}") end diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index 5a310f537146d7..ff0f56913b3adb 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -75,6 +75,27 @@ def test_coord assert_equal([coordinate].pack("N"), coord.coordinates) assert_equal(coord, Resolv::LOC::Coord.create(coord.to_s)) end + + def test_alt + assert_alt("0.0m", 0) + assert_alt("+0.0m", 0) + assert_alt("-0.0m", 0) + assert_alt("+0.01m", 1) + assert_alt("1.0m", 100) + assert_alt("+1.0m", 100) + assert_alt("100000.0m", +10000000) + assert_alt("+100000.0m", +10000000) + assert_alt("-100000.0m", -10000000) + assert_alt("+42849672.95m", 0xffff_ffff-100_000_00) + assert_raise(ArgumentError) {Resolv::LOC::Alt.create("-100000.01m")} + assert_raise(ArgumentError) {Resolv::LOC::Alt.create("+42849672.96m")} + end + + private def assert_alt(input, altitude) + alt = Resolv::LOC::Alt.create(input) + + assert_equal([altitude + 1e7].pack("N"), alt.altitude) + end end class TestResolvResourceCAA < Test::Unit::TestCase From a371a207f1e2ce536ce6d1e68dba3071387a952c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 6 Apr 2026 16:34:19 +0900 Subject: [PATCH 15/16] [ruby/resolv] Refine `Resolv::LOC::Alt#to_s` Extract the first element instead of `join.to_i` (`String#unpack1` is available since ruby 2.4.0, but this gem still supports 2.3). https://github.com/ruby/resolv/commit/f518da95ae --- lib/resolv.rb | 4 ++-- test/resolv/test_resource.rb | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/resolv.rb b/lib/resolv.rb index b7521fde4f8899..6b58f92813b435 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3468,8 +3468,8 @@ def initialize(altitude) attr_reader :altitude def to_s # :nodoc: - a = @altitude.unpack("N").join.to_i - return ((a.to_f/1e2)-1e5).to_s + "m" + a, = @altitude.unpack("N") + return "#{(a - Bias).fdiv(100)}m" end def inspect # :nodoc: diff --git a/test/resolv/test_resource.rb b/test/resolv/test_resource.rb index ff0f56913b3adb..3a1c9ae3c3ea41 100644 --- a/test/resolv/test_resource.rb +++ b/test/resolv/test_resource.rb @@ -95,6 +95,7 @@ def test_alt alt = Resolv::LOC::Alt.create(input) assert_equal([altitude + 1e7].pack("N"), alt.altitude) + assert_equal(alt, Resolv::LOC::Alt.create(alt.to_s)) end end From 0e94ca39e0972d92e27baf6ca7f7cdb45a146188 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:52:57 +0200 Subject: [PATCH 16/16] [ruby/prism] Implement `on_op` for ripper Mostly the same as for `on_kw`. Stop comparing order for a handful of events. They are emitted by ripper in a order that is not easy for prism to replicate. For example in`foo, bar, baz = 123`, `op` for `=` is emitted before the last mass assign identifier (so `foo`, `bar`, `=`, `baz`. I don't want to replicate that if I don't have to. Same for `::` in `A::B = 123` etc. https://github.com/ruby/prism/commit/4d88aed5e9 --- lib/prism/translation/ripper.rb | 188 ++++++++++++++++++++++++++++++-- test/prism/ruby/ripper_test.rb | 19 ++-- 2 files changed, 192 insertions(+), 15 deletions(-) diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 2f66bab97ee5b8..dd443e207f1da1 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -28,7 +28,6 @@ module Translation # - on_lbracket # - on_lparen # - on_nl - # - on_op # - on_operator_ambiguous # - on_rbrace # - on_rbracket @@ -647,6 +646,10 @@ def visit_alias_global_variable_node(node) # ^^^^^^^^^ def visit_alternation_pattern_node(node) left = visit_pattern_node(node.left) + + bounds(node.operator_loc) + on_op("|") + right = visit_pattern_node(node.right) bounds(node.location) @@ -667,10 +670,14 @@ def visit_alternation_pattern_node(node) # ^^^^^^^ def visit_and_node(node) left = visit(node.left) + + bounds(node.operator_loc) if node.operator == "and" - bounds(node.operator_loc) on_kw("and") + else + on_op("&&") end + right = visit(node.right) bounds(node.location) @@ -844,6 +851,9 @@ def visit_array_pattern_node(node) requireds = visit_all(node.requireds) if node.requireds.any? rest = if (rest_node = node.rest).is_a?(SplatNode) + bounds(rest_node.operator_loc) + on_op("*") + if rest_node.expression.nil? bounds(rest_node.location) on_var_field(nil) @@ -869,6 +879,12 @@ def visit_arguments_node(node) # ^^^^ def visit_assoc_node(node) key = visit(node.key) + + if node.operator_loc + bounds(node.operator_loc) + on_op("=>") + end + value = visit(node.value) bounds(node.location) @@ -881,6 +897,9 @@ def visit_assoc_node(node) # { **foo } # ^^^^^ def visit_assoc_splat_node(node) + bounds(node.operator_loc) + on_op("**") + value = visit(node.value) bounds(node.location) @@ -974,6 +993,8 @@ def visit_begin_node(node) # foo(&bar) # ^^^^ def visit_block_argument_node(node) + bounds(node.operator_loc) + on_op("&") visit(node.expression) end @@ -987,13 +1008,13 @@ def visit_block_local_variable_node(node) # Visit a BlockNode. def visit_block_node(node) braces = node.opening == "{" - parameters = visit(node.parameters) - unless braces bounds(node.opening_loc) on_kw("do") end + parameters = visit(node.parameters) + body = case node.body when nil @@ -1032,6 +1053,9 @@ def visit_block_node(node) # def foo(&bar); end # ^^^^ def visit_block_parameter_node(node) + bounds(node.operator_loc) + on_op("&") + if node.name_loc.nil? bounds(node.location) on_blockarg(nil) @@ -1046,6 +1070,9 @@ def visit_block_parameter_node(node) # A block's parameters. def visit_block_parameters_node(node) + bounds(node.opening_loc) + on_op("|") + parameters = if node.parameters.nil? on_params(nil, nil, nil, nil, nil, nil, nil) @@ -1060,6 +1087,9 @@ def visit_block_parameters_node(node) false end + bounds(node.closing_loc) + on_op("|") + bounds(node.location) on_block_var(parameters, locals) end @@ -1126,6 +1156,9 @@ def visit_call_node(node) end end + bounds(node.equal_loc) + on_op("=") + bounds(node.location) call = on_aref_field(receiver, arguments) value = visit_write_value(last_argument) @@ -1133,13 +1166,15 @@ def visit_call_node(node) bounds(last_argument.location) on_assign(call, value) when :-@, :+@, :~ - receiver = visit(node.receiver) + bounds(node.message_loc) + on_op(node.message) + receiver = visit(node.receiver) bounds(node.location) on_unary(node.name, receiver) when :! + bounds(node.message_loc) if node.message == "not" - bounds(node.message_loc) on_kw("not") receiver = @@ -1150,6 +1185,8 @@ def visit_call_node(node) bounds(node.location) on_unary(:not, receiver) else + on_op("!") + receiver = visit(node.receiver) bounds(node.location) @@ -1157,6 +1194,10 @@ def visit_call_node(node) end when *BINARY_OPERATORS receiver = visit(node.receiver) + + bounds(node.message_loc) + on_op(node.message) + value = visit(node.arguments.arguments.first) bounds(node.location) @@ -1203,6 +1244,11 @@ def visit_call_node(node) visit_token(node.message, false) end + if node.equal_loc + bounds(node.equal_loc) + on_op("=") + end + if node.name.end_with?("=") && !node.message.end_with?("=") && !node.arguments.nil? && node.block.nil? value = visit_write_value(node.arguments.arguments.first) @@ -1354,6 +1400,9 @@ def visit_call_target_node(node) if node.call_operator == "::" receiver = visit(node.receiver) + bounds(node.call_operator_loc) + on_op("::") + bounds(node.message_loc) message = visit_token(node.message) @@ -1377,6 +1426,10 @@ def visit_call_target_node(node) # ^^^^^^^^^^ def visit_capture_pattern_node(node) value = visit(node.value) + + bounds(node.operator_loc) + on_op("=>") + target = visit(node.target) bounds(node.location) @@ -1447,6 +1500,11 @@ def visit_class_node(node) visit(node.constant_path) end + if node.inheritance_operator_loc + bounds(node.inheritance_operator_loc) + on_op("<") + end + superclass = visit(node.superclass) bodystmt = visit_body_node(node.superclass&.location || node.constant_path.location, node.body, node.superclass.nil?) @@ -1472,6 +1530,10 @@ def visit_class_variable_read_node(node) def visit_class_variable_write_node(node) bounds(node.name_loc) target = on_var_field(on_cvar(node.name.to_s)) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -1542,6 +1604,10 @@ def visit_constant_read_node(node) def visit_constant_write_node(node) bounds(node.name_loc) target = on_var_field(on_const(node.name.to_s)) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -1601,6 +1667,11 @@ def visit_constant_target_node(node) # ^^^^^^^^ def visit_constant_path_node(node) if node.parent.nil? + if node.delimiter_loc + bounds(node.delimiter_loc) + on_op("::") + end + bounds(node.name_loc) child = on_const(node.name.to_s) @@ -1609,6 +1680,9 @@ def visit_constant_path_node(node) else parent = visit(node.parent) + bounds(node.delimiter_loc) + on_op("::") + bounds(node.name_loc) child = on_const(node.name.to_s) @@ -1624,6 +1698,10 @@ def visit_constant_path_node(node) # ^^^^^^^^ ^^^^^^^^ def visit_constant_path_write_node(node) target = visit_constant_path_write_node_target(node.target) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -1633,6 +1711,11 @@ def visit_constant_path_write_node(node) # Visit a constant path that is part of a write node. private def visit_constant_path_write_node_target(node) if node.parent.nil? + if node.delimiter_loc + bounds(node.delimiter_loc) + on_op("::") + end + bounds(node.name_loc) child = on_const(node.name.to_s) @@ -1641,6 +1724,9 @@ def visit_constant_path_write_node(node) else parent = visit(node.parent) + bounds(node.delimiter_loc) + on_op("::") + bounds(node.name_loc) child = on_const(node.name.to_s) @@ -1726,6 +1812,11 @@ def visit_def_node(node) parameters = on_paren(parameters) end + if node.equal_loc + bounds(node.equal_loc) + on_op("=") + end + bodystmt = if node.equal_loc.nil? visit_body_node(node.rparen_loc || node.end_keyword_loc, node.body) @@ -1864,6 +1955,10 @@ def visit_false_node(node) # ^^^^^^^^^^^ def visit_find_pattern_node(node) constant = visit(node.constant) + + bounds(node.left.operator_loc) + on_op("*") + left = if node.left.expression.nil? bounds(node.left.location) @@ -1873,6 +1968,10 @@ def visit_find_pattern_node(node) end requireds = visit_all(node.requireds) if node.requireds.any? + + bounds(node.right.operator_loc) + on_op("*") + right = if node.right.expression.nil? bounds(node.right.location) @@ -1889,6 +1988,10 @@ def visit_find_pattern_node(node) # ^^^^^^^^^^ def visit_flip_flop_node(node) left = visit(node.left) + + bounds(node.operator_loc) + on_op(node.operator) + right = visit(node.right) bounds(node.location) @@ -1939,6 +2042,7 @@ def visit_for_node(node) # ^^^ def visit_forwarding_arguments_node(node) bounds(node.location) + on_op("...") on_args_forward end @@ -1946,6 +2050,7 @@ def visit_forwarding_arguments_node(node) # ^^^ def visit_forwarding_parameter_node(node) bounds(node.location) + on_op("...") on_args_forward end @@ -1984,6 +2089,10 @@ def visit_global_variable_read_node(node) def visit_global_variable_write_node(node) bounds(node.name_loc) target = on_var_field(on_gvar(node.name.to_s)) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -2080,6 +2189,8 @@ def visit_hash_pattern_node(node) rest = case node.rest when AssocSplatNode + bounds(node.rest.operator_loc) + on_op("**") visit(node.rest.value) when NoKeywordsParameterNode bounds(node.rest.location) @@ -2101,7 +2212,15 @@ def visit_hash_pattern_node(node) def visit_if_node(node) if node.then_keyword == "?" predicate = visit(node.predicate) + + bounds(node.then_keyword_loc) + on_op("?") + truthy = visit(node.statements.body.first) + + bounds(node.subsequent.else_keyword_loc) + on_op(":") + falsy = visit(node.subsequent.statements.body.first) bounds(node.location) @@ -2261,6 +2380,10 @@ def visit_instance_variable_read_node(node) def visit_instance_variable_write_node(node) bounds(node.name_loc) target = on_var_field(on_ivar(node.name.to_s)) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -2464,6 +2587,9 @@ def visit_keyword_hash_node(node) # def foo(**); end # ^^ def visit_keyword_rest_parameter_node(node) + bounds(node.operator_loc) + on_op("**") + if node.name_loc.nil? bounds(node.location) on_kwrest_param(nil) @@ -2559,6 +2685,10 @@ def visit_local_variable_read_node(node) def visit_local_variable_write_node(node) bounds(node.name_loc) target = on_var_field(on_ident(node.name_loc.slice)) + + bounds(node.operator_loc) + on_op("=") + value = visit_write_value(node.value) bounds(node.location) @@ -2644,6 +2774,10 @@ def visit_match_predicate_node(node) # ^^^^^^^^^^ def visit_match_required_node(node) value = visit(node.value) + + bounds(node.operator_loc) + on_op("=>") + pattern = on_in(visit_pattern_node(node.pattern), nil, nil) on_case(value, pattern) @@ -2744,6 +2878,9 @@ def visit_multi_write_node(node) bounds(node.location) targets = visit_multi_target_node_targets(node.lefts, node.rest, node.rights, true) + bounds(node.operator_loc) + on_op("=") + unless node.lparen_loc.nil? bounds(node.lparen_loc) targets = on_mlhs_paren(targets) @@ -2785,6 +2922,8 @@ def visit_nil_node(node) # def foo(&nil); end # ^^^^ def visit_no_block_parameter_node(node) + bounds(node.operator_loc) + on_op("&") bounds(node.keyword_loc) on_kw("nil") bounds(node.location) @@ -2794,6 +2933,8 @@ def visit_no_block_parameter_node(node) # def foo(**nil); end # ^^^^^ def visit_no_keywords_parameter_node(node) + bounds(node.operator_loc) + on_op("**") bounds(node.keyword_loc) on_kw("nil") bounds(node.location) @@ -2829,6 +2970,10 @@ def visit_optional_keyword_parameter_node(node) def visit_optional_parameter_node(node) bounds(node.name_loc) name = visit_token(node.name.to_s) + + bounds(node.operator_loc) + on_op("=") + value = visit(node.value) [name, value] @@ -2838,10 +2983,14 @@ def visit_optional_parameter_node(node) # ^^^^^^ def visit_or_node(node) left = visit(node.left) + + bounds(node.operator_loc) if node.operator == "or" - bounds(node.operator_loc) on_kw("or") + else + on_op("||") end + right = visit(node.right) bounds(node.location) @@ -2892,6 +3041,9 @@ def visit_parentheses_node(node) # foo => ^(bar) # ^^^^^^ def visit_pinned_expression_node(node) + bounds(node.operator_loc) + on_op("^") + expression = visit(node.expression) bounds(node.location) @@ -2901,6 +3053,9 @@ def visit_pinned_expression_node(node) # foo = 1 and bar => ^foo # ^^^^ def visit_pinned_variable_node(node) + bounds(node.operator_loc) + on_op("^") + visit(node.variable) end @@ -2954,6 +3109,10 @@ def visit_program_node(node) # ^^^^ def visit_range_node(node) left = visit(node.left) + + bounds(node.operator_loc) + on_op(node.operator) + right = visit(node.right) bounds(node.location) @@ -3070,6 +3229,11 @@ def visit_rescue_node(node) end end + if node.operator_loc + bounds(node.operator_loc) + on_op("=>") + end + reference = visit(node.reference) statements = if node.statements.nil? @@ -3091,6 +3255,9 @@ def visit_rescue_node(node) # def foo(*); end # ^ def visit_rest_parameter_node(node) + bounds(node.operator_loc) + on_op("*") + if node.name_loc.nil? bounds(node.location) on_rest_param(nil) @@ -3145,6 +3312,8 @@ def visit_shareable_constant_node(node) def visit_singleton_class_node(node) bounds(node.class_keyword_loc) on_kw("class") + bounds(node.operator_loc) + on_op("<<") expression = visit(node.expression) bodystmt = visit_body_node(node.body&.location || node.end_keyword_loc, node.body) @@ -3186,6 +3355,8 @@ def visit_source_line_node(node) # def foo(*); bar(*); end # ^ def visit_splat_node(node) + bounds(node.operator_loc) + on_op("*") visit(node.expression) end @@ -3676,6 +3847,9 @@ def visit_number_node(node) location = node.location if slice[0] == "-" + bounds(location.copy(length: 1)) + on_op("-") + bounds(location.copy(start_offset: location.start_offset + 1)) value = yield slice[1..-1] diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index 1d20bceb40d6dc..6abe1bb2e5e50b 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -137,20 +137,22 @@ def test_lex_ignored_missing_heredoc_end end # Events that are currently not emitted - UNSUPPORTED_EVENTS = %i[comma ignored_nl label_end lbrace lbracket lparen nl op rbrace rbracket rparen semicolon sp words_sep ignored_sp] + UNSUPPORTED_EVENTS = %i[comma ignored_nl label_end lbrace lbracket lparen nl rbrace rbracket rparen semicolon sp words_sep ignored_sp] SUPPORTED_EVENTS = Translation::Ripper::EVENTS - UNSUPPORTED_EVENTS # Events that assert against their line/column - CHECK_LOCATION_EVENTS = %i[kw] + CHECK_LOCATION_EVENTS = %i[kw op] IGNORE_FOR_SORT_EVENTS = %i[ stmts_new stmts_add bodystmt void_stmt args_new args_add args_add_star args_add_block arg_paren method_add_arg - mlhs_new mlhs_add_star + mlhs_new mlhs_add mlhs_add_star mlhs_add_post + mrhs_new mrhs_add mrhs_add_star mrhs_new_from_args word_new words_new symbols_new qwords_new qsymbols_new xstring_new regexp_new words_add symbols_add qwords_add qsymbols_add regexp_end tstring_end heredoc_end call command fcall vcall field aref_field var_field var_ref block_var ident params string_content heredoc_dedent unary binary dyna_symbol + excessed_comma rest_param comment magic_comment embdoc embdoc_beg embdoc_end arg_ambiguous ] SORT_IGNORE = { @@ -189,7 +191,6 @@ def test_lex_ignored_missing_heredoc_end const_path_ref: ["unparser/corpus/literal/defs.txt"], do_block: ["whitequark/super_block.txt"], embexpr_end: ["seattlerb/str_interp_ternary_or_label.txt"], - rest_param: ["whitequark/send_lambda.txt"], top_const_field: [ "seattlerb/const_3_op_asgn_or.txt", "seattlerb/const_op_asgn_and1.txt", @@ -197,13 +198,9 @@ def test_lex_ignored_missing_heredoc_end "whitequark/const_op_asgn.txt", ], mlhs_paren: ["unparser/corpus/literal/for.txt"], - mlhs_add: [ - "whitequark/for_mlhs.txt", - ], kw: [ "defined.txt", "for.txt", - "seattlerb/block_kw__required.txt", "seattlerb/case_in_42.txt", "seattlerb/case_in_67.txt", "seattlerb/case_in_86_2.txt", @@ -220,6 +217,11 @@ def test_lex_ignored_missing_heredoc_end "whitequark/super_block.txt", "write_command_operator.txt", ], + op: [ + "ranges.txt", + "ternary_operator.txt", + "whitequark/args_args_assocs.txt", + ] } SORT_IGNORE.default = [] SORT_EVENTS = SUPPORTED_EVENTS - IGNORE_FOR_SORT_EVENTS @@ -235,6 +237,7 @@ def initialize(...) def sorted_events @events.select do |e,| next false if e == :kw && @events.any? { |e,| e == :if_mod || e == :while_mod || e == :until_mod || e == :rescue || e == :rescue_mod || e == :while || e == :ensure } + next false if e == :op && @events.any? { |e,| e == :const_path_field || e == :const_ref || e == :top_const_field || e == :top_const_ref } SORT_EVENTS.include?(e) && !SORT_IGNORE[e].include?(filename) end end