From f3c9c206d2c13aa022ca8a729652103a3ba9c146 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 13 Jul 2026 01:29:33 +0900 Subject: [PATCH 01/21] Pin simplecov version for net-imap ruby/net-imap#717 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index d9a6273a043d9b..6a9906c2441024 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -12,7 +12,7 @@ rake 13.4.2 https://github.com/ruby/rake test-unit 3.7.8 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.3 https://github.com/ruby/rss -net-imap 0.6.4.1 https://github.com/ruby/net-imap +net-imap 0.6.4.1 https://github.com/ruby/net-imap 65fdd6481e508e212b561769ffd00f9b4dd5218d 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 From 12ed8af9ca3cd9b4e9191c1c2891245f3060a1eb Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 13 Jul 2026 02:08:23 +0900 Subject: [PATCH 02/21] simplecov-{html,json} have been incorporated in SimpleCov 1.0.0 ruby/net-imap#718 --- common.mk | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common.mk b/common.mk index e7908cc0e54567..9e3697ece84822 100644 --- a/common.mk +++ b/common.mk @@ -1598,7 +1598,7 @@ no-install-for-test-bundled-gems: no-update-default-gemspecs yes-install-for-test-bundled-gems: yes-update-default-gemspecs $(XRUBY) -C "$(srcdir)" -r./tool/lib/gem_env.rb bin/gem \ install --no-document --conservative \ - "hoe" "json-schema:5.1.0" "test-unit-rr" "simplecov" "simplecov-html" "simplecov-json" "rspec" "zeitwerk" \ + "hoe" "json-schema:5.1.0" "test-unit-rr" "simplecov" "rspec" "zeitwerk" \ "sinatra" "rack" "tilt" "mustermann" "base64" "compact_index" "rack-test" "logger" "kpeg" "tracer" "minitest-mock" test-bundled-gems-fetch: yes-test-bundled-gems-fetch diff --git a/gems/bundled_gems b/gems/bundled_gems index 6a9906c2441024..3efa41dc454ea6 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -12,7 +12,7 @@ rake 13.4.2 https://github.com/ruby/rake test-unit 3.7.8 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.3 https://github.com/ruby/rss -net-imap 0.6.4.1 https://github.com/ruby/net-imap 65fdd6481e508e212b561769ffd00f9b4dd5218d +net-imap 0.6.4.1 https://github.com/ruby/net-imap 20d4f2c39dcc12307a6b30b497565770e89b66e3 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 From 4ac5902a231e620af78c4563965d517e3120d5ee Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 12 Jul 2026 13:36:19 -0500 Subject: [PATCH 03/21] [DOC] Doc for Pathname#setuid? --- pathname_builtin.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index f9b2b47ff3ff74..05be7b5fd451d5 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2129,7 +2129,28 @@ def world_readable?() File.world_readable?(@path) end # instead of the effective ids. def readable_real?() FileTest.readable_real?(@path) end - # See FileTest.setuid?. + # :markup: markdown + # + # call-seq: + # setuid? -> true or false + # + # Returns whether the [setuid bit](https://en.wikipedia.org/wiki/Setuid) is set + # in the permissions for the entry at the path in `self`: + # + # ```ruby + # # Create a file and get its permissions and setuid? setting. + # pn = Pathname('doc/t.tmp') + # pn.write('foo') + # mode = pn.stat.mode.to_s(8) # => "100664" + # pn.setuid? # => false + # # Set the bit. + # pn.chmod(0o4644) + # mode = pn.stat.mode.to_s(8) # => "104644" + # pn.setuid? # => true + # pn.delete # Clean up. + # ``` + # + # On Windows, the bit is never set; the method always returns `false`. def setuid?() FileTest.setuid?(@path) end # :markup: markdown From 7517b0855853f7792c2d546dd14bb22777b84f11 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 12 Jul 2026 13:38:43 -0500 Subject: [PATCH 04/21] [DOC] Doc for Pathname#rmtree (#17788) --- lib/pathname.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/pathname.rb b/lib/pathname.rb index 0e51e1fdf60383..7e1abd3e06aa9d 100644 --- a/lib/pathname.rb +++ b/lib/pathname.rb @@ -93,11 +93,25 @@ def find(ignore_error: true) # :yield: pathname class Pathname # * FileUtils * - # Recursively deletes a directory, including all directories beneath it. + + # :markup: markdown + # + # call-seq: + # rmtree -> 0 + # + # Deletes the entire filetree at the path in `self`; returns `0`: + # + # ```ruby + # dir_pn = Pathname('foo/bar/baz') # => # + # dir_pn.mkpath # Create 'baz' and intermediate directories. + # file_pn = dir_pn.join('t.tmp') # => # + # file_pn.write('foo') # Create file at nested directory 'baz'. + # Pathname('foo').rmtree # Delete the entire tree at directory 'foo'. + # Pathname('foo').exist? # => false + # ``` # - # Note that you need to require 'pathname' to use this method. + # Use method #rmdir to delete a single (empty) directory. # - # See FileUtils.rm_rf def rmtree(noop: nil, verbose: nil, secure: nil) # The name "rmtree" is borrowed from File::Path of Perl. # File::Path provides "mkpath" and "rmtree". From c02b5b1e6418ad0e4a697edfa58b52909cf4ea8c Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 11:55:36 -0500 Subject: [PATCH 05/21] [DOC] Doc for Pathname#split --- pathname_builtin.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 05be7b5fd451d5..5c2061fe00e473 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1831,8 +1831,20 @@ def extname() File.extname(@path) end # def expand_path(...) self.class.new(File.expand_path(@path, ...)) end - # See File.split. Returns the #dirname and the #basename in an - # Array. + # :markup: markdown + # + # call-seq: + # split -> array + # + # Returns a 2-element array containing #dirname and #basename: + # + # ```ruby + # Pathname('lib/pathname.rb').split # => [#, #] + # Pathname('README.md').split # => [#, #] + # Pathname('').split # => [#, #] + # Pathname('nosuch/foo/bar').split # => [#, #] + # ``` + # def split() array = File.split(@path) raise TypeError, 'wrong argument type nil (expected Array)' unless Array === array From da7c9491585fb2cb6cc7bdfb7215a8c2f5e9d9ec Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 10:24:41 -0500 Subject: [PATCH 06/21] [DOC] Doc for Pathname#socket? --- pathname_builtin.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 5c2061fe00e473..b1b62b55bdb3de 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2083,7 +2083,27 @@ def file?() FileTest.file?(@path) end # def pipe?() FileTest.pipe?(@path) end - # See FileTest.socket?. + # :markup: markdown + # + # call-seq: + # socket? -> true or false + # + # Returns whether the path in `self` points to a socket entry: + # + # ```ruby + # require 'socket' + # path = 'doc/socket' + # server = UNIXServer.new(path) # => # + # pn = Pathname(path) # => # + # pn.socket? # => true + # server.close + # pn.unlink + # Pathname('README.md').socket? # => false + # Pathname('nosuch').socket? # => false + # ``` + # + # Returns `false` on Windows. + # def socket?() FileTest.socket?(@path) end # :markup: markdown From 48ea06e6d595d8a5f16026353b4bc1603a9562da Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 12:00:33 -0500 Subject: [PATCH 07/21] Remove trailing space --- pathname_builtin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index b1b62b55bdb3de..64aa0456b445b5 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2101,7 +2101,7 @@ def pipe?() FileTest.pipe?(@path) end # Pathname('README.md').socket? # => false # Pathname('nosuch').socket? # => false # ``` - # + # # Returns `false` on Windows. # def socket?() FileTest.socket?(@path) end From 48b46e392add782d373e640ccd39cc1879e99b75 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 12:28:33 -0500 Subject: [PATCH 08/21] [DOC] Doc for Pathname#stat --- pathname_builtin.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 64aa0456b445b5..46f774df531a6c 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1606,7 +1606,20 @@ def readlink() self.class.new(File.readlink(@path)) end # Raises SystemCallError if the entry cannot be renamed. def rename(to) File.rename(@path, to) end - # See File.stat. Returns a File::Stat object. + # :markup: markdown + # + # call-seq: + # stat -> File::Stat + # + # Returns a File::Stat object for the entry at the path in `self`: + # + # ```ruby + # Pathname('README.md').stat.inspect + # => "#" + # Pathname('doc').stat.inspect + # => "#" + # ``` + # def stat() File.stat(@path) end # From a27896dc432243b1ec6f224142f2d1b1294aa6ed Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 12:49:04 -0500 Subject: [PATCH 09/21] [DOC] Doc for Pathname#sticky? --- pathname_builtin.rb | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 46f774df531a6c..dbdb911dbfb5d5 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2266,7 +2266,26 @@ def size() FileTest.size(@path) end # ``` # def size?() FileTest.size?(@path) end - # See FileTest.sticky?. + # :markup: markdown + # + # call-seq: + # sticky? -> true or false + # + # Returns whether the [sticky bit](https://en.wikipedia.org/wiki/Sticky_bit) is set + # for the entry at the path in `self`: + # + # ```ruby + # pn = Pathname('t.tmp') + # pn.write('foo') + # pn.stat.mode.to_s(8) # => "100664" + # pn.sticky? # => false + # pn.chmod(0o1644) + # pn.stat.mode.to_s(8) # => "101644" + # pn.sticky? # => true + # pn.delete + # ``` + # + # Returns `false` on Windows. def sticky?() FileTest.sticky?(@path) end # :markup: markdown From df78d758f2fef32a79d7593cbf0b1b16c19e2fa8 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 13:24:17 -0500 Subject: [PATCH 10/21] [DOC] Doc for Pathname#sub --- pathname.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/pathname.c b/pathname.c index 13ff83ff764d7e..b89ffbdf83eca3 100644 --- a/pathname.c +++ b/pathname.c @@ -100,11 +100,54 @@ path_cmp(VALUE self, VALUE other) } /* - * Return a pathname which is substituted by String#sub. + * :markup: markdown + * + * call-seq: + * sub(pattern, replacement) -> new_pathname + * sub(pattern) {|match| ... } -> new_pathname + * + * Returns a new pathname whose path is the path in `self`, + * after the specified substitutions. + * + * Argument `pattern` may be a string or a Regexp; + * argument `replacement` may be a string or a hash. + * + * Varying types for the argument values makes this method very versatile. + * + * Below are some simple examples; + * for many more related examples (using strings, not pathnames), + * see [Substitution Methods](rdoc-ref:String@Substitution+Methods). + * + * With arguments `pattern` and string `replacement` given, + * replaces the first matching substring with the given replacement string: + * + * ```ruby + * pn = Pathname('abracadabra.txt') # => # + * pn.sub('bra', 'xyzzy') # => # + * pn.sub(/bra/, 'xyzzy') # => # + * pn.sub('nope', 'xyzzy') # => # + * ``` + * + * With arguments `pattern` and hash `replacement` given, + * replaces the first matching substring with a value from the given replacement hash, + * or removes it: + * + * ```ruby + * h = {'a' => 'A', 'b' => 'B', 'c' => 'C'} + * pn.sub('b', h) # => # + * pn.sub(/b/, h) # => # + * pn.sub(/d/, h) # => # # 'd' removed. + * ``` + * + * With argument `pattern` and a block given, + * calls the block with the first matching substring; + * replaces that substring with the block’s return value: + * + * ```ruby + * pn.sub('b') {|match| match.upcase } # => # + * pn.sub(/X/) {|match| match.upcase } # => # + * ``` * - * path1 = Pathname.new('/usr/bin/perl') - * path1.sub('perl', 'ruby') - * #=> # */ static VALUE path_sub(int argc, VALUE *argv, VALUE self) From 9d2812723d801c436f88357de46b2245c7836358 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 14:00:40 -0500 Subject: [PATCH 11/21] [DOC] Doc for Pathname#sysopen --- pathname_builtin.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index dbdb911dbfb5d5..7bea953992d8b1 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1133,7 +1133,30 @@ def binread(...) File.binread(@path, ...) end # def readlines(...) File.readlines(@path, ...) end - # See File.sysopen. + # :markup: markdown + # + # call-seq: + # sysopen(mode = 'r', permissions = 0666) -> integer + # + # Opens the file at the path in `self` with the given mode and permissions; + # returns the integer file descriptor. + # + # If the file is to be readable, it must exist; + # if the file is to be writable and does not exist, + # it is created with the given permissions: + # + # ```ruby + # pn = Pathname('doc/t.tmp') + # pn.write('foo') + # fd = pn.sysopen # => 5 + # IO.new(fd).close + # fd = pn.sysopen('w') # => 5 + # IO.new(fd).close + # fd = pn.sysopen('r', 0o644) # => 5 + # IO.new(fd).close + # pn.delete + # ``` + # def sysopen(...) File.sysopen(@path, ...) end # call-seq: From 2e7d6d7135060a3adc95bcd56c52cada8dd84204 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 14:45:49 -0500 Subject: [PATCH 12/21] [DOC] Doc for Pathname#truncate --- pathname_builtin.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 7bea953992d8b1..08aa6180b80087 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1693,7 +1693,30 @@ def lstat() File.lstat(@path) end # See also: #read, #readlink, #symlink?. def make_symlink(old) File.symlink(old, @path) end - # See File.truncate. Truncate the file to +length+ bytes. + # :markup: markdown + # + # call-seq: + # truncate(size) -> 0 + # + # Adjusts the size of file at the path in `self` to the given `size`; + # returns `0`: + # + # ```ruby + # pn.write('0123456789') + # pn.size # => 10 + # pn.truncate(5) + # pn.size # => 5 + # pn.read # => "01234" + # ``` + # + # Pads on the right with null characters if necessary: + # + # ```ruby + # pn.truncate(10) + # pn.size # => 10 + # pn.read # => "01234\u0000\u0000\u0000\u0000\u0000" + # ``` + # def truncate(length) File.truncate(@path, length) end # See File.utime. Update the access and modification times. From 0c83e3e515c1756a957cb2dd2b4aaefe07b02fd2 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 17:00:37 -0500 Subject: [PATCH 13/21] [DOC] Doc for Pathname#world_writable? --- pathname_builtin.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 08aa6180b80087..457501bcdfc1c7 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2358,7 +2358,28 @@ def symlink?() FileTest.symlink?(@path) end # See FileTest.writable?. def writable?() FileTest.writable?(@path) end - # See FileTest.world_writable?. + # :markup: markdown + # + # call-seq: + # world_writable? -> integer or nil + # + # If the entry at the path in `self` is writable by others, + # returns the integer permissions for the entry: + # + # ```ruby + # Pathname('/tmp').world_writable?.to_s(8) # => "777" + # ``` + # + # Otherwise, returns `nil`: + # + # ```ruby + # pn = Pathname('doc/t.tmp') + # pn.write('foo') + # pn.chmod(0o0) + # pn.world_writable? # => nil + # pn.delete + # ``` + # def world_writable?() File.world_writable?(@path) end # See FileTest.writable_real?. From b2f95348064f13ed960b46dcbf3f1a6309c3820e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 16:45:19 -0500 Subject: [PATCH 14/21] [DOC] Doc for Pathname#world_readable? --- pathname_builtin.rb | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 457501bcdfc1c7..a9b6fea04d9f70 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2207,7 +2207,28 @@ def owned?() FileTest.owned?(@path) end # def readable?() FileTest.readable?(@path) end - # See FileTest.world_readable?. + # :markup: markdown + # + # call-seq: + # world_readable? -> integer or nil + # + # If the entry at the path in `self` is readable by others, + # returns the integer permissions for the entry: + # + # ```ruby + # Pathname('/etc/passwd').world_readable?.to_s(8) # => "644" + # ``` + # + # Otherwise, returns `nil`: + # + # ```ruby + # pn = Pathname('doc/t.tmp') + # pn.write('foo') + # pn.chmod(0o0) + # pn.world_readable? # => nil + # pn.delete + # ``` + # def world_readable?() File.world_readable?(@path) end # :markup: markdown From 08767e48f3eb35ffbebeeb14605812e7ea3a7ee3 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 16:26:57 -0500 Subject: [PATCH 15/21] [DOC] Doc for Pathname#utime --- pathname_builtin.rb | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index a9b6fea04d9f70..3371eef3f8dfad 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1719,7 +1719,47 @@ def make_symlink(old) File.symlink(old, @path) end # def truncate(length) File.truncate(@path, length) end - # See File.utime. Update the access and modification times. + # :markup: markdown + # + # call-seq: + # utime(atime, mtime) -> 1 + # + # For the entry at the path in `self`, + # updates its access time to the given `atime` + # and its modification time to the given `mtime`; + # each given time may be a Time object, an integer representing a time, + # or `nil` (meaning Time.now): + # + # ```ruby + # pn = Pathname('doc/t.tmp') + # pn.write('foo') + # pn.stat.atime # => 1969-12-31 18:00:00 -0600 + # pn.stat.mtime # => 2026-07-11 16:12:15.832556524 -0500 + # pn.utime(0, 0) + # pn.stat.atime # => 1969-12-31 18:00:00 -0600 + # pn.stat.mtime # => 1969-12-31 18:00:00 -0600 + # pn.utime(nil, nil) + # pn.stat.atime # => 2026-07-11 16:13:06.982646673 -0500 + # pn.stat.mtime # => 2026-07-11 16:13:04.983530291 -0500 + # time = Time.now # => 2026-07-11 16:13:40.190110708 -0500 + # pn.utime(time, time) + # pn.stat.atime # => 2026-07-11 16:13:51.99317823 -0500 + # pn.stat.mtime # => 2026-07-11 16:13:40.190110708 -0500 + # ``` + # + # Follows symbolic links: + # + # ```ruby + # link_pn = Pathname('link') + # link_pn.make_symlink(pn) + # link_pn.stat.atime # => 2026-07-11 16:13:51.99317823 -0500 + # link_pn.stat.mtime # => 2026-07-11 16:13:40.190110708 -0500 + # link_pn.utime(0, 0) + # pn.stat.atime # => 1969-12-31 18:00:00 -0600 + # pn.stat.mtime # => 1969-12-31 18:00:00 -0600 + # pn.delete + # link_pn.delete + # ``` def utime(atime, mtime) File.utime(atime, mtime, @path) end # :markup: markdown From aba94f69ebc6ac5786a94566466434695e4065be Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 14:54:41 -0500 Subject: [PATCH 16/21] [DOC] Tweaks for Pathname#unlink --- pathname_builtin.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 3371eef3f8dfad..88bc9bba9ccc13 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2624,18 +2624,14 @@ class Pathname # * mixed * # :markup: markdown # # call-seq: - # unlink -> 1 or 0 + # unlink -> 0 or 1 # - # Removes the file or directory represented by `self`, using: - # - # - File.unlink, if `self` represents a file; returns `1`. - # - Dir.unlink, if `self` represents a directory; returns `0`. - # - # Examples: + # Removes the entry represented by `self`; + # returns `0` if a directory, `1` if a file: # # ```ruby - # Pathname(Tempfile.create).unlink # => 1 # Pathname(Pathname.mktmpdir).unlink # => 0 + # Pathname(Tempfile.create).unlink # => 1 # ``` # def unlink() From 188343f1c9d3aacb2291585f91209b2847c739f6 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sat, 11 Jul 2026 13:38:01 -0500 Subject: [PATCH 17/21] [DOC] Doc for Pathname#sub_ext --- pathname.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pathname.c b/pathname.c index b89ffbdf83eca3..b46aaa2deba25d 100644 --- a/pathname.c +++ b/pathname.c @@ -277,12 +277,20 @@ has_separator_p(VALUE self, VALUE path) } /* - * Return a pathname with +repl+ added as a suffix to the basename. + * :markup: markdown + * + * call-seq: + * sub_ext -> new_pathname * - * If self has no extension part, +repl+ is appended. + * Returns a new pathname whose path is the path in `self`, + * after specified changes: + * + * ```ruby + * Pathname('t.tmp').sub_ext('.txt') # => # # Extension replaced. + * Pathname('temp').sub_ext('.txt') # => # # Extension added. + * Pathname('t.tmp').sub_ext('') # => # # Extension removed. + * ``` * - * Pathname.new('/usr/bin/shutdown').sub_ext('.rb') - * #=> # */ static VALUE path_sub_ext(VALUE self, VALUE repl) From 7df1fefe218d19d2919cf885f420ed1e52136a4f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 12 Jul 2026 14:05:52 -0500 Subject: [PATCH 18/21] [DOC] Respond to review --- pathname.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pathname.c b/pathname.c index b46aaa2deba25d..821e93dd5466d9 100644 --- a/pathname.c +++ b/pathname.c @@ -280,7 +280,7 @@ has_separator_p(VALUE self, VALUE path) * :markup: markdown * * call-seq: - * sub_ext -> new_pathname + * sub_ext(replacement) -> new_pathname * * Returns a new pathname whose path is the path in `self`, * after specified changes: From eec3aba7a3abbc5bbdafdca81099dceb9c03bf4a Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sun, 12 Jul 2026 14:20:26 -0500 Subject: [PATCH 19/21] [DOC] Doc for Pathname#to_s --- pathname_builtin.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 88bc9bba9ccc13..dc9f81a3b369d8 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -281,12 +281,21 @@ def hash # :nodoc: @path.hash end - # Return the path as a String. + # :markup: markdown + # + # call-seq: + # to_s -> string + # + # Returns a copy of the string path in `self`: + # + # ```ruby + # Pathname('nosuch/foo/bar').to_s # => "nosuch/foo/bar" + # ``` + # def to_s @path.dup end - # to_path is implemented so Pathname objects are usable with File.open, etc. alias to_path to_s def inspect # :nodoc: From 0d7e50a1746bfa3db2a50478d3680cd7c36ea383 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 12 Jul 2026 21:37:20 +0900 Subject: [PATCH 20/21] [Bug #22191] Report `EXIT` handler exceptions after `END` blocks Keep exceptions raised by `EXIT` traps until `END` handlers finish. That leaves them available for reporting during cleanup. Print `END` handler exceptions without replaying causes already reported from earlier exit handlers. This keeps the previous handler exception visible while avoiding duplicate diagnostics. --- eval.c | 13 +++++++++- eval_error.c | 49 ++++++++++++++++++++++++++++++------- eval_jump.c | 8 ++++-- test/ruby/test_exception.rb | 37 ++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 12 deletions(-) diff --git a/eval.c b/eval.c index 0f56d8eaec0c39..0d2d2c4089ec09 100644 --- a/eval.c +++ b/eval.c @@ -147,15 +147,26 @@ rb_ec_fiber_scheduler_finalize(rb_execution_context_t *ec) static void rb_ec_teardown(rb_execution_context_t *ec) { + enum ruby_tag_type state = TAG_NONE; + volatile VALUE trap_errinfo = Qnil; + // If the user code defined a scheduler for the top level thread, run it: rb_ec_fiber_scheduler_finalize(ec); EC_PUSH_TAG(ec); - if (EC_EXEC_TAG() == TAG_NONE) { + if ((state = EC_EXEC_TAG()) == TAG_NONE) { rb_vm_trap_exit(rb_ec_vm_ptr(ec)); } + else { + trap_errinfo = ec->errinfo; + ec->errinfo = Qnil; + } EC_POP_TAG(); rb_ec_exec_end_proc(ec); + if (!NIL_P(trap_errinfo)) { + error_handle(ec, trap_errinfo, state); + ec->errinfo = trap_errinfo; + } rb_ec_clear_all_trace_func(ec); } diff --git a/eval_error.c b/eval_error.c index f3d05a1043fea7..f1cf887b1ddef9 100644 --- a/eval_error.c +++ b/eval_error.c @@ -276,6 +276,16 @@ shown_cause_p(VALUE cause, VALUE *shown_causes) return FALSE; } +static void +add_shown_cause(VALUE cause, VALUE *shown_causes) +{ + if (!NIL_OR_UNDEF_P(cause) && + !THROW_DATA_P(cause) && + rb_obj_is_kind_of(cause, rb_eException)) { + shown_cause_p(cause, shown_causes); + } +} + static void show_cause(VALUE errinfo, VALUE str, VALUE opt, VALUE highlight, VALUE reverse, long backtrace_limit, VALUE *shown_causes) { @@ -309,15 +319,18 @@ rb_exc_check_circular_cause(VALUE exc) } while (!NIL_P(cause = rb_attr_get(cause, id_cause))); } -void -rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse) +static void +rb_error_write0(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse, VALUE *shown_causes) { volatile VALUE eclass; - VALUE shown_causes = 0; + VALUE local_shown_causes = 0; long backtrace_limit = rb_backtrace_length_limit; if (NIL_P(errinfo)) return; + if (!shown_causes) { + shown_causes = &local_shown_causes; + } if (UNDEF_P(errat)) { errat = Qnil; @@ -340,19 +353,25 @@ rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VA len = p - (msg = buff); } write_warn2(str, msg, len); - show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, &shown_causes); + show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, shown_causes); print_backtrace(eclass, errat, str, TRUE, backtrace_limit); print_errinfo(eclass, errat, emesg, str, RTEST(highlight)); } else { print_errinfo(eclass, errat, emesg, str, RTEST(highlight)); print_backtrace(eclass, errat, str, FALSE, backtrace_limit); - show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, &shown_causes); + show_cause(errinfo, str, opt, highlight, reverse, backtrace_limit, shown_causes); } } +void +rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VALUE highlight, VALUE reverse) +{ + rb_error_write0(errinfo, emesg, errat, str, opt, highlight, reverse, NULL); +} + static void -rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo, const VALUE str, VALUE emesg0) +rb_ec_error_print_detailed0(rb_execution_context_t *const ec, const VALUE errinfo, const VALUE str, VALUE emesg0, VALUE *shown_causes) { volatile uint8_t raised_flag = ec->raised_flag; volatile VALUE errat = Qundef; @@ -378,7 +397,7 @@ rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo if (!written) { written = true; - rb_error_write(errinfo, emesg, errat, str, opt, highlight, Qfalse); + rb_error_write0(errinfo, emesg, errat, str, opt, highlight, Qfalse, shown_causes); } EC_POP_TAG(); @@ -386,6 +405,12 @@ rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo rb_ec_raised_set(ec, raised_flag); } +static void +rb_ec_error_print_detailed(rb_execution_context_t *const ec, const VALUE errinfo, const VALUE str, VALUE emesg0) +{ + rb_ec_error_print_detailed0(ec, errinfo, str, emesg0, NULL); +} + void rb_ec_error_print(rb_execution_context_t *volatile ec, volatile VALUE errinfo) { @@ -504,7 +529,7 @@ exiting_split(VALUE errinfo, volatile int *exitcode, volatile int *sigstatus) rb_bug("Unknown longjmp status %d", status) static int -error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) +error_handle_with_shown_causes(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex, VALUE *shown_causes) { int status = EXIT_FAILURE; @@ -546,7 +571,7 @@ error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) } /* fallthrough */ case TAG_FATAL: - rb_ec_error_print(ec, errinfo); + rb_ec_error_print_detailed0(ec, errinfo, Qnil, Qundef, shown_causes); break; default: unknown_longjmp_status(ex); @@ -555,3 +580,9 @@ error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) rb_ec_reset_raised(ec); return status; } + +static int +error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) +{ + return error_handle_with_shown_causes(ec, errinfo, ex, NULL); +} diff --git a/eval_jump.c b/eval_jump.c index 7737148644ce37..296ca8208c5d1e 100644 --- a/eval_jump.c +++ b/eval_jump.c @@ -114,6 +114,8 @@ rb_ec_exec_end_proc(rb_execution_context_t * ec) { enum ruby_tag_type state; volatile VALUE errinfo = ec->errinfo; + /* Share only among END/at_exit handlers to avoid repeated causes. */ + VALUE shown_causes = 0; volatile bool finished = false; while (!finished) { @@ -125,8 +127,10 @@ rb_ec_exec_end_proc(rb_execution_context_t * ec) } EC_POP_TAG(); if (state != TAG_NONE) { - error_handle(ec, ec->errinfo, state); - if (!NIL_P(ec->errinfo)) errinfo = ec->errinfo; + VALUE err = ec->errinfo; + add_shown_cause(errinfo, &shown_causes); + error_handle_with_shown_causes(ec, err, state, &shown_causes); + if (!NIL_P(err)) errinfo = err; } } diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 4365150a13fba3..0dc91f9abc4f43 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -826,6 +826,43 @@ def test_cause_at_end end; end + def test_multiple_error_handle + errs = [ + /.*END3 \(RuntimeError\).*\n/, + /.*END2 \(RuntimeError\).*\n/, + /.*END1 \(RuntimeError\).*\n/, + /.*EXIT \(RuntimeError\).*\n/, + ] + assert_in_out_err([], <<-'end;', [], errs, success: false) + Signal.trap(:EXIT) {raise "EXIT"} + END{raise "END1"}; + END{raise "END2"}; + END{raise "END3"}; + end; + end + + def test_cause_in_exit_handler + errs = [ + /.*outer \(RuntimeError\).*\n/, + /.*inner \(RuntimeError\).*\n/, + ] + assert_in_out_err([], <<-'end;', [], errs, success: false) + END{begin; raise "inner"; rescue; raise "outer"; end} + end; + assert_in_out_err([], <<-'end;', [], errs, success: false) + Signal.trap(:EXIT) {begin; raise "inner"; rescue; raise "outer"; end} + end; + errs = [ + /.*previous \(RuntimeError\).*\n/, + /.*outer \(RuntimeError\).*\n/, + /.*middle \(RuntimeError\).*\n/, + ] + assert_in_out_err([], <<-'end;', [], errs, success: false) + END{begin; raise "middle"; rescue => e; raise "outer", cause: e; end} + END{raise "previous"} + end; + end + def test_raise_with_cause msg = "[Feature #8257]" cause = ArgumentError.new("foobar") From 32a0375677e2ed22ee27b09c8f6b0454e506f6b3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sun, 12 Jul 2026 12:11:14 -0700 Subject: [PATCH 21/21] Remove dead test exclude that doesn't exist anymore TestGem#test_looks_for_gemdeps_files_automatically_from_binstubs doesn't exist anymore. --- test/.excludes/TestGem.rb | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 test/.excludes/TestGem.rb diff --git a/test/.excludes/TestGem.rb b/test/.excludes/TestGem.rb deleted file mode 100644 index 042af26eff7f30..00000000000000 --- a/test/.excludes/TestGem.rb +++ /dev/null @@ -1,4 +0,0 @@ -if RbConfig::CONFIG["LIBRUBY_RELATIVE"] == "yes" - exclude(/test_looks_for_gemdeps_files_automatically_from_binstubs/, - "can't test before installation") -end