From d80c03d22a5e92a5423a18da1d6494c484392c87 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 8 Aug 2025 16:31:29 -0700 Subject: [PATCH 01/25] Fix id2ref table build when GC in progress Previously, if GC was in progress when we're initially building the id2ref table, it could see the empty table and then crash when trying to remove ids from it. This commit fixes the bug by only publishing the table after GC is done. Co-authored-by: Aaron Patterson --- gc.c | 7 +++++-- test/ruby/test_objectspace.rb | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/gc.c b/gc.c index 3d0935ad1a4f10..4c8a042c1e8513 100644 --- a/gc.c +++ b/gc.c @@ -1975,14 +1975,17 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id) // GC Must not trigger while we build the table, otherwise if we end // up freeing an object that had an ID, we might try to delete it from // the table even though it wasn't inserted yet. - id2ref_tbl = st_init_table(&object_id_hash_type); - id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, id2ref_tbl); + st_table *tmp_id2ref_tbl = st_init_table(&object_id_hash_type); + VALUE tmp_id2ref_value = TypedData_Wrap_Struct(0, &id2ref_tbl_type, tmp_id2ref_tbl); // build_id2ref_i will most certainly malloc, which could trigger GC and sweep // objects we just added to the table. // By calling rb_gc_disable() we also save having to handle potentially garbage objects. bool gc_disabled = RTEST(rb_gc_disable()); { + id2ref_tbl = tmp_id2ref_tbl; + id2ref_value = tmp_id2ref_value; + rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl); } if (!gc_disabled) rb_gc_enable(); diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index f27f586ab79fca..a479547599a03a 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -284,6 +284,21 @@ def test_each_object_recursive_key end; end + def test_id2ref_table_build + assert_separately([], <<-End) + 10.times do + Object.new.object_id + end + + GC.start(immediate_mark: false) + + obj = Object.new + EnvUtil.suppress_warning do + assert_equal obj, ObjectSpace._id2ref(obj.object_id) + end + End + end + def test_each_object_singleton_class assert_separately([], <<-End) class C From 22fe80f275f5e8a66d1e38daab1eb663eab79850 Mon Sep 17 00:00:00 2001 From: Carl Zulauf Date: Mon, 4 Aug 2025 08:52:03 -0600 Subject: [PATCH 02/25] Fix Typo in Regular Expressions docs (_regexp.rdoc) Small fix for a typo in the regular expression docs. The line of code above this change does not produce the output shown in the docs. With this change the docs will show the correct output for this example of using regex quantifiers. --- doc/_regexp.rdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/_regexp.rdoc b/doc/_regexp.rdoc index 45a307fad660d3..c9f3742241dd00 100644 --- a/doc/_regexp.rdoc +++ b/doc/_regexp.rdoc @@ -502,7 +502,7 @@ An added _quantifier_ specifies how many matches are required or allowed: /\w*/.match('x') # => # /\w*/.match('xyz') - # => # + # => # - + - Matches one or more times: From 4209ebb1e4c30f6cb70047238c858c99e773e964 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 5 Aug 2025 11:12:13 -0700 Subject: [PATCH 03/25] [DOC] Array#fill fix to indicate return is self doc currently indicates the return value as `new_array` but then in the first sentence explains "always returns +self+ (never a new array)". --- array.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/array.c b/array.c index f485223e34bd21..1afd52fb3dab91 100644 --- a/array.c +++ b/array.c @@ -4755,10 +4755,10 @@ rb_ary_clear(VALUE ary) /* * call-seq: - * fill(object, start = nil, count = nil) -> new_array - * fill(object, range) -> new_array - * fill(start = nil, count = nil) {|element| ... } -> new_array - * fill(range) {|element| ... } -> new_array + * fill(object, start = nil, count = nil) -> self + * fill(object, range) -> self + * fill(start = nil, count = nil) {|element| ... } -> self + * fill(range) {|element| ... } -> self * * Replaces selected elements in +self+; * may add elements to +self+; From 60ca525fce71b702ea8e5893c976044170a56d75 Mon Sep 17 00:00:00 2001 From: Ethan Date: Tue, 5 Aug 2025 11:42:05 -0700 Subject: [PATCH 04/25] [DOC] Array#map! fix to indicate return is self --- array.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/array.c b/array.c index 1afd52fb3dab91..9f13b1bf5142b3 100644 --- a/array.c +++ b/array.c @@ -3659,9 +3659,9 @@ rb_ary_collect(VALUE ary) /* * call-seq: - * collect! {|element| ... } -> new_array + * collect! {|element| ... } -> self * collect! -> new_enumerator - * map! {|element| ... } -> new_array + * map! {|element| ... } -> self * map! -> new_enumerator * * With a block given, calls the block with each element of +self+ From 23c0113932407abccddbc6ee5b297d38d2d2bb9c Mon Sep 17 00:00:00 2001 From: koh-sh <34917718+koh-sh@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:30:17 +0900 Subject: [PATCH 05/25] [ruby/stringio] fix: prevent segfault in StringIO#seek with SEEK_END on null device (https://github.com/ruby/stringio/pull/137) Fixes segmentation fault when calling `seek` with `SEEK_END` on null device StringIO created by `StringIO.new(nil)`. ```bash ruby -e "require 'stringio'; StringIO.new(nil).seek(0, IO::SEEK_END)" ``` I tested with below versions. ```bash [koh@Kohs-MacBook-Pro] ~ % ruby -v;gem info stringio;sw_vers ruby 3.4.5 (2025-07-16 revision https://github.com/ruby/stringio/commit/20cda200d3) +PRISM [arm64-darwin24] *** LOCAL GEMS *** stringio (3.1.2) Authors: Nobu Nakada, Charles Oliver Nutter Homepage: https://github.com/ruby/stringio Licenses: Ruby, BSD-2-Clause Installed at (default): /Users/koh/.local/share/mise/installs/ruby/3.4.5/lib/ruby/gems/3.4.0 Pseudo IO on String ProductName: macOS ProductVersion: 15.5 BuildVersion: 24F74 [koh@Kohs-MacBook-Pro] ~ % ``` https://github.com/ruby/stringio/commit/9399747bf9 --- ext/stringio/stringio.c | 6 +++++- test/stringio/test_stringio.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 3003939e10f8e4..d9beb25434df56 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -837,7 +837,11 @@ strio_seek(int argc, VALUE *argv, VALUE self) offset = ptr->pos; break; case 2: - offset = RSTRING_LEN(ptr->string); + if (NIL_P(ptr->string)) { + offset = 0; + } else { + offset = RSTRING_LEN(ptr->string); + } break; default: error_inval("invalid whence"); diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 002b946b6f1b58..4c9cf374253965 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,16 @@ def test_null assert_nil io.getc end + def test_seek_null + io = StringIO.new(nil) + assert_equal(0, io.seek(0, IO::SEEK_SET)) + assert_equal(0, io.pos) + assert_equal(0, io.seek(0, IO::SEEK_CUR)) + assert_equal(0, io.pos) + assert_equal(0, io.seek(0, IO::SEEK_END)) # This should not segfault + assert_equal(0, io.pos) + end + def test_truncate io = StringIO.new("") io.puts "abc" From 31f2d8990dcebf84cbbd3fedf838babaa59554a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 19:44:26 +0900 Subject: [PATCH 06/25] [ruby/stringio] Fix SEGV at read/pread on null StringIO https://github.com/ruby/stringio/commit/113dd5a55e --- ext/stringio/stringio.c | 28 +++++++++++++++++++--------- test/stringio/test_stringio.rb | 10 ++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index d9beb25434df56..1e6310d2922a2e 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -203,6 +203,18 @@ check_modifiable(struct StringIO *ptr) } } +static inline bool +outside_p(struct StringIO *ptr, long pos) +{ + return NIL_P(ptr->string) || pos >= RSTRING_LEN(ptr->string); +} + +static inline bool +eos_p(struct StringIO *ptr) +{ + return outside_p(ptr, ptr->pos); +} + static VALUE strio_s_allocate(VALUE klass) { @@ -628,9 +640,8 @@ static struct StringIO * strio_to_read(VALUE self) { struct StringIO *ptr = readable(self); - if (NIL_P(ptr->string)) return NULL; - if (ptr->pos < RSTRING_LEN(ptr->string)) return ptr; - return NULL; + if (eos_p(ptr)) return NULL; + return ptr; } /* @@ -910,7 +921,7 @@ strio_getc(VALUE self) int len; char *p; - if (NIL_P(str) || pos >= RSTRING_LEN(str)) { + if (eos_p(ptr)) { return Qnil; } p = RSTRING_PTR(str)+pos; @@ -931,7 +942,7 @@ strio_getbyte(VALUE self) { struct StringIO *ptr = readable(self); int c; - if (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string)) { + if (eos_p(ptr)) { return Qnil; } c = RSTRING_PTR(ptr->string)[ptr->pos++]; @@ -1609,10 +1620,9 @@ strio_read(int argc, VALUE *argv, VALUE self) if (len < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } - if (len > 0 && - (NIL_P(ptr->string) || ptr->pos >= RSTRING_LEN(ptr->string))) { + if (eos_p(ptr)) { if (!NIL_P(str)) rb_str_resize(str, 0); - return Qnil; + return len > 0 ? Qnil : rb_str_new(0, 0); } binary = 1; break; @@ -1688,7 +1698,7 @@ strio_pread(int argc, VALUE *argv, VALUE self) struct StringIO *ptr = readable(self); - if (offset >= RSTRING_LEN(ptr->string)) { + if (outside_p(ptr, offset)) { rb_eof_error(); } diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 4c9cf374253965..8b5ab37657ddec 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,16 @@ def test_null assert_nil io.getc end + def test_pread_null + io = StringIO.new(nil) + assert_raise(EOFError) { io.pread(1, 0) } + end + + def test_read_null + io = StringIO.new(nil) + assert_equal "", io.read(0) + end + def test_seek_null io = StringIO.new(nil) assert_equal(0, io.seek(0, IO::SEEK_SET)) From b4d5ebcd12418642dec9f1e2e73ac87495666262 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 19:45:25 +0900 Subject: [PATCH 07/25] [ruby/stringio] Fix SEGV at eof? on null StringIO https://github.com/ruby/stringio/commit/29b9133332 --- test/stringio/test_stringio.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/stringio/test_stringio.rb b/test/stringio/test_stringio.rb index 8b5ab37657ddec..5215a6d31273dd 100644 --- a/test/stringio/test_stringio.rb +++ b/test/stringio/test_stringio.rb @@ -70,6 +70,11 @@ def test_null assert_nil io.getc end + def test_pread_eof + io = StringIO.new(nil) + assert_predicate io, :eof? + end + def test_pread_null io = StringIO.new(nil) assert_raise(EOFError) { io.pread(1, 0) } From 77b3495e979ceb597e86d255d3d647b8d5b43a9e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 20:15:53 +0900 Subject: [PATCH 08/25] [ruby/stringio] Adjust indent [ci skip] https://github.com/ruby/stringio/commit/ac6292c17f --- ext/stringio/stringio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/stringio/stringio.c b/ext/stringio/stringio.c index 1e6310d2922a2e..0493c8cd50856b 100644 --- a/ext/stringio/stringio.c +++ b/ext/stringio/stringio.c @@ -1935,7 +1935,7 @@ Init_stringio(void) #undef rb_intern #ifdef HAVE_RB_EXT_RACTOR_SAFE - rb_ext_ractor_safe(true); + rb_ext_ractor_safe(true); #endif VALUE StringIO = rb_define_class("StringIO", rb_cObject); From 2a6345e957c01f4495323723c7a3d7ac0d4ac339 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sat, 9 Aug 2025 10:54:22 +0200 Subject: [PATCH 09/25] time.c: fix time_mark_and_move when WIDEVALUE_IS_WIDER In such case the pointer need to be casted. --- time.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/time.c b/time.c index 7159a9309840de..95df85db9359a4 100644 --- a/time.c +++ b/time.c @@ -1891,8 +1891,8 @@ static void time_mark_and_move(void *ptr) { struct time_object *tobj = ptr; - if (!FIXWV_P(tobj->timew)) { - rb_gc_mark_and_move(&WIDEVAL_GET(tobj->timew)); + if (!WIDEVALUE_IS_WIDER || !FIXWV_P(tobj->timew)) { + rb_gc_mark_and_move((VALUE *)&WIDEVAL_GET(tobj->timew)); } rb_gc_mark_and_move(&tobj->vtm.year); rb_gc_mark_and_move(&tobj->vtm.subsecx); From 90f81994125200d0640cc3bcf7340700144f804b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 10 Aug 2025 00:55:34 +0900 Subject: [PATCH 10/25] CI: mingw: Tweak misc system & package info --- .github/workflows/mingw.yml | 50 +++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index ce53e33aaea46c..d74bf062b96842 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -86,25 +86,37 @@ jobs: - name: Misc system & package info working-directory: run: | - # show where - result=true - for e in gcc.exe ragel.exe make.exe libcrypto-3-x64.dll libssl-3-x64.dll; do - echo ::group::$'\e[93m'$e$'\e[m' - where $e || result=false - echo ::endgroup:: - done - # show version - for e in gcc ragel make "openssl version"; do - case "$e" in *" "*) ;; *) e="$e --version";; esac - echo ::group::$'\e[93m'$e$'\e[m' - $e || result=false - echo ::endgroup:: - done - # show packages - echo ::group::$'\e[93m'Packages$'\e[m' - pacman -Qs mingw-w64-ucrt-x86_64-* | sed -n "s,local/mingw-w64-ucrt-x86_64-,,p" - echo ::endgroup:: - $result + group() { echo ::group::$'\e[94;1m'"$*"$'\e[m'; } + endgroup() { echo ::endgroup::; } + + group Path + cygpath -wa / . $(type -p cygpath bash sh) + endgroup + + I() { + group $1 + run Where type -pa $1 && { [ $# -eq 1 ] || run Version "$@"; } || + failed+=($1) + endgroup + } + run() { local w m=$1; shift; w="$("$@")" && show "$m" && indent "$w"; } + indent() { [ -z "$1" ] || echo "$1" | /bin/sed '/^$/!s/^/ /'; } + show() { echo $'\e[96m'"$*"$'\e[m'; } + + failed=() + + I gcc.exe --version + I ragel.exe --version + I make.exe --version + I openssl.exe version + I libcrypto-3-x64.dll + I libssl-3-x64.dll + + group Packages + pacman -Qs mingw-w64-ucrt-x86_64-* | /bin/sed -n "s,local/mingw-w64-ucrt-x86_64-,,p" + endgroup + + [ ${#failed[@]} -eq 0 ] - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: From c1f9f0a7ef9cfb4c57fb90b0a04f8f0274856386 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 10 Aug 2025 15:41:35 +0900 Subject: [PATCH 11/25] CI: windows: Windows-2019 or earlier no longer used --- .github/workflows/windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 053c37ec5d9f23..d7c88393be52e0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -39,7 +39,7 @@ jobs: test_task: test-bundled-gems fail-fast: false - runs-on: windows-${{ matrix.os < 2022 && '2019' || matrix.os }} + runs-on: windows-${{ matrix.os }} if: >- ${{!(false @@ -94,7 +94,7 @@ jobs: - name: Install libraries with vcpkg run: | - vcpkg install --vcpkg-root=C:\Users\runneradmin\scoop\apps\vcpkg\current + vcpkg install --vcpkg-root=%USERPROFILE%\scoop\apps\vcpkg\current working-directory: src - name: Save vcpkg artifact @@ -184,7 +184,7 @@ jobs: - name: Set up Launchable uses: ./.github/actions/launchable/setup with: - os: windows-${{ matrix.os < 2022 && '2019' || matrix.os }} + os: windows-${{ matrix.os }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build srcdir: src From cc4eba000b18558dc65fb349dce2b92aa7d1760f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 10 Aug 2025 01:07:40 +0900 Subject: [PATCH 12/25] Win: Use `@` instead of `echo off` in `vssetup.cmd` `echo off` affects the batch files called from this file as well. --- win32/vssetup.cmd | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/win32/vssetup.cmd b/win32/vssetup.cmd index be77c87b294a6c..1ff0a7d10ab6f4 100755 --- a/win32/vssetup.cmd +++ b/win32/vssetup.cmd @@ -1,27 +1,27 @@ -@echo off -setlocal ENABLEEXTENSIONS +@setlocal ENABLEEXTENSIONS +::- do not `echo off` that affects the called batch files ::- check for vswhere -set vswhere=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe -if not exist "%vswhere%" ( +@set vswhere=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe +@if not exist "%vswhere%" ( echo 1>&2 vswhere.exe not found exit /b 1 ) ::- find the latest build tool and its setup batch file. -set VSDEVCMD= -for /f "delims=" %%I in ('"%vswhere%" -products * -latest -property installationPath') do ( +@set VSDEVCMD= +@for /f "delims=" %%I in ('"%vswhere%" -products * -latest -property installationPath') do @( set VSDEVCMD=%%I\Common7\Tools\VsDevCmd.bat ) -if not defined VSDEVCMD ( +@if not defined VSDEVCMD ( echo 1>&2 Visual Studio not found exit /b 1 ) ::- default to the current processor. -set arch=%PROCESSOR_ARCHITECTURE% +@set arch=%PROCESSOR_ARCHITECTURE% ::- `vsdevcmd.bat` requires arch names to be lowercase -for %%i in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do @( +@for %%i in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do @( call set arch=%%arch:%%i=%%i%% ) -echo on && endlocal && "%VSDEVCMD%" -arch=%arch% -host_arch=%arch% %* +@(endlocal && "%VSDEVCMD%" -arch=%arch% -host_arch=%arch% %*) From a443cd012a352a7660ee22f9468082031f354d47 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 20:26:19 +0900 Subject: [PATCH 13/25] CI: mingw: Set `cmd` as the default shell It is used in more steps than `sh`. --- .github/workflows/mingw.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index d74bf062b96842..ca3d6a28593a1a 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -117,6 +117,7 @@ jobs: endgroup [ ${#failed[@]} -eq 0 ] + shell: sh - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -135,6 +136,7 @@ jobs: run: > ../src/configure --disable-install-doc --prefix=/. --build=$CHOST --host=$CHOST --target=$CHOST + shell: sh - name: make all timeout-minutes: 30 @@ -157,7 +159,6 @@ jobs: - name: test timeout-minutes: 30 run: make test - shell: cmd env: GNUMAKEFLAGS: '' RUBY_TESTOPTS: '-v --tty=no' @@ -165,7 +166,6 @@ jobs: - name: test-all timeout-minutes: 45 - shell: cmd run: | make ${{ StartsWith(matrix.test_task, 'test/') && matrix.test_task || 'test-all' }} env: @@ -180,7 +180,6 @@ jobs: timeout-minutes: 10 run: | make ${{ StartsWith(matrix.test_task, 'spec/') && matrix.test_task || 'test-spec' }} - shell: cmd if: ${{ matrix.test_task == 'check' || matrix.test_task == 'test-spec' || StartsWith(matrix.test_task, 'spec/') }} - uses: ./src/.github/actions/slack @@ -192,4 +191,4 @@ jobs: defaults: run: working-directory: build - shell: sh + shell: cmd From 4adb6f6969d3f58d3987708201acf4f44b870cc9 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 9 Aug 2025 21:37:08 +0900 Subject: [PATCH 14/25] CI: mingw: Set up msys2 environment variables --- .github/workflows/mingw.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index ca3d6a28593a1a..3e889c56c06fff 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -70,6 +70,24 @@ jobs: with: ruby-version: '3.2' + - name: Set up env + id: setup-env + working-directory: + run: | + $msys2 = ${env:MSYS2_LOCATION} + echo $msys2\usr\bin $msys2\ucrt64\bin | + Tee-Object ${env:GITHUB_PATH} -Append -Encoding utf-8 + + # Use the fast device for the temporary directory. + # %TEMP% is inconsistent with %TMP% and test-all expects they are consistent. + # https://github.com/actions/virtual-environments/issues/712#issuecomment-613004302 + $tmp = ${env:RUNNER_TEMP} + echo HOME=$home TMP=$tmp TEMP=$tmp TMPDIR=$tmp | + Tee-Object ${env:GITHUB_ENV} -Append -Encoding utf-8 + shell: pwsh # cmd.exe does not strip spaces before `|`. + env: + MSYS2_LOCATION: ${{ env.RI_DEVKIT }} + - name: Remove Strawberry Perl pkg-config working-directory: # `pkg-config.bat` included in Strawberry Perl is written in From b0a4e2399b34701d4fc05f9543142b0c73d1fb9b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 24 Jul 2025 14:01:02 +0900 Subject: [PATCH 15/25] CI: mingw: Use the official actions for msys2 Because ruby/setup-ruby is affected to test result. --- .github/workflows/mingw.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 3e889c56c06fff..1c4cb533d3f3c7 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -65,10 +65,21 @@ jobs: )}} steps: - - name: Set up Ruby & MSYS2 - uses: ruby/setup-ruby@d8d83c3960843afb664e821fed6be52f37da5267 # v1.231.0 + - uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0 + id: msys2 with: - ruby-version: '3.2' + msystem: UCRT64 + update: true + install: >- + git + make + ruby + autoconf + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-ragel + mingw-w64-ucrt-x86_64-openssl + mingw-w64-ucrt-x86_64-libyaml + mingw-w64-ucrt-x86_64-libffi - name: Set up env id: setup-env @@ -86,7 +97,7 @@ jobs: Tee-Object ${env:GITHUB_ENV} -Append -Encoding utf-8 shell: pwsh # cmd.exe does not strip spaces before `|`. env: - MSYS2_LOCATION: ${{ env.RI_DEVKIT }} + MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }} - name: Remove Strawberry Perl pkg-config working-directory: From df11c073f37d6bd17be249a7e3ff0e596fb3f42b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 10 Aug 2025 15:45:49 +0900 Subject: [PATCH 16/25] [DOC] Fix `vssetup.cmd` arguments --- doc/windows.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/windows.md b/doc/windows.md index 13c797875e6a3d..4ea03d0507d2b7 100644 --- a/doc/windows.md +++ b/doc/windows.md @@ -99,16 +99,18 @@ sh ../../ruby/configure -C --disable-install-doc --with-opt-dir=C:\Users\usernam To cross build arm64 binary: ``` - cmd /k win32\vssetup.cmd -arch arm64 + cmd /k win32\vssetup.cmd -arch=arm64 ``` To cross build x64 binary: ``` - cmd /k win32\vssetup.cmd -arch x64 + cmd /k win32\vssetup.cmd -arch=x64 ``` - See `win32\vssetup.cmd -help` for other command line options. + This batch file is a wrapper of `vsdevcmd.bat` and options are + passed to it as-is. `win32\vssetup.cmd -help` for other command + line options. **Note** building ruby requires following commands. From 5e324ac11c2c9c6712e2cdff37f212367f71e094 Mon Sep 17 00:00:00 2001 From: Erim Icel Date: Sun, 10 Aug 2025 13:50:43 +0100 Subject: [PATCH 17/25] Optimize `str_casecmp` length check using pointer end --- benchmark/string_casecmp.yml | 2 ++ string.c | 6 +++--- test/ruby/test_string.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/benchmark/string_casecmp.yml b/benchmark/string_casecmp.yml index 2354040a049e3f..cc75bc803e3e78 100644 --- a/benchmark/string_casecmp.yml +++ b/benchmark/string_casecmp.yml @@ -20,7 +20,9 @@ benchmark: casecmp-10: lstr10.casecmp(ustr10) casecmp-100: lstr100.casecmp(ustr100) casecmp-1000: lstr1000.casecmp(ustr1000) + casecmp-1000vs10: lstr1000.casecmp(ustr10) casecmp-nonascii1: lnonascii1.casecmp(unonascii1) casecmp-nonascii10: lnonascii10.casecmp(unonascii10) casecmp-nonascii100: lnonascii100.casecmp(unonascii100) casecmp-nonascii1000: lnonascii1000.casecmp(unonascii1000) + casecmp-nonascii1000vs10: lnonascii1000.casecmp(unonascii10) \ No newline at end of file diff --git a/string.c b/string.c index fe848d6a4a821c..96a9f96bd3432b 100644 --- a/string.c +++ b/string.c @@ -4381,9 +4381,9 @@ str_casecmp(VALUE str1, VALUE str2) p2 += l2; } } - if (RSTRING_LEN(str1) == RSTRING_LEN(str2)) return INT2FIX(0); - if (RSTRING_LEN(str1) > RSTRING_LEN(str2)) return INT2FIX(1); - return INT2FIX(-1); + if (p1 == p1end && p2 == p2end) return INT2FIX(0); + if (p1 == p1end) return INT2FIX(-1); + return INT2FIX(1); } /* diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 8fb57bd58e6111..811785bf7ed3fb 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2832,9 +2832,12 @@ def test_compare_different_encoding_string def test_casecmp assert_equal(0, S("FoO").casecmp("fOO")) assert_equal(1, S("FoO").casecmp("BaR")) + assert_equal(-1, S("foo").casecmp("FOOBAR")) assert_equal(-1, S("baR").casecmp("FoO")) assert_equal(1, S("\u3042B").casecmp("\u3042a")) assert_equal(-1, S("foo").casecmp("foo\0")) + assert_equal(1, S("FOOBAR").casecmp("foo")) + assert_equal(0, S("foo\0bar").casecmp("FOO\0BAR")) assert_nil(S("foo").casecmp(:foo)) assert_nil(S("foo").casecmp(Object.new)) @@ -2842,6 +2845,16 @@ def test_casecmp o = Object.new def o.to_str; "fOO"; end assert_equal(0, S("FoO").casecmp(o)) + + assert_equal(0, S("#" * 128 + "A" * 256 + "b").casecmp("#" * 128 + "a" * 256 + "B")) + assert_equal(0, S("a" * 256 + "B").casecmp("A" * 256 + "b")) + + assert_equal(-1, S("@").casecmp("`")) + assert_equal(0, S("hello\u00E9X").casecmp("HELLO\u00E9x")) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp(s2)) end def test_casecmp? @@ -2857,6 +2870,10 @@ def test_casecmp? o = Object.new def o.to_str; "fOO"; end assert_equal(true, S("FoO").casecmp?(o)) + + s1 = S("\xff".force_encoding("UTF-8")) + s2 = S("\xff".force_encoding("ISO-2022-JP")) + assert_nil(s1.casecmp?(s2)) end def test_upcase2 From 09d6cfc55c9048dfe248be0ca71eb5b8968e6b28 Mon Sep 17 00:00:00 2001 From: Erim Icel Date: Sun, 10 Aug 2025 14:08:53 +0100 Subject: [PATCH 18/25] Update test_string.rb --- test/ruby/test_string.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 811785bf7ed3fb..c7e4b0c1ec7fee 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2842,6 +2842,9 @@ def test_casecmp assert_nil(S("foo").casecmp(:foo)) assert_nil(S("foo").casecmp(Object.new)) + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + o = Object.new def o.to_str; "fOO"; end assert_equal(0, S("FoO").casecmp(o)) @@ -2867,6 +2870,9 @@ def test_casecmp? assert_nil(S("foo").casecmp?(:foo)) assert_nil(S("foo").casecmp?(Object.new)) + assert_nil(S("foo").casecmp(0)) + assert_nil(S("foo").casecmp(5.00)) + o = Object.new def o.to_str; "fOO"; end assert_equal(true, S("FoO").casecmp?(o)) From c914389ae83d0a5b8ddc00d2c1577c937de72743 Mon Sep 17 00:00:00 2001 From: Erim Icel Date: Sun, 10 Aug 2025 15:36:04 +0100 Subject: [PATCH 19/25] Update string_casecmp.yml --- benchmark/string_casecmp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/string_casecmp.yml b/benchmark/string_casecmp.yml index cc75bc803e3e78..88a3555c8a8837 100644 --- a/benchmark/string_casecmp.yml +++ b/benchmark/string_casecmp.yml @@ -25,4 +25,4 @@ benchmark: casecmp-nonascii10: lnonascii10.casecmp(unonascii10) casecmp-nonascii100: lnonascii100.casecmp(unonascii100) casecmp-nonascii1000: lnonascii1000.casecmp(unonascii1000) - casecmp-nonascii1000vs10: lnonascii1000.casecmp(unonascii10) \ No newline at end of file + casecmp-nonascii1000vs10: lnonascii1000.casecmp(unonascii10) From 62b5fe8984db6c36919ff6f0a69ccca37090ce0c Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Wed, 6 Aug 2025 17:59:15 -0500 Subject: [PATCH 20/25] [DOC] Tweaks for GC.config --- gc.rb | 85 +++++++++++++++++++++++++---------------------------------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/gc.rb b/gc.rb index 1298e3005660b6..54a88cf8f7dbd4 100644 --- a/gc.rb +++ b/gc.rb @@ -258,71 +258,58 @@ def self.stat_heap heap_name = nil, hash_or_key = nil # call-seq: # GC.config -> hash - # GC.config(hash) -> hash + # GC.config(hash_to_merge) -> merged_hash # - # Sets or gets information about the current \GC config. + # This method is expected to be defined, useful, and well-behaved + # only in the CRuby implementation. # - # Configuration parameters are \GC implementation-specific and may change - # without notice. + # Sets or gets information about the current \GC configuration. # - # This method can be called without parameters to retrieve the current config - # as a +Hash+ with +Symbol+ keys. + # With no argument given, returns a hash containing the configuration: # - # This method can also be called with a +Hash+ argument to assign values to - # valid config keys. Config keys missing from the passed +Hash+ will be left - # unmodified. + # GC.config + # # => {rgengc_allow_full_mark: true, implementation: "default"} # - # If a key/value pair is passed to this function that does not correspond to - # a valid config key for the \GC implementation being used, no config will be - # updated, the key will be present in the returned Hash, and its value will - # be +nil+. This is to facilitate easy migration between \GC implementations. + # With argument +hash_to_merge+ given, + # merges that hash into the stored configuration hash; + # ignores unknown hash keys; + # returns the implementation-specific configuration hash (see below): # - # In both call-seqs, the return value of GC.config will be a +Hash+ - # containing the most recent full configuration, i.e., all keys and values - # defined by the specific \GC implementation being used. In the case of a - # config update, the return value will include the new values being updated. + # GC.config(rgengc_allow_full_mark: false) + # # => {rgengc_allow_full_mark: false} + # GC.config + # # => {rgengc_allow_full_mark: false, implementation: "default"} + # GC.config(foo: 'bar') + # # => {rgengc_allow_full_mark: false} + # GC.config + # # => {rgengc_allow_full_mark: false, implementation: "default"} # - # This method is only expected to work on CRuby. - # - # === \GC Implementation independent values - # - # The GC.config hash can also contain keys that are global and - # read-only. These keys are not specific to any one \GC library implementation - # and attempting to write to them will raise +ArgumentError+. + # All-Implementations Configuration # - # There is currently only one global, read-only key: + # The single read-only entry for all implementations is: # - # [implementation] - # Returns a +String+ containing the name of the currently loaded \GC library, - # if one has been loaded using +RUBY_GC_LIBRARY+, and "default" in all other - # cases + # - +implementation+: + # the string name of the implementation; + # for the Ruby default implementation, 'default'. # - # === \GC Implementation specific values + # Implementation-Specific Configuration # - # \GC libraries are expected to document their own configuration. Valid keys - # for Ruby's default \GC implementation are: + # A \GC implementation maintains its own implementation-specific configuration. # - # [rgengc_allow_full_mark] - # Controls whether the \GC is allowed to run a full mark (young & old objects). + # For Ruby's default implementation the single entry is: # - # When +true+, \GC interleaves major and minor collections. This is the default. \GC - # will function as intended. + # - +rgengc_allow_full_mark+: + # Controls whether the \GC is allowed to run a full mark (young & old objects): # - # When +false+, the \GC will never trigger a full marking cycle unless - # explicitly requested by user code. Instead, only a minor mark will run— - # only young objects will be marked. When the heap space is exhausted, new - # pages will be allocated immediately instead of running a full mark. + # - +true+ (default): \GC interleaves major and minor collections. + # - +false+: \GC does not initiate a full marking cycle unless + # explicitly directed by user code; + # see GC.start. # - # A flag will be set to notify that a full mark has been - # requested. This flag is accessible using - # GC.latest_gc_info(:need_major_by) - # - # The user can trigger a major collection at any time using - # GC.start(full_mark: true) - # - # When +false+, Young to Old object promotion is disabled. For performance - # reasons, it is recommended to warm up an application using +Process.warmup+ + # Setting this parameter to +false+ disables young-to-old promotion . + # For performance reasons, we recommended warming up the application using Process.warmup # before setting this parameter to +false+. + # def self.config hash = nil return Primitive.gc_config_get unless hash From ad146320957cd84311d5ce857591e05f50f87ca6 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 8 Aug 2025 11:19:40 -0500 Subject: [PATCH 21/25] [DOC] Tweaks for GC.config --- gc.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gc.rb b/gc.rb index 54a88cf8f7dbd4..a7620fd9ace9a3 100644 --- a/gc.rb +++ b/gc.rb @@ -260,11 +260,12 @@ def self.stat_heap heap_name = nil, hash_or_key = nil # GC.config -> hash # GC.config(hash_to_merge) -> merged_hash # - # This method is expected to be defined, useful, and well-behaved - # only in the CRuby implementation. + # This method is implementation-specific to CRuby. # # Sets or gets information about the current \GC configuration. # + # Configuration parameters are \GC implementation-specific and may change without notice. + # # With no argument given, returns a hash containing the configuration: # # GC.config @@ -302,13 +303,13 @@ def self.stat_heap heap_name = nil, hash_or_key = nil # Controls whether the \GC is allowed to run a full mark (young & old objects): # # - +true+ (default): \GC interleaves major and minor collections. - # - +false+: \GC does not initiate a full marking cycle unless - # explicitly directed by user code; + # A flag is set to notify GC that a full mark has been requested. + # This flag is accessible via GC.latest_gc_info(:need_major_by). + # - +false+: \GC does not initiate a full marking cycle unless explicitly directed by user code; # see GC.start. - # - # Setting this parameter to +false+ disables young-to-old promotion . - # For performance reasons, we recommended warming up the application using Process.warmup - # before setting this parameter to +false+. + # Setting this parameter to +false+ disables young-to-old promotion. + # For performance reasons, we recommended warming up the application using Process.warmup + # before setting this parameter to +false+. # def self.config hash = nil return Primitive.gc_config_get unless hash From e0b72ad2f1f4836e3303596ecae1f62aa60b2d80 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 11 Aug 2025 08:25:07 -0500 Subject: [PATCH 22/25] [DOC] Update JIT options --- doc/ruby/options.md | 46 +++++---------------------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/doc/ruby/options.md b/doc/ruby/options.md index bfbd2530de397e..95f8cf453c9f14 100644 --- a/doc/ruby/options.md +++ b/doc/ruby/options.md @@ -672,6 +672,11 @@ $ ruby --internal-encoding=cesu-8 -e 'puts Encoding::default_internal' CESU-8 ``` +### `--jit` + +Option `--jit` is an alias for option `--yjit`, which enables YJIT; +see additional YJIT options in the [YJIT documentation](rdoc-ref:yjit/yjit.md). + ### `--verbose`: Set `$VERBOSE` Option `--verbose` sets global variable `$VERBOSE` to `true` @@ -681,44 +686,3 @@ and disables input from `$stdin`. Option `--version` prints the version of the Ruby interpreter, then exits. -## Experimental Options - -These options are experimental in the current Ruby release, -and may be modified or withdrawn in later releases. - -### `--jit` - -Option `-jit` enables JIT compilation with the default option. - -#### `--jit-debug` - -Option `--jit-debug` enables JIT debugging (very slow); -adds compiler flags if given. - -#### `--jit-max-cache=num` - -Option `--jit-max-cache=num` sets the maximum number of methods -to be JIT-ed in a cache; default: 100). - -#### `--jit-min-calls=num` - -Option `jit-min-calls=num` sets the minimum number of calls to trigger JIT -(for testing); default: 10000). - -#### `--jit-save-temps` - -Option `--jit-save-temps` saves JIT temporary files in $TMP or /tmp (for testing). - -#### `--jit-verbose` - -Option `--jit-verbose` prints JIT logs of level `num` or less -to `$stderr`; default: 0. - -#### `--jit-wait` - -Option `--jit-wait` waits until JIT compilation finishes every time (for testing). - -#### `--jit-warnings` - -Option `--jit-warnings` enables printing of JIT warnings. - From 4775d1ffa8a34f0bca3f6124c98426d56eb8e1b6 Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Sun, 10 Aug 2025 21:30:41 +0900 Subject: [PATCH 23/25] Add NODE IN locations Add locations to struct `RNode_IN`. memo: ```bash > ruby -e 'case 1; in 2 then 3; end' --parser=prism --dump=parsetree @ ProgramNode (location: (1,0)-(1,24)) +-- locals: [] +-- statements: @ StatementsNode (location: (1,0)-(1,24)) +-- body: (length: 1) +-- @ CaseMatchNode (location: (1,0)-(1,24)) +-- predicate: | @ IntegerNode (location: (1,5)-(1,6)) | +-- IntegerBaseFlags: decimal | +-- value: 1 +-- conditions: (length: 1) | +-- @ InNode (location: (1,8)-(1,19)) | +-- pattern: | | @ IntegerNode (location: (1,11)-(1,12)) | | +-- IntegerBaseFlags: decimal | | +-- value: 2 | +-- statements: | | @ StatementsNode (location: (1,18)-(1,19)) | | +-- body: (length: 1) | | +-- @ IntegerNode (location: (1,18)-(1,19)) | | +-- IntegerBaseFlags: decimal | | +-- value: 3 | +-- in_loc: (1,8)-(1,10) = "in" | +-- then_loc: (1,13)-(1,17) = "then" +-- else_clause: nil +-- case_keyword_loc: (1,0)-(1,4) = "case" +-- end_keyword_loc: (1,21)-(1,24) = "end" ``` --- ast.c | 6 ++++++ node_dump.c | 5 ++++- parse.y | 15 +++++++++------ rubyparser.h | 3 +++ test/ruby/test_ast.rb | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/ast.c b/ast.c index bc2adeacd630a4..04b954385477e1 100644 --- a/ast.c +++ b/ast.c @@ -866,6 +866,12 @@ node_locations(VALUE ast_value, const NODE *node) location_new(&RNODE_IF(node)->if_keyword_loc), location_new(&RNODE_IF(node)->then_keyword_loc), location_new(&RNODE_IF(node)->end_keyword_loc)); + case NODE_IN: + return rb_ary_new_from_args(4, + location_new(nd_code_loc(node)), + location_new(&RNODE_IN(node)->in_keyword_loc), + location_new(&RNODE_IN(node)->then_keyword_loc), + location_new(&RNODE_IN(node)->operator_loc)); case NODE_MODULE: return rb_ary_new_from_args(3, location_new(nd_code_loc(node)), diff --git a/node_dump.c b/node_dump.c index c318baeeede009..18ac3d7b35b4e5 100644 --- a/node_dump.c +++ b/node_dump.c @@ -309,8 +309,11 @@ dump_node(VALUE buf, VALUE indent, int comment, const NODE * node) ANN("example: case x; in 1; foo; in 2; bar; else baz; end"); F_NODE(nd_head, RNODE_IN, "in pattern"); F_NODE(nd_body, RNODE_IN, "in body"); - LAST_NODE; F_NODE(nd_next, RNODE_IN, "next in clause"); + F_LOC(in_keyword_loc, RNODE_IN); + F_LOC(then_keyword_loc, RNODE_IN); + LAST_NODE; + F_LOC(operator_loc, RNODE_IN); return; case NODE_WHILE: diff --git a/parse.y b/parse.y index e77dc790bcd1cd..c0f46a395f9f59 100644 --- a/parse.y +++ b/parse.y @@ -1070,7 +1070,7 @@ static rb_node_case_t *rb_node_case_new(struct parser_params *p, NODE *nd_head, static rb_node_case2_t *rb_node_case2_new(struct parser_params *p, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *case_keyword_loc, const YYLTYPE *end_keyword_loc); static rb_node_case3_t *rb_node_case3_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, const YYLTYPE *loc, const YYLTYPE *case_keyword_loc, const YYLTYPE *end_keyword_loc); static rb_node_when_t *rb_node_when_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc, const YYLTYPE *keyword_loc, const YYLTYPE *then_keyword_loc); -static rb_node_in_t *rb_node_in_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc); +static rb_node_in_t *rb_node_in_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc, const YYLTYPE *in_keyword_loc, const YYLTYPE *then_keyword_loc, const YYLTYPE *operator_loc); static rb_node_while_t *rb_node_while_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, long nd_state, const YYLTYPE *loc, const YYLTYPE *keyword_loc, const YYLTYPE *closing_loc); static rb_node_until_t *rb_node_until_new(struct parser_params *p, NODE *nd_cond, NODE *nd_body, long nd_state, const YYLTYPE *loc, const YYLTYPE *keyword_loc, const YYLTYPE *closing_loc); static rb_node_iter_t *rb_node_iter_new(struct parser_params *p, rb_node_args_t *nd_args, NODE *nd_body, const YYLTYPE *loc); @@ -1178,7 +1178,7 @@ static rb_node_error_t *rb_node_error_new(struct parser_params *p, const YYLTYPE #define NEW_CASE2(b,loc,ck_loc,ek_loc) (NODE *)rb_node_case2_new(p,b,loc,ck_loc,ek_loc) #define NEW_CASE3(h,b,loc,ck_loc,ek_loc) (NODE *)rb_node_case3_new(p,h,b,loc,ck_loc,ek_loc) #define NEW_WHEN(c,t,e,loc,k_loc,t_loc) (NODE *)rb_node_when_new(p,c,t,e,loc,k_loc,t_loc) -#define NEW_IN(c,t,e,loc) (NODE *)rb_node_in_new(p,c,t,e,loc) +#define NEW_IN(c,t,e,loc,ik_loc,tk_loc,o_loc) (NODE *)rb_node_in_new(p,c,t,e,loc,ik_loc,tk_loc,o_loc) #define NEW_WHILE(c,b,n,loc,k_loc,c_loc) (NODE *)rb_node_while_new(p,c,b,n,loc,k_loc,c_loc) #define NEW_UNTIL(c,b,n,loc,k_loc,c_loc) (NODE *)rb_node_until_new(p,c,b,n,loc,k_loc,c_loc) #define NEW_ITER(a,b,loc) (NODE *)rb_node_iter_new(p,a,b,loc) @@ -3472,7 +3472,7 @@ expr : command_call pop_pktbl(p, $p_pktbl); pop_pvtbl(p, $p_pvtbl); p->ctxt.in_kwarg = $ctxt.in_kwarg; - $$ = NEW_CASE3($arg, NEW_IN($body, 0, 0, &@body), &@$, &NULL_LOC, &NULL_LOC); + $$ = NEW_CASE3($arg, NEW_IN($body, 0, 0, &@body, &NULL_LOC, &NULL_LOC, &@2), &@$, &NULL_LOC, &NULL_LOC); /*% ripper: case!($:arg, in!($:body, Qnil, Qnil)) %*/ } | arg keyword_in @@ -3485,7 +3485,7 @@ expr : command_call pop_pktbl(p, $p_pktbl); pop_pvtbl(p, $p_pvtbl); p->ctxt.in_kwarg = $ctxt.in_kwarg; - $$ = NEW_CASE3($arg, NEW_IN($body, NEW_TRUE(&@body), NEW_FALSE(&@body), &@body), &@$, &NULL_LOC, &NULL_LOC); + $$ = NEW_CASE3($arg, NEW_IN($body, NEW_TRUE(&@body), NEW_FALSE(&@body), &@body, &@keyword_in, &NULL_LOC, &NULL_LOC), &@$, &NULL_LOC, &NULL_LOC); /*% ripper: case!($:arg, in!($:body, Qnil, Qnil)) %*/ } | arg %prec tLBRACE_ARG @@ -5399,7 +5399,7 @@ p_case_body : keyword_in compstmt(stmts) p_cases[cases] { - $$ = NEW_IN($expr, $compstmt, $cases, &@$); + $$ = NEW_IN($expr, $compstmt, $cases, &@$, &@keyword_in, &@then, &NULL_LOC); /*% ripper: in!($:expr, $:compstmt, $:cases) %*/ } ; @@ -11528,12 +11528,15 @@ rb_node_when_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd } static rb_node_in_t * -rb_node_in_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc) +rb_node_in_new(struct parser_params *p, NODE *nd_head, NODE *nd_body, NODE *nd_next, const YYLTYPE *loc, const YYLTYPE *in_keyword_loc, const YYLTYPE *then_keyword_loc, const YYLTYPE *operator_loc) { rb_node_in_t *n = NODE_NEWNODE(NODE_IN, rb_node_in_t, loc); n->nd_head = nd_head; n->nd_body = nd_body; n->nd_next = nd_next; + n->in_keyword_loc = *in_keyword_loc; + n->then_keyword_loc = *then_keyword_loc; + n->operator_loc = *operator_loc; return n; } diff --git a/rubyparser.h b/rubyparser.h index e436d1c404b2a2..cc63efd3f85998 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -324,6 +324,9 @@ typedef struct RNode_IN { struct RNode *nd_head; struct RNode *nd_body; struct RNode *nd_next; + rb_code_location_t in_keyword_loc; + rb_code_location_t then_keyword_loc; + rb_code_location_t operator_loc; } rb_node_in_t; typedef struct RNode_LOOP { diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 6372b0d34e5e71..9a7d75c270b661 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1514,6 +1514,20 @@ def test_if_locations assert_locations(node.children[-1].children[1].children[0].locations, [[1, 11, 1, 17], [1, 13, 1, 15], nil, nil]) end + def test_in_locations + node = ast_parse("case 1; in 2 then 3; end") + assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 20], [1, 8, 1, 10], [1, 13, 1, 17], nil]) + + node = ast_parse("1 => a") + assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], nil, nil, [1, 2, 1, 4]]) + + node = ast_parse("1 in a") + assert_locations(node.children[-1].children[1].locations, [[1, 5, 1, 6], [1, 2, 1, 4], nil, nil]) + + node = ast_parse("case 1; in 2; 3; end") + assert_locations(node.children[-1].children[1].locations, [[1, 8, 1, 16], [1, 8, 1, 10], [1, 12, 1, 13], nil]) + end + def test_next_locations node = ast_parse("loop { next 1 }") assert_locations(node.children[-1].children[-1].children[-1].locations, [[1, 7, 1, 13], [1, 7, 1, 11]]) From 61fff8a92f5b7fbcdd0bea46150ce0845637483e Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 8 Aug 2025 11:15:15 -0400 Subject: [PATCH 24/25] Fix return value of setting in GC.config gc_config_set returned rb_gc_impl_config_get, but gc_config_get also added the implementation key to the return value. This caused the return value of GC.config to differ depending on whether the optional hash argument is provided or not. --- gc.c | 2 +- gc.rb | 8 ++++---- test/ruby/test_gc.rb | 9 +++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/gc.c b/gc.c index 4c8a042c1e8513..7663e82f41b5ab 100644 --- a/gc.c +++ b/gc.c @@ -4357,7 +4357,7 @@ gc_config_set(rb_execution_context_t *ec, VALUE self, VALUE hash) rb_gc_impl_config_set(objspace, hash); - return rb_gc_impl_config_get(objspace); + return Qnil; } static VALUE diff --git a/gc.rb b/gc.rb index a7620fd9ace9a3..603520df5312ab 100644 --- a/gc.rb +++ b/gc.rb @@ -312,17 +312,17 @@ def self.stat_heap heap_name = nil, hash_or_key = nil # before setting this parameter to +false+. # def self.config hash = nil - return Primitive.gc_config_get unless hash - - if(Primitive.cexpr!("RBOOL(RB_TYPE_P(hash, T_HASH))")) + if Primitive.cexpr!("RBOOL(RB_TYPE_P(hash, T_HASH))") if hash.include?(:implementation) raise ArgumentError, 'Attempting to set read-only key "Implementation"' end Primitive.gc_config_set hash - else + elsif hash != nil raise ArgumentError end + + Primitive.gc_config_get end # call-seq: diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 85022cbc4d8924..ccccd212b692eb 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -75,12 +75,9 @@ def test_gc_config_setting_returns_updated_config_hash GC.start end - def test_gc_config_setting_returns_nil_for_missing_keys - missing_value = GC.config(no_such_key: true)[:no_such_key] - assert_nil(missing_value) - ensure - GC.config(full_mark: true) - GC.start + def test_gc_config_setting_returns_config_hash + hash = GC.config(no_such_key: true) + assert_equal(GC.config, hash) end def test_gc_config_disable_major From 27df91c2d4a509be7b78cc712548c823e8ae35d6 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Mon, 11 Aug 2025 16:44:22 -0400 Subject: [PATCH 25/25] CI: Surface Rust warnings on PRs that touch any Rust code Rust PRs will have a failed CI step if they trigger any warnings. This helps us stay on top of warnings from new Rust releases and also ones we accidentally write. Fix a typo for demo, since this only runs when Rust files are changed. --- .github/workflows/rust-warnings.yml | 53 +++++++++++++++++++++++++++++ zjit/src/hir.rs | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/rust-warnings.yml diff --git a/.github/workflows/rust-warnings.yml b/.github/workflows/rust-warnings.yml new file mode 100644 index 00000000000000..b05ebbfe64ff3e --- /dev/null +++ b/.github/workflows/rust-warnings.yml @@ -0,0 +1,53 @@ +# Surface Rust warnings on PRs that touch any Rust code. +# Not a required check so we never block people over new warnings +# that might come from a new Rust version being released. +name: Rust warnings +on: + pull_request: + types: + - opened + - synchronize + - reopened + paths: + - '**.rs' + - '!**.inc.rs' + merge_group: + +concurrency: + group: ${{ github.workflow }} / ${{ startsWith(github.event_name, 'pull') && github.ref_name || github.sha }} + cancel-in-progress: ${{ startsWith(github.event_name, 'pull') }} + +permissions: + contents: read + +jobs: + make: + env: + GITPULLOPTIONS: --no-tags origin ${{ github.ref }} + + runs-on: ubuntu-24.04 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.head_commit.message, 'Document') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') + )}} + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install Rust + run: rustup default beta + + - name: Rust warnings + run: | + set -euo pipefail + cargo check --quiet --all-features --message-format=json \ + | jq -r 'select(.reason == "compiler-message" and .message.level == "warning") | .message.rendered' \ + > warnings.txt + cat warnings.txt + ! grep --quiet '[^[:space:]]' warnings.txt diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 87d2a613d0e930..4c9b53f3ed9418 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -343,7 +343,7 @@ impl<'a> std::fmt::Display for ConstPrinter<'a> { /// /// Because this is extra state external to any pointer being printed, a /// printing adapter struct that wraps the pointer along with this map is -/// required to make use of this effectly. The [`std::fmt::Display`] +/// required to make use of this effectively. The [`std::fmt::Display`] /// implementation on the adapter struct can then be reused to implement /// `Display` on the inner type with a default [`PtrPrintMap`], which /// does not perform any mapping.