From 38310fe79dae8373ceaab1f2c2b54c870e1f966c Mon Sep 17 00:00:00 2001 From: git Date: Wed, 17 Dec 2025 06:48:34 +0000 Subject: [PATCH 01/10] Update default gems list at 26447b3597ab95af7cc220c641a1bd [ci skip] --- NEWS.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index 479a49145ccfe0..e55df0f097ddb8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -279,16 +279,17 @@ The following default gems are updated. * io-wait 0.4.0 * ipaddr 1.2.8 * json 2.18.0 -* net-http 0.8.0 +* net-http 0.9.1 * openssl 4.0.0 * optparse 0.8.1 * pp 0.6.3 * prism 1.6.0 -* psych 5.3.0 +* psych 5.3.1 * resolv 0.7.0 -* stringio 3.1.9.dev -* strscan 3.1.6.dev -* timeout 0.5.0 +* stringio 3.2.0 +* strscan 3.1.6 +* time 0.4.2 +* timeout 0.6.0 * uri 1.1.1 * weakref 0.1.4 * zlib 3.2.2 From 1506c489eef67f268a54484662011a60897bdd48 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 17 Dec 2025 13:04:06 +0900 Subject: [PATCH 02/10] Update NEWS.md for improvements of error backtrace --- NEWS.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/NEWS.md b/NEWS.md index e55df0f097ddb8..ae87adecd9e0a9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -98,6 +98,24 @@ Note: We're only listing outstanding class updates. `Binding#implicit_parameter_defined?` have been added to access numbered parameters and "it" parameter. [[Bug #21049]] +* ErrorHighlight + + * When an ArgumentError is raised, it now displays code snippets for + both the method call (caller) and the method definition (callee). + [[Feature #21543]] + + ``` + test.rb:1:in 'Object#add': wrong number of arguments (given 1, expected 2) (ArgumentError) + + caller: test.rb:3 + | add(1) + ^^^ + callee: test.rb:1 + | def add(x, y) = x + y + ^^^ + from test.rb:3:in '
' + ``` + * File * `File::Stat#birthtime` is now available on Linux via the statx @@ -347,6 +365,31 @@ The following bundled gems are updated. and was already deprecated,. [[Feature #20971]] +* A backtrace for `ArgumentError` of "wrong number of arguments" now + include the receiver's class or module name (e.g., in `Foo#bar` + instead of in `bar`). [[Bug #21698]] + +* Backtraces no longer display `internal` frames. + These methods now appear as if it is in the Ruby source file, + consistent with other C-implemented methods. [[Bug #20968]] + + Before: + ``` + ruby -e '[1].fetch_values(42)' + :211:in 'Array#fetch': index 42 outside of array bounds: -1...1 (IndexError) + from :211:in 'block in Array#fetch_values' + from :211:in 'Array#map!' + from :211:in 'Array#fetch_values' + from -e:1:in '
' + ``` + + After: + ``` + $ ruby -e '[1].fetch_values(42)' + -e:1:in 'Array#fetch_values': index 42 outside of array bounds: -1...1 (IndexError) + from -e:1:in '
' + ``` + ## Stdlib compatibility issues * CGI library is removed from the default gems. Now we only provide `cgi/escape` for @@ -446,6 +489,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #20750]: https://bugs.ruby-lang.org/issues/20750 [Feature #20884]: https://bugs.ruby-lang.org/issues/20884 [Feature #20925]: https://bugs.ruby-lang.org/issues/20925 +[Bug #20968]: https://bugs.ruby-lang.org/issues/20968 [Feature #20971]: https://bugs.ruby-lang.org/issues/20971 [Bug #20974]: https://bugs.ruby-lang.org/issues/20974 [Feature #21047]: https://bugs.ruby-lang.org/issues/21047 @@ -470,8 +514,10 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21459]: https://bugs.ruby-lang.org/issues/21459 [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 +[Feature #21543]: https://bugs.ruby-lang.org/issues/21543 [Feature #21550]: https://bugs.ruby-lang.org/issues/21550 [Feature #21557]: https://bugs.ruby-lang.org/issues/21557 [Bug #21654]: https://bugs.ruby-lang.org/issues/21654 [Feature #21678]: https://bugs.ruby-lang.org/issues/21678 +[Bug #21698]: https://bugs.ruby-lang.org/issues/21698 [Feature #21701]: https://bugs.ruby-lang.org/issues/21701 From 54d3945ee53fa29186c6aa44f673a3a5ec3b38d9 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:16:32 +0900 Subject: [PATCH 03/10] Add host information to timeout error messages in `TCPSocket.new` and `Socket.tcp` (#15582) This change adds host information to the error messages shown when a timeout occurs while passing timeout options to `TCPSocket.new` or `Socket.tcp`, for improved usability. (When the `fast_fallback option` is enabled, there may be multiple possible destinations, so the host name is shown instead of an IP address.) As part of this change, the error messages in `Addrinfo.getaddrinfo` and `Addrinfo#connect_internal`, both of which are used by `Socket.tcp`, have also been improved in the same way. --- ext/socket/init.c | 9 ++++++--- ext/socket/ipsocket.c | 28 +++++++++++++++++++++++----- ext/socket/lib/socket.rb | 8 +++++--- ext/socket/raddrinfo.c | 10 +++++++++- ext/socket/rubysocket.h | 2 +- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/ext/socket/init.c b/ext/socket/init.c index 8e405609ba0aa4..b761d601c39d57 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -473,7 +473,7 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(VALUE self, VALUE timeout) +wait_connectable(VALUE self, VALUE timeout, const struct sockaddr *sockaddr, int len) { int sockerr; socklen_t sockerrlen; @@ -514,7 +514,10 @@ wait_connectable(VALUE self, VALUE timeout) VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); if (result == Qfalse) { - rb_raise(rb_eIOTimeoutError, "Connect timed out!"); + VALUE rai = rsock_addrinfo_new((struct sockaddr *)sockaddr, len, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + VALUE message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); } int revents = RB_NUM2INT(result); @@ -603,7 +606,7 @@ rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, V #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(self, timeout); + return wait_connectable(self, timeout, sockaddr, len); } } return status; diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 20d9101c11510d..88225f76e5c882 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -27,9 +27,19 @@ struct inetsock_arg }; void -rsock_raise_user_specified_timeout(void) +rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port) { - rb_raise(rb_eIOTimeoutError, "user specified timeout"); + VALUE message; + + if (ai && ai->ai_addr) { + VALUE rai = rsock_addrinfo_new((struct sockaddr *)ai->ai_addr, (socklen_t)ai->ai_addrlen, PF_UNSPEC, 0, 0, Qnil, Qnil); + VALUE addr_str = rsock_addrinfo_inspect_sockaddr(rai); + message = rb_sprintf("user specified timeout for %" PRIsVALUE, addr_str); + } else { + message = rb_sprintf("user specified timeout for %" PRIsVALUE " port %" PRIsVALUE, host, port); + } + + rb_raise(rb_eIOTimeoutError, "%" PRIsVALUE, message); } static VALUE @@ -149,7 +159,9 @@ init_inetsock_internal(VALUE v) } else { VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); timeout = rb_funcall(open_timeout, '-', 1, elapsed); - if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) rsock_raise_user_specified_timeout(); + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(res, arg->remote.host, arg->remote.serv); + } } if (status >= 0) { @@ -844,6 +856,10 @@ init_fast_fallback_inetsock_internal(VALUE v) if (!NIL_P(open_timeout)) { VALUE elapsed = rb_funcall(current_clocktime(), '-', 1, starts_at); timeout = rb_funcall(open_timeout, '-', 1, elapsed); + + if (rb_funcall(timeout, '<', 1, INT2FIX(0)) == Qtrue) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } } if (NIL_P(timeout)) { if (!NIL_P(connect_timeout)) { @@ -1180,7 +1196,9 @@ init_fast_fallback_inetsock_internal(VALUE v) } } - if (is_timeout_tv(user_specified_open_timeout_at, now)) rsock_raise_user_specified_timeout(); + if (is_timeout_tv(user_specified_open_timeout_at, now)) { + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); + } if (!any_addrinfos(&resolution_store)) { if (!in_progress_fds(arg->connection_attempt_fds_size) && @@ -1203,7 +1221,7 @@ init_fast_fallback_inetsock_internal(VALUE v) resolution_store.is_all_finished) && (is_timeout_tv(user_specified_connect_timeout_at, now) || !in_progress_fds(arg->connection_attempt_fds_size))) { - rsock_raise_user_specified_timeout(); + rsock_raise_user_specified_timeout(NULL, arg->remote.host, arg->remote.serv); } } } diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index 6d04ca2483a385..e74eaec43a3318 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -62,7 +62,7 @@ def connect_internal(local_addrinfo, timeout=nil) # :yields: socket break when :wait_writable sock.wait_writable(timeout) or - raise Errno::ETIMEDOUT, 'user specified timeout' + raise Errno::ETIMEDOUT, "user specified timeout for #{self.ip_address}:#{self.ip_port}" end while true else sock.connect(self) @@ -905,7 +905,9 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, end end - raise(IO::TimeoutError, 'user specified timeout') if expired?(now, user_specified_open_timeout_at) + if expired?(now, user_specified_open_timeout_at) + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") + end if resolution_store.empty_addrinfos? if connecting_sockets.empty? && resolution_store.resolved_all_families? @@ -918,7 +920,7 @@ def self.tcp_with_fast_fallback(host, port, local_host = nil, local_port = nil, if (expired?(now, user_specified_resolv_timeout_at) || resolution_store.resolved_all_families?) && (expired?(now, user_specified_connect_timeout_at) || connecting_sockets.empty?) - raise IO::TimeoutError, 'user specified timeout' + raise(IO::TimeoutError, "user specified timeout for #{host}:#{port}") end end end diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index e4e40e591dc880..3dcbe7717a0ca2 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -597,7 +597,15 @@ rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hint if (need_free) free_getaddrinfo_arg(arg); - if (timedout) rsock_raise_user_specified_timeout(); + if (timedout) { + if (arg->ai) { + rsock_raise_user_specified_timeout(arg->ai, Qnil, Qnil); + } else { + VALUE host = rb_str_new_cstr(hostp); + VALUE port = rb_str_new_cstr(portp); + rsock_raise_user_specified_timeout(NULL, host, port); + } + } // If the current thread is interrupted by asynchronous exception, the following raises the exception. // But if the current thread is interrupted by timer thread, the following returns; we need to manually retry. diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 1adccee4276da0..638b7ede6ec72e 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -454,7 +454,7 @@ void free_fast_fallback_getaddrinfo_shared(struct fast_fallback_getaddrinfo_shar #endif unsigned int rsock_value_timeout_to_msec(VALUE); -NORETURN(void rsock_raise_user_specified_timeout(void)); +NORETURN(void rsock_raise_user_specified_timeout(struct addrinfo *ai, VALUE host, VALUE port)); void rsock_init_basicsocket(void); void rsock_init_ipsocket(void); From 79f36c544a0431d9b76c3c11a5f622383eaca7bd Mon Sep 17 00:00:00 2001 From: Akinori Musha Date: Wed, 17 Dec 2025 17:30:24 +0900 Subject: [PATCH 04/10] Revert the override of Enumerator#to_set that performed size checks [Bug #21780] --- NEWS.md | 4 ++-- enumerator.c | 19 ------------------- test/ruby/test_enumerator.rb | 9 --------- test/ruby/test_set.rb | 6 ++++++ 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/NEWS.md b/NEWS.md index ae87adecd9e0a9..869f9b2f855d95 100644 --- a/NEWS.md +++ b/NEWS.md @@ -188,8 +188,8 @@ Note: We're only listing outstanding class updates. * Range - * `Range#to_set` and `Enumerator#to_set` now perform size checks to prevent - issues with endless ranges. [[Bug #21654]] + * `Range#to_set` now performs size checks to prevent issues with + endless ranges. [[Bug #21654]] * `Range#overlap?` now correctly handles infinite (unbounded) ranges. [[Bug #21185]] diff --git a/enumerator.c b/enumerator.c index c2b2bfa9a032dd..93671dfe712073 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3366,24 +3366,6 @@ enumerator_plus(VALUE obj, VALUE eobj) return new_enum_chain(rb_ary_new_from_args(2, obj, eobj)); } -/* - * call-seq: - * e.to_set -> set - * - * Returns a set generated from this enumerator. - * - * e = Enumerator.new { |y| y << 1 << 1 << 2 << 3 << 5 } - * e.to_set #=> # - */ -static VALUE enumerator_to_set(int argc, VALUE *argv, VALUE obj) -{ - VALUE size = rb_funcall(obj, id_size, 0); - if (RB_TYPE_P(size, T_FLOAT) && RFLOAT_VALUE(size) == INFINITY) { - rb_raise(rb_eArgError, "cannot convert an infinite enumerator to a set"); - } - return rb_call_super(argc, argv); -} - /* * Document-class: Enumerator::Product * @@ -4540,7 +4522,6 @@ InitVM_Enumerator(void) rb_define_method(rb_cEnumerator, "rewind", enumerator_rewind, 0); rb_define_method(rb_cEnumerator, "inspect", enumerator_inspect, 0); rb_define_method(rb_cEnumerator, "size", enumerator_size, 0); - rb_define_method(rb_cEnumerator, "to_set", enumerator_to_set, -1); rb_define_method(rb_cEnumerator, "+", enumerator_plus, 1); rb_define_method(rb_mEnumerable, "chain", enum_chain, -1); diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 5fabea645d2b8d..177d7c04fab4cf 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -1070,13 +1070,4 @@ def initialize(val) enum = ary.each assert_equal(35.0, enum.sum) end - - def test_to_set - e = Enumerator.new { it << 1 << 1 << 2 << 3 << 5 } - set = e.to_set - assert_equal(Set[1, 2, 3, 5], set) - - ei = Enumerator.new(Float::INFINITY) { it << 1 << 1 << 2 << 3 << 5 } - assert_raise(ArgumentError) { ei.to_set } - end end diff --git a/test/ruby/test_set.rb b/test/ruby/test_set.rb index e8ac3e329e6678..70a61aa3b5d5fe 100644 --- a/test/ruby/test_set.rb +++ b/test/ruby/test_set.rb @@ -1003,6 +1003,12 @@ def test_to_set_not_calling_size set = assert_nothing_raised { enum.to_set } assert(set.is_a?(Set)) assert_equal(Set[1,2,3], set) + + enumerator = enum.to_enum + + set = assert_nothing_raised { enumerator.to_set } + assert(set.is_a?(Set)) + assert_equal(Set[1,2,3], set) end end From 61bab1889048f758396acf671c9797d6bc52504b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 17 Dec 2025 14:28:03 +0900 Subject: [PATCH 05/10] Rename to `struct rbimpl_size_overflow_tag` This struct is used for addition not only for multiplication, so remove the word `mul`, and make the member names more descriptive. --- gc.c | 42 +++++++++---------- include/ruby/internal/memory.h | 73 +++++++++++++++++----------------- io_buffer.c | 4 +- 3 files changed, 60 insertions(+), 59 deletions(-) diff --git a/gc.c b/gc.c index 8b6142b139650e..5a14cb3675032d 100644 --- a/gc.c +++ b/gc.c @@ -484,21 +484,21 @@ rb_malloc_grow_capa(size_t current, size_t type_size) return new_capacity; } -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag size_mul_add_overflow(size_t x, size_t y, size_t z) /* x * y + z */ { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - struct rbimpl_size_mul_overflow_tag u = rbimpl_size_add_overflow(t.right, z); - return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left, u.right }; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + struct rbimpl_size_overflow_tag u = rbimpl_size_add_overflow(t.result, z); + return (struct rbimpl_size_overflow_tag) { t.overflowed || u.overflowed, u.result }; } -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag size_mul_add_mul_overflow(size_t x, size_t y, size_t z, size_t w) /* x * y + z * w */ { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - struct rbimpl_size_mul_overflow_tag u = rbimpl_size_mul_overflow(z, w); - struct rbimpl_size_mul_overflow_tag v = rbimpl_size_add_overflow(t.right, u.right); - return (struct rbimpl_size_mul_overflow_tag) { t.left || u.left || v.left, v.right }; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + struct rbimpl_size_overflow_tag u = rbimpl_size_mul_overflow(z, w); + struct rbimpl_size_overflow_tag v = rbimpl_size_add_overflow(t.result, u.result); + return (struct rbimpl_size_overflow_tag) { t.overflowed || u.overflowed || v.overflowed, v.result }; } PRINTF_ARGS(NORETURN(static void gc_raise(VALUE, const char*, ...)), 2, 3); @@ -506,9 +506,9 @@ PRINTF_ARGS(NORETURN(static void gc_raise(VALUE, const char*, ...)), 2, 3); static inline size_t size_mul_or_raise(size_t x, size_t y, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(x, y); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(x, y); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -532,9 +532,9 @@ rb_size_mul_or_raise(size_t x, size_t y, VALUE exc) static inline size_t size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = size_mul_add_overflow(x, y, z); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = size_mul_add_overflow(x, y, z); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -559,9 +559,9 @@ rb_size_mul_add_or_raise(size_t x, size_t y, size_t z, VALUE exc) static inline size_t size_mul_add_mul_or_raise(size_t x, size_t y, size_t z, size_t w, VALUE exc) { - struct rbimpl_size_mul_overflow_tag t = size_mul_add_mul_overflow(x, y, z, w); - if (LIKELY(!t.left)) { - return t.right; + struct rbimpl_size_overflow_tag t = size_mul_add_mul_overflow(x, y, z, w); + if (LIKELY(!t.overflowed)) { + return t.result; } else if (rb_during_gc()) { rb_memerror(); /* or...? */ @@ -5372,11 +5372,11 @@ ruby_mimcalloc(size_t num, size_t size) { void *mem; #if CALC_EXACT_MALLOC_SIZE - struct rbimpl_size_mul_overflow_tag t = rbimpl_size_mul_overflow(num, size); - if (UNLIKELY(t.left)) { + struct rbimpl_size_overflow_tag t = rbimpl_size_mul_overflow(num, size); + if (UNLIKELY(t.overflowed)) { return NULL; } - size = t.right + sizeof(struct malloc_obj_info); + size = t.result + sizeof(struct malloc_obj_info); mem = calloc1(size); if (!mem) { return NULL; diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index 9fc6c39efca70b..cd099f85db9b31 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -408,7 +408,8 @@ typedef uint128_t DSIZE_T; /** * @private * - * This is an implementation detail of rbimpl_size_mul_overflow(). + * This is an implementation detail of rbimpl_size_mul_overflow() and + * rbimpl_size_add_overflow(). * * @internal * @@ -416,9 +417,9 @@ typedef uint128_t DSIZE_T; * nothing more than std::variant if we could use recent C++, but * reality is we cannot. */ -struct rbimpl_size_mul_overflow_tag { - bool left; /**< Whether overflow happened or not. */ - size_t right; /**< Multiplication result. */ +struct rbimpl_size_overflow_tag { + bool overflowed; /**< Whether overflow happened or not. */ + size_t result; /**< Calculation result. */ }; RBIMPL_SYMBOL_EXPORT_BEGIN() @@ -572,46 +573,46 @@ RBIMPL_ATTR_CONST() * * @param[in] x Arbitrary value. * @param[in] y Arbitrary value. - * @return `{ left, right }`, where `left` is whether there is an integer - * overflow or not, and `right` is a (possibly overflowed) result - * of `x` * `y`. + * @return `{ overflowed, result }`, where `overflowed` is whether there is + * an integer overflow or not, and `result` is a (possibly + * overflowed) result of `x` * `y`. * * @internal * * This is in fact also an implementation detail of ruby_xmalloc2() etc. */ -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag rbimpl_size_mul_overflow(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag ret = { false, 0, }; + struct rbimpl_size_overflow_tag ret = { false, 0, }; #if defined(ckd_mul) - ret.left = ckd_mul(&ret.right, x, y); + ret.overflowed = ckd_mul(&ret.result, x, y); #elif RBIMPL_HAS_BUILTIN(__builtin_mul_overflow) - ret.left = __builtin_mul_overflow(x, y, &ret.right); + ret.overflowed = __builtin_mul_overflow(x, y, &ret.result); #elif defined(DSIZE_T) RB_GNUC_EXTENSION DSIZE_T dx = x; RB_GNUC_EXTENSION DSIZE_T dy = y; RB_GNUC_EXTENSION DSIZE_T dz = dx * dy; - ret.left = dz > SIZE_MAX; - ret.right = RBIMPL_CAST((size_t)dz); + ret.overflowed = dz > SIZE_MAX; + ret.result = RBIMPL_CAST((size_t)dz); #elif defined(_MSC_VER) && defined(_M_AMD64) unsigned __int64 dp = 0; unsigned __int64 dz = _umul128(x, y, &dp); - ret.left = RBIMPL_CAST((bool)dp); - ret.right = RBIMPL_CAST((size_t)dz); + ret.overflowed = RBIMPL_CAST((bool)dp); + ret.result = RBIMPL_CAST((size_t)dz); #elif defined(_MSC_VER) && defined(_M_ARM64) - ret.left = __umulh(x, y) != 0; - ret.right = x * y; + ret.overflowed = __umulh(x, y) != 0; + ret.result = x * y; #else /* https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap */ - ret.left = (y != 0) && (x > SIZE_MAX / y); - ret.right = x * y; + ret.overflowed = (y != 0) && (x > SIZE_MAX / y); + ret.result = x * y; #endif return ret; @@ -635,11 +636,11 @@ rbimpl_size_mul_overflow(size_t x, size_t y) static inline size_t rbimpl_size_mul_or_raise(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag size = + struct rbimpl_size_overflow_tag size = rbimpl_size_mul_overflow(x, y); - if (RB_LIKELY(! size.left)) { - return size.right; + if (RB_LIKELY(! size.overflowed)) { + return size.result; } else { ruby_malloc_size_overflow(x, y); @@ -662,33 +663,33 @@ RBIMPL_ATTR_CONST() * * @param[in] x Arbitrary value. * @param[in] y Arbitrary value. - * @return `{ left, right }`, where `left` is whether there is an integer - * overflow or not, and `right` is a (possibly overflowed) result - * of `x` + `y`. + * @return `{ overflowed, result }`, where `overflowed` is whether there is + * an integer overflow or not, and `result` is a (possibly + * overflowed) result of `x` + `y`. * * @internal */ -static inline struct rbimpl_size_mul_overflow_tag +static inline struct rbimpl_size_overflow_tag rbimpl_size_add_overflow(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag ret = { false, 0, }; + struct rbimpl_size_overflow_tag ret = { false, 0, }; #if defined(ckd_add) - ret.left = ckd_add(&ret.right, x, y); + ret.overflowed = ckd_add(&ret.result, x, y); #elif RBIMPL_HAS_BUILTIN(__builtin_add_overflow) - ret.left = __builtin_add_overflow(x, y, &ret.right); + ret.overflowed = __builtin_add_overflow(x, y, &ret.result); #elif defined(DSIZE_T) RB_GNUC_EXTENSION DSIZE_T dx = x; RB_GNUC_EXTENSION DSIZE_T dy = y; RB_GNUC_EXTENSION DSIZE_T dz = dx + dy; - ret.left = dz > SIZE_MAX; - ret.right = (size_t)dz; + ret.overflowed = dz > SIZE_MAX; + ret.result = (size_t)dz; #else - ret.right = x + y; - ret.left = ret.right < y; + ret.result = x + y; + ret.overflowed = ret.result < y; #endif @@ -710,11 +711,11 @@ rbimpl_size_add_overflow(size_t x, size_t y) static inline size_t rbimpl_size_add_or_raise(size_t x, size_t y) { - struct rbimpl_size_mul_overflow_tag size = + struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(x, y); - if (RB_LIKELY(!size.left)) { - return size.right; + if (RB_LIKELY(!size.overflowed)) { + return size.result; } else { ruby_malloc_add_size_overflow(x, y); diff --git a/io_buffer.c b/io_buffer.c index 1ee2bc9324c6d2..85061076cd9476 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1527,8 +1527,8 @@ VALUE rb_io_buffer_free_locked(VALUE self) static bool size_sum_is_bigger_than(size_t a, size_t b, size_t x) { - struct rbimpl_size_mul_overflow_tag size = rbimpl_size_add_overflow(a, b); - return size.left || size.right > x; + struct rbimpl_size_overflow_tag size = rbimpl_size_add_overflow(a, b); + return size.overflowed || size.result > x; } // Validate that access to the buffer is within bounds, assuming you want to From f286e7001930dc1e13c9235b83af32216fde8d5c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 8 Oct 2024 12:09:15 +0900 Subject: [PATCH 06/10] win32: Set the source code charset to UTF-8 --- win32/setup.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/setup.mak b/win32/setup.mak index 15fa41b5622651..6eeaa325aea45e 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -22,7 +22,7 @@ MAKE = $(MAKE) -f $(MAKEFILE) MAKEFILE = Makefile !endif CPU = PROCESSOR_LEVEL -CC = $(CC) -nologo +CC = $(CC) -nologo -source-charset:utf-8 CPP = $(CC) -EP all: -prologue- -generic- -epilogue- From bd4353ba7aeb464c9736fe49ac017899b2295d60 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 16 Dec 2025 18:10:51 +0900 Subject: [PATCH 07/10] CI: Assume all C source files are UTF-8 now --- .github/workflows/check_misc.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 5cc891c477982c..e4d2b2c68961e5 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -38,10 +38,6 @@ jobs: # Skip 'push' events because post_push.yml fixes them on push if: ${{ github.repository == 'ruby/ruby' && startsWith(github.event_name, 'pull') }} - - name: Check if C-sources are US-ASCII - run: | - grep -r -n --exclude-dir=vendor --include='*.[chyS]' --include='*.asm' $'[^\t-~]' -- . && exit 1 || : - - name: Check for bash specific substitution in configure.ac run: | git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || : From f45b1e3a1bfff6546a63d741c404fc404fa91cc6 Mon Sep 17 00:00:00 2001 From: Misaki Shioi <31817032+shioimm@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:53:34 +0900 Subject: [PATCH 08/10] Update NEWS.md for Socket (#15608) --- NEWS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NEWS.md b/NEWS.md index 869f9b2f855d95..f039f7af1e7aef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -224,6 +224,11 @@ Note: We're only listing outstanding class updates. * `Socket.tcp` & `TCPSocket.new` accepts an `open_timeout` keyword argument to specify the timeout for the initial connection. [[Feature #21347]] + * When a user-specified timeout occurred in `TCPSocket.new`, either `Errno::ETIMEDOUT` + or `IO::TimeoutError` could previously be raised depending on the situation. + This behavior has been unified so that `IO::TimeoutError` is now consistently raised. + (Please note that, in `Socket.tcp`, there are still cases where `Errno::ETIMEDOUT` may + be raised in similar situations.) * String From c99670d6683fec770271d35c2ae082514b1abce3 Mon Sep 17 00:00:00 2001 From: Akinori Musha Date: Wed, 17 Dec 2025 18:36:10 +0900 Subject: [PATCH 09/10] Revert the default size of Enumerator::Producer to infinity [Bug #21780] --- NEWS.md | 4 ++-- enumerator.c | 5 +++-- test/ruby/test_enumerator.rb | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/NEWS.md b/NEWS.md index f039f7af1e7aef..4734a93f4cf75d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -38,8 +38,8 @@ Note: We're only listing outstanding class updates. * `Enumerator.produce` now accepts an optional `size` keyword argument to specify the size of the enumerator. It can be an integer, `Float::INFINITY`, a callable object (such as a lambda), or `nil` to - indicate unknown size. When not specified, the size is unknown (`nil`). - Previously, the size was always `Float::INFINITY` and not specifiable. + indicate unknown size. When not specified, the size defaults to + `Float::INFINITY`. ```ruby # Infinite enumerator diff --git a/enumerator.c b/enumerator.c index 93671dfe712073..5514d76dace3d4 100644 --- a/enumerator.c +++ b/enumerator.c @@ -3037,7 +3037,8 @@ producer_size(VALUE obj, VALUE args, VALUE eobj) * The optional +size+ keyword argument specifies the size of the enumerator, * which can be retrieved by Enumerator#size. It can be an integer, * +Float::INFINITY+, a callable object (such as a lambda), or +nil+ to - * indicate unknown size. When not specified, the size is unknown (+nil+). + * indicate unknown size. When not specified, the size defaults to + * +Float::INFINITY+. * * # Infinite enumerator * enum = Enumerator.produce(1, size: Float::INFINITY, &:succ) @@ -3063,7 +3064,7 @@ enumerator_s_produce(int argc, VALUE *argv, VALUE klass) rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "01:", &init, &opts); rb_get_kwargs(opts, keyword_ids, 0, 1, &size); - size = UNDEF_P(size) ? Qnil : convert_to_feasible_size_value(size); + size = UNDEF_P(size) ? DBL2NUM(HUGE_VAL) : convert_to_feasible_size_value(size); if (argc == 0 || (argc == 1 && !NIL_P(opts))) { init = Qundef; diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 177d7c04fab4cf..9b972d7b22e1aa 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -892,7 +892,7 @@ def test_produce passed_args = [] enum = Enumerator.produce { |obj| passed_args << obj; (obj || 0).succ } assert_instance_of(Enumerator, enum) - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [nil, 1, 2], passed_args @@ -900,14 +900,14 @@ def test_produce passed_args = [] enum = Enumerator.produce(1) { |obj| passed_args << obj; obj.succ } assert_instance_of(Enumerator, enum) - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_equal [1, 2, 3], enum.take(3) assert_equal [1, 2], passed_args # Raising StopIteration words = "The quick brown fox jumps over the lazy dog.".scan(/\w+/) enum = Enumerator.produce { words.shift or raise StopIteration } - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_instance_of(Enumerator, enum) assert_equal %w[The quick brown fox jumps over the lazy dog], enum.to_a @@ -917,7 +917,7 @@ def test_produce obj.respond_to?(:first) or raise StopIteration obj.first } - assert_nil enum.size + assert_equal Float::INFINITY, enum.size assert_instance_of(Enumerator, enum) assert_nothing_raised { assert_equal [ From aee4b24829cfd26c9089f539179bd7770d283bfa Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Wed, 17 Dec 2025 13:26:55 +0900 Subject: [PATCH 10/10] [ruby/error_highlight] Show no message when failing to get caller/callee snippets Even with Ruby 4.0, snippets is not always available, such as in irb by default. It would be better to just say nothing than to show a confusing message. https://github.com/ruby/error_highlight/commit/ef80ce73a1 --- lib/error_highlight/core_ext.rb | 2 +- test/error_highlight/test_error_highlight.rb | 142 +++++++------------ 2 files changed, 53 insertions(+), 91 deletions(-) diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index 1cfc331582748e..c3354f46cdf5dc 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -24,7 +24,7 @@ module CoreExt _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines out += "\n | #{ snippet } #{ highlight }" else - out += "\n (cannot highlight method definition; try Ruby 4.0 or later)" + # do nothing end end ret << "\n" + out if out diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index d3ca99021b9ad2..ec39b1c4db6fc7 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1450,26 +1450,28 @@ def exc.backtrace_locations = [] RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(exc.backtrace_locations.first) end - WRONG_NUMBER_OF_ARGUMENTS_LIENO = __LINE__ + 1 + def process_callee_snippet(str) + return str if MethodDefLocationSupported + + str.sub(/\n +\|.*\n +\^+\n\z/, "") + end + + WRONG_NUMBER_OF_ARGUMENTS_LINENO = __LINE__ + 1 def wrong_number_of_arguments_test(x, y) x + y end def test_wrong_number_of_arguments_for_method lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 2) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | wrong_number_of_arguments_test(1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LIENO } - #{ - MethodDefLocationSupported ? - "| def wrong_number_of_arguments_test(x, y) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + callee: #{ __FILE__ }:#{ WRONG_NUMBER_OF_ARGUMENTS_LINENO } + | def wrong_number_of_arguments_test(x, y) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END wrong_number_of_arguments_test(1) @@ -1483,19 +1485,15 @@ def keyword_test(kw1:, kw2:, kw3:) def test_missing_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keyword: :kw3 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2) @@ -1504,19 +1502,15 @@ def test_missing_keyword def test_missing_keywords # multiple missing keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keywords: :kw2, :kw3 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1) @@ -1525,19 +1519,15 @@ def test_missing_keywords # multiple missing keywords def test_unknown_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do unknown keyword: :kw4 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4) @@ -1546,19 +1536,15 @@ def test_unknown_keyword def test_unknown_keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do unknown keywords: :kw4, :kw5 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) ^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ KEYWORD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| def keyword_test(kw1:, kw2:, kw3:) - ^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def keyword_test(kw1:, kw2:, kw3:) + ^^^^^^^^^^^^ END keyword_test(kw1: 1, kw2: 2, kw3: 3, kw4: 4, kw5: 5) @@ -1576,19 +1562,15 @@ def wrong_number_of_arguments_test2( def test_wrong_number_of_arguments_for_method2 lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 3) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | wrong_number_of_arguments_test2(1) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ WRONG_NUBMER_OF_ARGUMENTS_TEST2_LINENO } - #{ - MethodDefLocationSupported ? - "| def wrong_number_of_arguments_test2( - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def wrong_number_of_arguments_test2( + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ END wrong_number_of_arguments_test2(1) @@ -1598,19 +1580,15 @@ def test_wrong_number_of_arguments_for_method2 def test_wrong_number_of_arguments_for_lambda_literal v = -> {} lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 0) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | v.call(1) ^^^^^ callee: #{ __FILE__ }:#{ lineno - 1 } - #{ - MethodDefLocationSupported ? - "| v = -> {} - ^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | v = -> {} + ^^ END v.call(1) @@ -1620,19 +1598,15 @@ def test_wrong_number_of_arguments_for_lambda_literal def test_wrong_number_of_arguments_for_lambda_method v = lambda { } lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 0) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | v.call(1) ^^^^^ callee: #{ __FILE__ }:#{ lineno - 1 } - #{ - MethodDefLocationSupported ? - "| v = lambda { } - ^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | v = lambda { } + ^ END v.call(1) @@ -1646,19 +1620,15 @@ def test_wrong_number_of_arguments_for_lambda_method def test_wrong_number_of_arguments_for_define_method lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do wrong number of arguments (given 1, expected 2) (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | define_method_test(1) ^^^^^^^^^^^^^^^^^^ callee: #{ __FILE__ }:#{ DEFINE_METHOD_TEST_LINENO } - #{ - MethodDefLocationSupported ? - "| define_method :define_method_test do |x, y| - ^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | define_method :define_method_test do |x, y| + ^^ END define_method_test(1) @@ -1742,19 +1712,15 @@ def self . baz(x:) def test_singleton_method_with_spacing_missing_keyword lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keyword: :x (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | SingletonMethodWithSpacing.baz ^^^^ callee: #{ __FILE__ }:#{ SingletonMethodWithSpacing::LINENO } - #{ - MethodDefLocationSupported ? - "| def self . baz(x:) - ^^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def self . baz(x:) + ^^^^^ END SingletonMethodWithSpacing.baz @@ -1770,19 +1736,15 @@ def self.run(shop_id:, param1:) def test_singleton_method_multiple_missing_keywords lineno = __LINE__ - assert_error_message(ArgumentError, <<~END) do + assert_error_message(ArgumentError, process_callee_snippet(<<~END)) do missing keywords: :shop_id, :param1 (ArgumentError) - caller: #{ __FILE__ }:#{ lineno + 16 } + caller: #{ __FILE__ }:#{ lineno + 12 } | SingletonMethodMultipleKwargs.run ^^^^ callee: #{ __FILE__ }:#{ SingletonMethodMultipleKwargs::LINENO } - #{ - MethodDefLocationSupported ? - "| def self.run(shop_id:, param1:) - ^^^^" : - "(cannot highlight method definition; try Ruby 4.0 or later)" - } + | def self.run(shop_id:, param1:) + ^^^^ END SingletonMethodMultipleKwargs.run