From d2a9bb69437ee054c6841dceec30238ec4604abc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 22:30:27 +0900 Subject: [PATCH 01/87] [ruby/zlib] Fix buffer overflow at ungetc https://github.com/ruby/zlib/commit/608d2be66f --- ext/zlib/zlib.c | 4 +--- test/zlib/test_zlib.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 578ad108515d64..466a901c139753 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -860,9 +860,7 @@ zstream_buffer_ungets(struct zstream *z, const Bytef *b, unsigned long len) char *bufptr; long filled; - if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) { - zstream_expand_buffer_into(z, len); - } + zstream_expand_buffer_into(z, len); RSTRING_GETMEM(z->buf, bufptr, filled); memmove(bufptr + len, bufptr, filled); diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 5b0bf9c9807ce7..48b8f172ff2bc2 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -882,6 +882,25 @@ def test_ungetc_at_start_of_file assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]") end + def test_ungetc_buffer_underflow + initial_bufsize = 1024 + payload = "A" * initial_bufsize + gzip_io = StringIO.new + Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) } + compressed = gzip_io.string + + reader = Zlib::GzipReader.new(StringIO.new(compressed)) + reader.read(1) + overflow_bytes = "B" * (initial_bufsize) + reader.ungetc(overflow_bytes) + data = reader.read(overflow_bytes.bytesize) + assert_equal overflow_bytes.bytesize, data.bytesize, data + assert_empty data.delete("B"), data + data = reader.read() + assert_equal initial_bufsize - 1, data.bytesize, data + assert_empty data.delete("A"), data + end + def test_open Tempfile.create("test_zlib_gzip_reader_open") {|t| t.close From 48a210537de69c3ff2da1609ee8f2e3b94e2b536 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 26 Feb 2026 11:22:39 +0900 Subject: [PATCH 02/87] [ruby/zlib] Bump up to 3.2.3 https://github.com/ruby/zlib/commit/d9c7876988 --- ext/zlib/zlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 466a901c139753..481d74b2b60e5f 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "3.2.2" +#define RUBY_ZLIB_VERSION "3.2.3" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) From c76ead9e21127622591c1bec215bb8cf21f0cbe6 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Mar 2026 01:39:42 +0000 Subject: [PATCH 03/87] Update default gems list at 48a210537de69c3ff2da1609ee8f2e [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index d9aaa08be6c754..353243add1f3f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -76,6 +76,7 @@ releases. * strscan 3.1.7.dev * 3.1.6 to [v3.1.7][strscan-v3.1.7] * syntax_suggest 2.0.3 +* zlib 3.2.3 ### The following bundled gems are updated. From 97e27d7f30a0df4b0b67a619d5df4f1b2d1cca0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:11:19 +0000 Subject: [PATCH 04/87] Bump the github-actions group across 1 directory with 2 updates Bumps the github-actions group with 2 updates in the / directory: [ruby/setup-ruby](https://github.com/ruby/setup-ruby) and [taiki-e/install-action](https://github.com/taiki-e/install-action). Updates `ruby/setup-ruby` from 1.288.0 to 1.289.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/09a7688d3b55cf0e976497ff046b70949eeaccfd...19a43a6a2428d455dbd1b85344698725179c9d8c) Updates `taiki-e/install-action` from 2.68.18 to 2.68.19 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/205eb1d74c6feda89abb1f3a09360601953286c0...385db9cc6bf65d19775b02084a4b698eaca9a4f2) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-version: 1.289.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: taiki-e/install-action dependency-version: 2.68.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 4 ++-- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index e7351adc0c9d59..ecaca98b8b6b10 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -73,7 +73,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 9c826666483724..1eb931e82ee4fe 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 4563a455fc05b0..223b30e7e86042 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 5fffa0d6954d13..7254aa669e0a29 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -38,7 +38,7 @@ jobs: with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: 4.0 diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 1919e4f7bf2365..29ddfa80d697f3 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -42,7 +42,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 6569c4f726fcfa..32cbcb53c8a7cf 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -23,7 +23,7 @@ jobs: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: head diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index fcde133c802a89..435efaea0b80af 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -62,7 +62,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 13ca6ad5d3fba1..ec34cc742c2f4c 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -59,7 +59,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7db5d2f1891f0b..23c6689042e7f1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: 3.3.4 diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 029f4a6bd43962..0314269a32594c 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -49,7 +49,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 8355159e249e57..3ffe0cc1a5a1f2 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 0de982cbe4f369..d0491f4482d278 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,7 +70,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index b9f02735dd8992..0196fa51cf5616 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -99,7 +99,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1b9be96ab7b189..ae01cf3ebb5d56 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 1ff0b1deb3b765..0f40de798ceb3d 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -133,7 +133,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index baf1b1bc0a6cd6..12b4fc08412e31 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 + - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 6c529ed6bc40c4..20e6b456756c2e 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,12 +114,12 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 + - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} From 0201e926b05cfb559346c8b6b8bc1d1dbff44aad Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Mar 2026 02:42:29 +0000 Subject: [PATCH 05/87] [DOC] Update bundled gems list at 97e27d7f30a0df4b0b67a619d5df4f --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 353243add1f3f3..7a73e16f9f54cb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -77,6 +77,7 @@ releases. * 3.1.6 to [v3.1.7][strscan-v3.1.7] * syntax_suggest 2.0.3 * zlib 3.2.3 + * 3.2.2 to [v3.2.3][zlib-v3.2.3] ### The following bundled gems are updated. @@ -137,6 +138,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0 [resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1 [strscan-v3.1.7]: https://github.com/ruby/strscan/releases/tag/v3.1.7 +[zlib-v3.2.3]: https://github.com/ruby/zlib/releases/tag/v3.2.3 [test-unit-3.7.4]: https://github.com/test-unit/test-unit/releases/tag/3.7.4 [test-unit-3.7.5]: https://github.com/test-unit/test-unit/releases/tag/3.7.5 [test-unit-3.7.6]: https://github.com/test-unit/test-unit/releases/tag/3.7.6 From 8a87cebd1874f8f9f68af8928191ee3f0d97bb28 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 4 Mar 2026 21:55:54 -0600 Subject: [PATCH 06/87] [DOC] Add note about link fragments (#16304) --- doc/contributing/documentation_guide.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index 51b480103c70ac..9945ab57fbf28a 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -288,6 +288,30 @@ The link should lead to a target in https://docs.ruby-lang.org/en/master/. Also use a full URL-based link for a link to an off-site document. +#### Fragments + +In general, a link that includes a [fragment][fragment] +must cite the exact identifier on the target page; +otherwise, the browser finds no suitable identifier, +and does not scroll to the desired part of the page. + +However, certain pages on `github.com` and `github.io` +support "fuzzy" identifier matching, so that URL +https://github.com/rdp/ruby_tutorials_core/wiki/Ruby-Talk-FAQ#-why-are-rubys-floats-imprecise, +(whose fragment is `-why-are-rubys-floats-imprecise`) +scrolls to heading "Why are ruby’s floats imprecise?" +even though the identifier there actually is the longer +`#user-content--why-are-rubys-floats-imprecise`. + +Ruby documentation should avoid using these shortened fragments, for two reasons: + +- The GitHub pages that do this implement it using Javascript; + if the user's browser has Javascript disabled + (which some employers actually require), + the shortened fragment is ineffective and the desired scrolling does not occur. +- A program that checks links in Ruby documentation will find no suitable identifier, + and therefore will report the fragment as not found. + ### Variable Names The name of a variable (as specified in its call-seq) should be marked up as @@ -617,6 +641,7 @@ best to use a separate paragraph for each case you are discussing. [bold text]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#bold [call-seq]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#directive-for-specifying-rdoc-source-format [code blocks]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#code-blocks +[fragment]: https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment [headings]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#headings [irb]: https://ruby.github.io/irb/index.html [links]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#links From 858c96c568b64a5fcb7f93b567643e7b2b565307 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 20 Feb 2026 08:58:18 +0100 Subject: [PATCH 07/87] [ruby/json] Reimplement `to_json` methods in Ruby https://github.com/ruby/json/commit/3f32c47de4 --- ext/json/generator/generator.c | 292 ++++-------------------- ext/json/lib/json/common.rb | 46 +++- test/json/json_common_interface_test.rb | 12 +- test/json/json_generator_test.rb | 23 -- 4 files changed, 84 insertions(+), 289 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 186a45714d48eb..e699d31812c45e 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -74,7 +74,6 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj); -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj); @@ -701,206 +700,6 @@ static void convert_UTF8_to_ASCII_only_JSON(search_state *search, const unsigned } } -/* - * Document-module: JSON::Ext::Generator - * - * This is the JSON generator implemented as a C extension. It can be - * configured to be used by setting - * - * JSON.generator = JSON::Ext::Generator - * - * with the method generator= in JSON. - * - */ - -/* Explanation of the following: that's the only way to not pollute - * standard library's docs with GeneratorMethods:: which - * are uninformative and take a large place in a list of classes - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Array - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Bignum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::FalseClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Fixnum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Float - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Hash - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Integer - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::NilClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Object - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String::Extend - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::TrueClass - * :nodoc: - */ - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON object, that is generated from - * this Hash instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_object, Qfalse); -} - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON array, that is generated from - * this Array instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_array, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_integer, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Float number. - */ -static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_float, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * This string should be encoded with UTF-8 A call to this method - * returns a JSON string encoded with UTF16 big endian characters as - * \u????. - */ -static VALUE mString_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_string, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for true: 'true'. - */ -static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("true", 4); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for false: 'false'. - */ -static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("false", 5); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for nil: 'null'. - */ -static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("null", 4); -} - -/* - * call-seq: to_json(*) - * - * Converts this object to a string (calling #to_s), converts - * it to a JSON string, and returns the result. This is a fallback, if no - * special method #to_json was defined for some object. - */ -static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self) -{ - VALUE state; - VALUE string = rb_funcall(self, i_to_s, 0); - rb_scan_args(argc, argv, "01", &state); - Check_Type(string, T_STRING); - state = cState_from_state_s(cState, state); - return cState_partial_generate(state, string, generate_json_string, Qfalse); -} - static void State_mark(void *ptr) { JSON_Generator_State *state = ptr; @@ -1348,14 +1147,6 @@ static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *dat fbuffer_append_str(buffer, StringValue(tmp)); } -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj) -{ - if (FIXNUM_P(obj)) - generate_json_fixnum(buffer, data, obj); - else - generate_json_bignum(buffer, data, obj); -} - static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { double value = RFLOAT_VALUE(obj); @@ -1399,7 +1190,7 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d fbuffer_append_str(buffer, fragment); } -static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback) { bool as_json_called = false; start: @@ -1426,15 +1217,15 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU generate_json_bignum(buffer, data, obj); break; case T_HASH: - if (klass != rb_cHash) goto general; + if (fallback && klass != rb_cHash) goto general; generate_json_object(buffer, data, obj); break; case T_ARRAY: - if (klass != rb_cArray) goto general; + if (fallback && klass != rb_cArray) goto general; generate_json_array(buffer, data, obj); break; case T_STRING: - if (klass != rb_cString) goto general; + if (fallback && klass != rb_cString) goto general; if (RB_LIKELY(valid_json_string_p(obj))) { raw_generate_json_string(buffer, data, obj); @@ -1450,7 +1241,7 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU generate_json_symbol(buffer, data, obj); break; case T_FLOAT: - if (klass != rb_cFloat) goto general; + if (fallback && klass != rb_cFloat) goto general; generate_json_float(buffer, data, obj); break; case T_STRUCT: @@ -1474,6 +1265,16 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU } } +static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, true); +} + +static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, false); +} + static VALUE generate_json_try(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; @@ -1491,7 +1292,7 @@ static VALUE generate_json_ensure(VALUE d) return Qundef; } -static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) +static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) { GET_STATE(self); @@ -1509,9 +1310,7 @@ static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, .obj = obj, .func = func }; - VALUE result = rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); - RB_GC_GUARD(self); - return result; + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* call-seq: @@ -1530,6 +1329,15 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) return cState_partial_generate(self, obj, generate_json, io); } +/* :nodoc: */ +static VALUE cState_generate_no_fallback(int argc, VALUE *argv, VALUE self) +{ + rb_check_arity(argc, 1, 2); + VALUE obj = argv[0]; + VALUE io = argc > 1 ? argv[1] : Qnil; + return cState_partial_generate(self, obj, generate_json_no_fallback, io); +} + static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) { rb_warn("The json gem extension was loaded with the stdlib ruby code. You should upgrade rubygems with `gem update --system`"); @@ -2028,7 +1836,7 @@ static VALUE cState_configure(VALUE self, VALUE opts) return self; } -static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io, generator_func func) { JSON_Generator_State state = {0}; state_init(&state); @@ -2046,14 +1854,21 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) .state = &state, .depth = state.depth, .obj = obj, - .func = generate_json, + .func = func, }; return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } -/* - * - */ +static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json); +} + +static VALUE cState_m_generate_no_fallback(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json_no_fallback); +} + void Init_generator(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE @@ -2118,39 +1933,12 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); + rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1); rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); rb_define_singleton_method(cState, "generate", cState_m_generate, 3); - - VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods"); - - VALUE mObject = rb_define_module_under(mGeneratorMethods, "Object"); - rb_define_method(mObject, "to_json", mObject_to_json, -1); - - VALUE mHash = rb_define_module_under(mGeneratorMethods, "Hash"); - rb_define_method(mHash, "to_json", mHash_to_json, -1); - - VALUE mArray = rb_define_module_under(mGeneratorMethods, "Array"); - rb_define_method(mArray, "to_json", mArray_to_json, -1); - - VALUE mInteger = rb_define_module_under(mGeneratorMethods, "Integer"); - rb_define_method(mInteger, "to_json", mInteger_to_json, -1); - - VALUE mFloat = rb_define_module_under(mGeneratorMethods, "Float"); - rb_define_method(mFloat, "to_json", mFloat_to_json, -1); - - VALUE mString = rb_define_module_under(mGeneratorMethods, "String"); - rb_define_method(mString, "to_json", mString_to_json, -1); - - VALUE mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass"); - rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1); - - VALUE mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass"); - rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1); - - VALUE mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass"); - rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1); + rb_define_singleton_method(cState, "_generate_no_fallback", cState_m_generate_no_fallback, 3); rb_global_variable(&Encoding_UTF_8); Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8")); diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 1a33c41030e2d8..fe2dd52262e0e4 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -156,15 +156,17 @@ def parser=(parser) # :nodoc: def generator=(generator) # :nodoc: old, $VERBOSE = $VERBOSE, nil @generator = generator - generator_methods = generator::GeneratorMethods - for const in generator_methods.constants - klass = const_get(const) - modul = generator_methods.const_get(const) - klass.class_eval do - instance_methods(false).each do |m| - m.to_s == 'to_json' and remove_method m + if generator.const_defined?(:GeneratorMethods) + generator_methods = generator::GeneratorMethods + for const in generator_methods.constants + klass = const_get(const) + modul = generator_methods.const_get(const) + klass.class_eval do + instance_methods(false).each do |m| + m.to_s == 'to_json' and remove_method m + end + include modul end - include modul end end self.state = generator::State @@ -1096,6 +1098,30 @@ def load_file(path) load(File.read(path, encoding: Encoding::UTF_8)) end end + + module GeneratorMethods + # call-seq: to_json(*) + # + # Converts this object into a JSON string. + # If this object doesn't directly maps to a JSON native type, + # first convert it to a string (calling #to_s), then converts + # it to a JSON string, and returns the result. + # This is a fallback, if no special method #to_json was defined for some object. + def to_json(state = nil, *) + obj = case self + when nil, false, true, Integer, Float, Array, Hash + self + else + "#{self}" + end + + if state.nil? + JSON::State._generate_no_fallback(obj, nil, nil) + else + JSON::State.from_state(state)._generate_no_fallback(obj) + end + end + end end module ::Kernel @@ -1141,3 +1167,7 @@ def JSON(object, opts = nil) JSON[object, opts] end end + +class Object + include JSON::GeneratorMethods +end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 3dfd0623cd98bc..b6001e3fb0d6a4 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -42,12 +42,6 @@ def setup '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}' end - def test_index - assert_equal @json, JSON[@hash] - assert_equal @json, JSON[@hash_with_method_missing] - assert_equal @hash, JSON[@json] - end - def test_parser assert_match(/::Parser\z/, JSON.parser.name) end @@ -287,6 +281,12 @@ def test_JSON assert_equal @hash, JSON(@json) end + def test_index + assert_equal @json, JSON[@hash] + assert_equal @json, JSON[@hash_with_method_missing] + assert_equal @hash, JSON[@json] + end + def test_load_file test_load_shared(:load_file) end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index d5f6ac64afcf2c..9a8ee5157ed6bc 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -921,29 +921,6 @@ def test_valid_utf8_in_different_encoding assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string) end end - - def test_string_ext_included_calls_super - included = false - - Module.send(:alias_method, :included_orig, :included) - Module.send(:remove_method, :included) - Module.send(:define_method, :included) do |base| - included_orig(base) - included = true - end - - Class.new(String) do - include JSON::Ext::Generator::GeneratorMethods::String - end - - assert included - ensure - if Module.private_method_defined?(:included_orig) - Module.send(:remove_method, :included) if Module.method_defined?(:included) - Module.send(:alias_method, :included, :included_orig) - Module.send(:remove_method, :included_orig) - end - end end def test_nonutf8_encoding From 6531343242e28b179e6d3d25af3b7fb65c90b66b Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Wed, 4 Mar 2026 16:41:27 +0200 Subject: [PATCH 08/87] weakmap: return assigned value from #[]= and add regression test --- test/ruby/test_weakmap.rb | 7 +++++++ weakmap.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_weakmap.rb b/test/ruby/test_weakmap.rb index 4f5823ecf4350c..2f5c7473390d28 100644 --- a/test/ruby/test_weakmap.rb +++ b/test/ruby/test_weakmap.rb @@ -39,6 +39,13 @@ def test_aset_const assert_same(:foo, @wm[x]) end + def test_aset_returns_value + key = Object.new + value = Object.new + + assert_same(value, @wm.send(:[]=, key, value)) + end + def assert_weak_include(m, k, n = 100) if n > 0 return assert_weak_include(m, k, n-1) diff --git a/weakmap.c b/weakmap.c index 80ef29b4cce8bc..7cef1fd46a63a7 100644 --- a/weakmap.c +++ b/weakmap.c @@ -394,7 +394,7 @@ wmap_aset(VALUE self, VALUE key, VALUE val) RB_OBJ_WRITTEN(self, Qundef, key); RB_OBJ_WRITTEN(self, Qundef, val); - return Qnil; + return val; } /* Retrieves a weakly referenced object with the given key */ From 275e53e4524141290e4127f70914256772e19741 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 5 Mar 2026 12:38:46 +0100 Subject: [PATCH 09/87] [ruby/json] Fix `allow_blank` parsing option to only consider strings. Ref: https://github.com/ruby/json/pull/946 https://github.com/ruby/json/commit/6ccc102db6 --- ext/json/lib/json/common.rb | 2 +- test/json/json_common_interface_test.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index fe2dd52262e0e4..230bf08012e111 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -880,7 +880,7 @@ def load(source, proc = nil, options = nil) end end - if opts[:allow_blank] && (source.nil? || source.empty?) + if opts[:allow_blank] && (source.nil? || (String === source && source.empty?)) source = 'null' end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index b6001e3fb0d6a4..37568b556e3b42 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -150,6 +150,8 @@ def test_load_null assert_equal nil, JSON.load(nil, nil, :allow_blank => true) assert_raise(TypeError) { JSON.load(nil, nil, :allow_blank => false) } assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } + assert_raise(TypeError) { JSON.load([], nil, :allow_blank => true) } + assert_raise(TypeError) { JSON.load({}, nil, :allow_blank => true) } end def test_unsafe_load From 67d4396dc98d1fd29d4555ac3ce212368eda76de Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 19 Dec 2025 13:14:46 +0900 Subject: [PATCH 10/87] Refine `Array#pack` `r`/`R` directives * remove the temporary buffer object. * simplify the condition under which an extra byte is required for sign extension. * in the case of `R`, raise an error earlier before packing for the negative number. --- depend | 3 +++ pack.c | 45 ++++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/depend b/depend index 1392a36c3e9809..fbf6f7fd701a9d 100644 --- a/depend +++ b/depend @@ -10409,11 +10409,14 @@ pack.$(OBJEXT): $(CCAN_DIR)/str/str.h pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h pack.$(OBJEXT): $(top_srcdir)/internal/array.h pack.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +pack.$(OBJEXT): $(top_srcdir)/internal/bignum.h pack.$(OBJEXT): $(top_srcdir)/internal/bits.h pack.$(OBJEXT): $(top_srcdir)/internal/box.h pack.$(OBJEXT): $(top_srcdir)/internal/compilers.h +pack.$(OBJEXT): $(top_srcdir)/internal/fixnum.h pack.$(OBJEXT): $(top_srcdir)/internal/gc.h pack.$(OBJEXT): $(top_srcdir)/internal/imemo.h +pack.$(OBJEXT): $(top_srcdir)/internal/numeric.h pack.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h pack.$(OBJEXT): $(top_srcdir)/internal/serial.h pack.$(OBJEXT): $(top_srcdir)/internal/set_table.h diff --git a/pack.c b/pack.c index f956e686e396eb..b6d9063a07e799 100644 --- a/pack.c +++ b/pack.c @@ -19,6 +19,7 @@ #include "internal.h" #include "internal/array.h" #include "internal/bits.h" +#include "internal/numeric.h" #include "internal/string.h" #include "internal/symbol.h" #include "internal/variable.h" @@ -677,43 +678,41 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer) } while (len-- > 0) { - size_t numbytes; - int sign; + size_t numbytes, nlz_bits; + int sign, extra = 0; char *cp; + const long start = RSTRING_LEN(res); from = NEXTFROM; from = rb_to_int(from); - numbytes = rb_absint_numwords(from, 7, NULL); - if (numbytes == 0) - numbytes = 1; - VALUE buf = rb_str_new(NULL, numbytes); - - sign = rb_integer_pack(from, RSTRING_PTR(buf), RSTRING_LEN(buf), 1, 1, pack_flags); - - if (sign < 0 && type == 'R') { + if (type == 'R' && rb_int_negative_p(from)) { rb_raise(rb_eArgError, "can't encode negative numbers in ULEB128"); } - if (type == 'r') { - /* Check if we need an extra byte for sign extension */ - unsigned char last_byte = (unsigned char)RSTRING_PTR(buf)[numbytes - 1]; - if ((sign >= 0 && (last_byte & 0x40)) || /* positive but sign bit set */ - (sign < 0 && !(last_byte & 0x40))) { /* negative but sign bit clear */ - /* Need an extra byte */ - rb_str_resize(buf, numbytes + 1); - RSTRING_PTR(buf)[numbytes] = sign < 0 ? 0x7f : 0x00; - numbytes++; - } + numbytes = rb_absint_numwords(from, 7, &nlz_bits); + if (numbytes == 0) { + numbytes = 1; } + else if (nlz_bits == 0 && type == 'r') { + /* No leading zero bits, we need an extra byte for sign extension */ + extra = 1; + } + rb_str_modify_expand(res, numbytes + extra); + + cp = RSTRING_PTR(res) + start; + sign = rb_integer_pack(from, cp, numbytes, 1, 1, pack_flags); + + if (extra) { + /* Need an extra byte */ + cp[numbytes++] = sign < 0 ? 0x7f : 0x00; + } + rb_str_set_len(res, start + numbytes); - cp = RSTRING_PTR(buf); while (1 < numbytes) { *cp |= 0x80; cp++; numbytes--; } - - rb_str_buf_cat(res, RSTRING_PTR(buf), RSTRING_LEN(buf)); } } break; From 45250a4169dd0995e5560ca57ab0c5bf47d9d4ad Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 5 Mar 2026 10:34:21 -0500 Subject: [PATCH 11/87] [PRISM] Use arena allocator for nodes --- common.mk | 1 + depend | 63 +++- lib/prism/prism.gemspec | 2 + prism/defines.h | 12 + prism/extension.c | 41 +- prism/node.h | 29 +- prism/parser.h | 4 + prism/prism.c | 560 ++++++++++++---------------- prism/prism.h | 24 +- prism/templates/src/node.c.erb | 139 ++----- prism/templates/src/serialize.c.erb | 12 +- prism/util/pm_arena.c | 105 ++++++ prism/util/pm_arena.h | 89 +++++ prism/util/pm_constant_pool.c | 41 +- prism/util/pm_constant_pool.h | 18 +- prism_compile.c | 11 +- prism_compile.h | 3 + vm_eval.c | 3 +- 18 files changed, 631 insertions(+), 526 deletions(-) create mode 100644 prism/util/pm_arena.c create mode 100644 prism/util/pm_arena.h diff --git a/common.mk b/common.mk index ec81324549766b..963af3987b55ef 100644 --- a/common.mk +++ b/common.mk @@ -101,6 +101,7 @@ PRISM_FILES = prism/api_node.$(OBJEXT) \ prism/serialize.$(OBJEXT) \ prism/static_literals.$(OBJEXT) \ prism/token_type.$(OBJEXT) \ + prism/util/pm_arena.$(OBJEXT) \ prism/util/pm_buffer.$(OBJEXT) \ prism/util/pm_char.$(OBJEXT) \ prism/util/pm_constant_pool.$(OBJEXT) \ diff --git a/depend b/depend index fbf6f7fd701a9d..4d204c82f23a92 100644 --- a/depend +++ b/depend @@ -316,6 +316,7 @@ ast.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h ast.$(OBJEXT): $(top_srcdir)/prism/prism.h ast.$(OBJEXT): $(top_srcdir)/prism/regexp.h ast.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h ast.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -770,6 +771,7 @@ box.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h box.$(OBJEXT): $(top_srcdir)/prism/prism.h box.$(OBJEXT): $(top_srcdir)/prism/regexp.h box.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +box.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h box.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h box.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h box.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -1011,6 +1013,7 @@ builtin.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h builtin.$(OBJEXT): $(top_srcdir)/prism/prism.h builtin.$(OBJEXT): $(top_srcdir)/prism/regexp.h builtin.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h builtin.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -1662,6 +1665,7 @@ compile.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h compile.$(OBJEXT): $(top_srcdir)/prism/prism.h compile.$(OBJEXT): $(top_srcdir)/prism/regexp.h compile.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h compile.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -2342,6 +2346,7 @@ cont.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h cont.$(OBJEXT): $(top_srcdir)/prism/prism.h cont.$(OBJEXT): $(top_srcdir)/prism/regexp.h cont.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h cont.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -5356,6 +5361,7 @@ eval.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h eval.$(OBJEXT): $(top_srcdir)/prism/prism.h eval.$(OBJEXT): $(top_srcdir)/prism/regexp.h eval.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h eval.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -5847,6 +5853,7 @@ gc.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h gc.$(OBJEXT): $(top_srcdir)/prism/prism.h gc.$(OBJEXT): $(top_srcdir)/prism/regexp.h gc.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h gc.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -6111,6 +6118,7 @@ goruby.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h goruby.$(OBJEXT): $(top_srcdir)/prism/prism.h goruby.$(OBJEXT): $(top_srcdir)/prism/regexp.h goruby.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h goruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -6357,6 +6365,7 @@ hash.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h hash.$(OBJEXT): $(top_srcdir)/prism/prism.h hash.$(OBJEXT): $(top_srcdir)/prism/regexp.h hash.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h hash.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -7440,6 +7449,7 @@ iseq.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h iseq.$(OBJEXT): $(top_srcdir)/prism/prism.h iseq.$(OBJEXT): $(top_srcdir)/prism/regexp.h iseq.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h iseq.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -7689,6 +7699,7 @@ jit.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h jit.$(OBJEXT): $(top_srcdir)/prism/prism.h jit.$(OBJEXT): $(top_srcdir)/prism/regexp.h jit.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +jit.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h jit.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h jit.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h jit.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -7809,7 +7820,6 @@ jit.$(OBJEXT): {$(VPATH)}internal/error.h jit.$(OBJEXT): {$(VPATH)}internal/eval.h jit.$(OBJEXT): {$(VPATH)}internal/event.h jit.$(OBJEXT): {$(VPATH)}internal/fl_type.h -jit.$(OBJEXT): {$(VPATH)}internal/core/rtypeddata.h jit.$(OBJEXT): {$(VPATH)}internal/gc.h jit.$(OBJEXT): {$(VPATH)}internal/glob.h jit.$(OBJEXT): {$(VPATH)}internal/globals.h @@ -7945,6 +7955,7 @@ load.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h load.$(OBJEXT): $(top_srcdir)/prism/prism.h load.$(OBJEXT): $(top_srcdir)/prism/regexp.h load.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +load.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h load.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -9302,6 +9313,7 @@ miniinit.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h miniinit.$(OBJEXT): $(top_srcdir)/prism/prism.h miniinit.$(OBJEXT): $(top_srcdir)/prism/regexp.h miniinit.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h miniinit.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11111,6 +11123,7 @@ prism/api_node.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/api_node.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11295,6 +11308,7 @@ prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/ast.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/diagnostic.c prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h +prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/diagnostic.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11370,6 +11384,7 @@ prism/extension.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/extension.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11561,6 +11576,7 @@ prism/node.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/node.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/node.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/node.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/node.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11632,6 +11648,7 @@ prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/prettyprint.c prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/prettyprint.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11676,6 +11693,7 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/prism.c prism/prism.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11719,6 +11737,7 @@ prism/regexp.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/regexp.c prism/regexp.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/regexp.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11764,6 +11783,7 @@ prism/serialize.$(OBJEXT): $(top_srcdir)/prism/prism.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/serialize.c prism/serialize.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/serialize.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11807,6 +11827,7 @@ prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/options.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/static_literals.c prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/static_literals.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11842,6 +11863,7 @@ prism/static_literals.$(OBJEXT): {$(VPATH)}prism_xallocator.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/ast.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/token_type.c +prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/token_type.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -11872,6 +11894,33 @@ prism/token_type.$(OBJEXT): {$(VPATH)}internal/has/feature.h prism/token_type.$(OBJEXT): {$(VPATH)}internal/has/warning.h prism/token_type.$(OBJEXT): {$(VPATH)}internal/xmalloc.h prism/token_type.$(OBJEXT): {$(VPATH)}prism_xallocator.h +prism/util/pm_arena.$(OBJEXT): $(top_srcdir)/prism/defines.h +prism/util/pm_arena.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.c +prism/util/pm_arena.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}config.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/attr/alloc_size.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/attr/nodiscard.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/attr/noexcept.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/attr/restrict.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/attr/returns_nonnull.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/apple.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/clang.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/gcc.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/intel.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/msvc.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_is/sunpro.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/compiler_since.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/config.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/dllexport.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/attribute.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/c_attribute.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/cpp_attribute.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/extension.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/feature.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/has/warning.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +prism/util/pm_arena.$(OBJEXT): {$(VPATH)}prism_xallocator.h prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.c prism/util/pm_buffer.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h @@ -11930,6 +11979,7 @@ prism/util/pm_char.$(OBJEXT): {$(VPATH)}internal/has/warning.h prism/util/pm_char.$(OBJEXT): {$(VPATH)}internal/xmalloc.h prism/util/pm_char.$(OBJEXT): {$(VPATH)}prism_xallocator.h prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/defines.h +prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.c prism/util/pm_constant_pool.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h prism/util/pm_constant_pool.$(OBJEXT): {$(VPATH)}config.h @@ -12136,6 +12186,7 @@ prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/options.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism/util/pm_strpbrk.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -12185,6 +12236,7 @@ prism_init.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h prism_init.$(OBJEXT): $(top_srcdir)/prism/prism.h prism_init.$(OBJEXT): $(top_srcdir)/prism/regexp.h prism_init.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h prism_init.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -12406,6 +12458,7 @@ proc.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h proc.$(OBJEXT): $(top_srcdir)/prism/prism.h proc.$(OBJEXT): $(top_srcdir)/prism/regexp.h proc.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h proc.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -14975,6 +15028,7 @@ ruby.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h ruby.$(OBJEXT): $(top_srcdir)/prism/prism.h ruby.$(OBJEXT): $(top_srcdir)/prism/regexp.h ruby.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -17726,6 +17780,7 @@ thread.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h thread.$(OBJEXT): $(top_srcdir)/prism/prism.h thread.$(OBJEXT): $(top_srcdir)/prism/regexp.h thread.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h thread.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -19041,6 +19096,7 @@ vm.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h vm.$(OBJEXT): $(top_srcdir)/prism/prism.h vm.$(OBJEXT): $(top_srcdir)/prism/regexp.h vm.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h vm.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -19307,6 +19363,7 @@ vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/prism.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/regexp.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h vm_backtrace.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -19542,6 +19599,7 @@ vm_dump.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/prism.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/regexp.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h vm_dump.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -19992,6 +20050,7 @@ vm_trace.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/prism.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/regexp.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h vm_trace.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -20443,6 +20502,7 @@ yjit.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h yjit.$(OBJEXT): $(top_srcdir)/prism/prism.h yjit.$(OBJEXT): $(top_srcdir)/prism/regexp.h yjit.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h yjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h @@ -20699,6 +20759,7 @@ zjit.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h zjit.$(OBJEXT): $(top_srcdir)/prism/prism.h zjit.$(OBJEXT): $(top_srcdir)/prism/regexp.h zjit.$(OBJEXT): $(top_srcdir)/prism/static_literals.h +zjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_arena.h zjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_buffer.h zjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_char.h zjit.$(OBJEXT): $(top_srcdir)/prism/util/pm_constant_pool.h diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index ca2db717baffc2..74d54127319aca 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -58,6 +58,7 @@ Gem::Specification.new do |spec| "include/prism/prettyprint.h", "include/prism/regexp.h", "include/prism/static_literals.h", + "include/prism/util/pm_arena.h", "include/prism/util/pm_buffer.h", "include/prism/util/pm_char.h", "include/prism/util/pm_constant_pool.h", @@ -167,6 +168,7 @@ Gem::Specification.new do |spec| "src/serialize.c", "src/static_literals.c", "src/token_type.c", + "src/util/pm_arena.c", "src/util/pm_buffer.c", "src/util/pm_char.c", "src/util/pm_constant_pool.c", diff --git a/prism/defines.h b/prism/defines.h index 1c4e5fa053dabf..c48a600b21c370 100644 --- a/prism/defines.h +++ b/prism/defines.h @@ -278,6 +278,18 @@ #define PRISM_FALLTHROUGH #endif +/** + * A macro for defining a flexible array member. C99 supports `data[]`, GCC + * supports `data[0]` as an extension, and older compilers require `data[1]`. + */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) + #define PM_FLEX_ARY_LEN /* data[] */ +#elif defined(__GNUC__) && !defined(__STRICT_ANSI__) + #define PM_FLEX_ARY_LEN 0 /* data[0] */ +#else + #define PM_FLEX_ARY_LEN 1 /* data[1] */ +#endif + /** * We need to align nodes in the AST to a pointer boundary so that it can be * safely cast to different node types. Use PRISM_ALIGNAS/PRISM_ALIGNOF to diff --git a/prism/extension.c b/prism/extension.c index fcbc1e6c244fa2..7c90e488456588 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -374,16 +374,17 @@ dump_input(pm_string_t *input, const pm_options_t *options) { rb_raise(rb_eNoMemError, "failed to allocate memory"); } + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); pm_node_t *node = pm_parse(&parser); pm_serialize(&parser, node, &buffer); VALUE result = rb_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer)); - pm_node_destroy(&parser, node); pm_buffer_free(&buffer); pm_parser_free(&parser); + pm_arena_free(&arena); return result; } @@ -736,8 +737,9 @@ parse_lex_encoding_changed_callback(pm_parser_t *parser) { */ static VALUE parse_lex_input(pm_string_t *input, const pm_options_t *options, bool return_nodes) { + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); pm_parser_register_encoding_changed_callback(&parser, parse_lex_encoding_changed_callback); VALUE source_string = rb_str_new((const char *) pm_string_source(input), pm_string_length(input)); @@ -789,8 +791,8 @@ parse_lex_input(pm_string_t *input, const pm_options_t *options, bool return_nod result = parse_result_create(rb_cPrismLexResult, &parser, parse_lex_data.tokens, parse_lex_data.encoding, source, options->freeze); } - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); return result; } @@ -848,8 +850,9 @@ lex_file(int argc, VALUE *argv, VALUE self) { */ static VALUE parse_input(pm_string_t *input, const pm_options_t *options) { + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); pm_node_t *node = pm_parse(&parser); rb_encoding *encoding = rb_enc_find(parser.encoding->name); @@ -862,8 +865,8 @@ parse_input(pm_string_t *input, const pm_options_t *options) { rb_obj_freeze(source); } - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); return result; } @@ -965,12 +968,13 @@ parse_file(int argc, VALUE *argv, VALUE self) { */ static void profile_input(pm_string_t *input, const pm_options_t *options) { + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); - pm_node_t *node = pm_parse(&parser); - pm_node_destroy(&parser, node); + pm_parse(&parser); pm_parser_free(&parser); + pm_arena_free(&arena); } /** @@ -1065,19 +1069,20 @@ parse_stream(int argc, VALUE *argv, VALUE self) { pm_options_t options = { 0 }; extract_options(&options, Qnil, keywords); + pm_arena_t arena = { 0 }; pm_parser_t parser; pm_buffer_t buffer; - pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, parse_stream_eof, &options); + pm_node_t *node = pm_parse_stream(&arena, &parser, &buffer, (void *) stream, parse_stream_fgets, parse_stream_eof, &options); rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE source = pm_source_new(&parser, encoding, options.freeze); VALUE value = pm_ast_new(&parser, node, encoding, source, options.freeze); VALUE result = parse_result_create(rb_cPrismParseResult, &parser, value, encoding, source, options.freeze); - pm_node_destroy(&parser, node); pm_buffer_free(&buffer); pm_parser_free(&parser); + pm_arena_free(&arena); return result; } @@ -1087,17 +1092,18 @@ parse_stream(int argc, VALUE *argv, VALUE self) { */ static VALUE parse_input_comments(pm_string_t *input, const pm_options_t *options) { + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); - pm_node_t *node = pm_parse(&parser); + pm_parse(&parser); rb_encoding *encoding = rb_enc_find(parser.encoding->name); VALUE source = pm_source_new(&parser, encoding, options->freeze); VALUE comments = parser_comments(&parser, source, options->freeze); - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); return comments; } @@ -1209,14 +1215,15 @@ parse_lex_file(int argc, VALUE *argv, VALUE self) { */ static VALUE parse_input_success_p(pm_string_t *input, const pm_options_t *options) { + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + pm_parser_init(&arena, &parser, pm_string_source(input), pm_string_length(input), options); - pm_node_t *node = pm_parse(&parser); - pm_node_destroy(&parser, node); + pm_parse(&parser); VALUE result = parser.error_list.size == 0 ? Qtrue : Qfalse; pm_parser_free(&parser); + pm_arena_free(&arena); return result; } diff --git a/prism/node.h b/prism/node.h index e8686a327c7c95..253f89005564fa 100644 --- a/prism/node.h +++ b/prism/node.h @@ -20,41 +20,29 @@ /** * Append a new node onto the end of the node list. * + * @param arena The arena to allocate from. * @param list The list to append to. * @param node The node to append. */ -void pm_node_list_append(pm_node_list_t *list, pm_node_t *node); +void pm_node_list_append(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node); /** * Prepend a new node onto the beginning of the node list. * + * @param arena The arena to allocate from. * @param list The list to prepend to. * @param node The node to prepend. */ -void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node); +void pm_node_list_prepend(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node); /** * Concatenate the given node list onto the end of the other node list. * + * @param arena The arena to allocate from. * @param list The list to concatenate onto. * @param other The list to concatenate. */ -void pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other); - -/** - * Free the internal memory associated with the given node list. - * - * @param list The list to free. - */ -void pm_node_list_free(pm_node_list_t *list); - -/** - * Deallocate a node and all of its children. - * - * @param parser The parser that owns the node. - * @param node The node to deallocate. - */ -PRISM_EXPORTED_FUNCTION void pm_node_destroy(pm_parser_t *parser, struct pm_node *node); +void pm_node_list_concat(pm_arena_t *arena, pm_node_list_t *list, pm_node_list_t *other); /** * Returns a string representation of the given node type. @@ -93,9 +81,10 @@ PRISM_EXPORTED_FUNCTION const char * pm_node_type_to_str(pm_node_type_t node_typ * const char *source = "1 + 2; 3 + 4"; * size_t size = strlen(source); * + * pm_arena_t arena = { 0 }; * pm_parser_t parser; * pm_options_t options = { 0 }; - * pm_parser_init(&parser, (const uint8_t *) source, size, &options); + * pm_parser_init(&arena, &parser, (const uint8_t *) source, size, &options); * * size_t indent = 0; * pm_node_t *node = pm_parse(&parser); @@ -103,8 +92,8 @@ PRISM_EXPORTED_FUNCTION const char * pm_node_type_to_str(pm_node_type_t node_typ * size_t *data = &indent; * pm_visit_node(node, visit, data); * - * pm_node_destroy(&parser, node); * pm_parser_free(&parser); + * pm_arena_free(&arena); * return EXIT_SUCCESS; * } * ``` diff --git a/prism/parser.h b/prism/parser.h index b4e3038439d0e3..c76fba58cfd63f 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -11,6 +11,7 @@ #include "prism/encoding.h" #include "prism/options.h" #include "prism/static_literals.h" +#include "prism/util/pm_arena.h" #include "prism/util/pm_constant_pool.h" #include "prism/util/pm_list.h" #include "prism/util/pm_line_offset_list.h" @@ -635,6 +636,9 @@ typedef uint32_t pm_state_stack_t; * it's considering. */ struct pm_parser { + /** The arena used for all AST-lifetime allocations. Caller-owned. */ + pm_arena_t *arena; + /** * The next node identifier that will be assigned. This is a unique * identifier used to track nodes such that the syntax tree can be dropped diff --git a/prism/prism.c b/prism/prism.c index a131f3791277b2..e10a7710af3de6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -983,7 +983,7 @@ pm_locals_reads(pm_locals_t *locals, pm_constant_id_t name) { */ static void pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool toplevel) { - pm_constant_id_list_init_capacity(list, locals->size); + pm_constant_id_list_init_capacity(parser->arena, list, locals->size); // If we're still below the threshold for switching to a hash, then we only // need to loop over the locals until we hit the size because the locals are @@ -2005,20 +2005,20 @@ static size_t pm_statements_node_body_length(pm_statements_node_t *node); /** - * This function is here to allow us a place to extend in the future when we - * implement our own arena allocation. + * Move an integer's values array into the arena. If the integer has heap- + * allocated values, copy them to the arena and free the original. */ -static inline void * -pm_node_alloc(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, size_t size) { - void *memory = xcalloc(1, size); - if (memory == NULL) { - fprintf(stderr, "Failed to allocate %d bytes\n", (int) size); - abort(); +static inline void +pm_integer_arena_move(pm_arena_t *arena, pm_integer_t *integer) { + if (integer->values != NULL) { + size_t byte_size = integer->length * sizeof(uint32_t); + uint32_t *old_values = integer->values; + integer->values = (uint32_t *) pm_arena_memdup(arena, old_values, byte_size, PRISM_ALIGNOF(uint32_t)); + xfree(old_values); } - return memory; } -#define PM_NODE_ALLOC(parser_, type_) (type_ *) pm_node_alloc(parser_, sizeof(type_)) +#define PM_NODE_ALLOC(parser_, type_) (type_ *) pm_arena_zalloc((parser_)->arena, sizeof(type_), PRISM_ALIGNOF(type_)) #define PM_NODE_INIT(parser_, type_, flags_, location_) (pm_node_t) { \ .type = (type_), \ .flags = (flags_), \ @@ -2139,7 +2139,7 @@ pm_arguments_node_size(pm_arguments_node_t *node) { * Append an argument to an arguments node. */ static void -pm_arguments_node_arguments_append(pm_arguments_node_t *node, pm_node_t *argument) { +pm_arguments_node_arguments_append(pm_arena_t *arena, pm_arguments_node_t *node, pm_node_t *argument) { if (pm_arguments_node_size(node) == 0) { PM_NODE_START_SET_NODE(node, argument); } @@ -2148,7 +2148,7 @@ pm_arguments_node_arguments_append(pm_arguments_node_t *node, pm_node_t *argumen PM_NODE_LENGTH_SET_NODE(node, argument); } - pm_node_list_append(&node->arguments, argument); + pm_node_list_append(arena, &node->arguments, argument); if (PM_NODE_TYPE_P(argument, PM_SPLAT_NODE)) { if (PM_NODE_FLAG_P(node, PM_ARGUMENTS_NODE_FLAGS_CONTAINS_SPLAT)) { @@ -2189,12 +2189,12 @@ pm_array_node_create(pm_parser_t *parser, const pm_token_t *opening) { * Append an argument to an array node. */ static inline void -pm_array_node_elements_append(pm_array_node_t *node, pm_node_t *element) { +pm_array_node_elements_append(pm_arena_t *arena, pm_array_node_t *node, pm_node_t *element) { if (!node->elements.size && !node->opening_loc.length) { PM_NODE_START_SET_NODE(node, element); } - pm_node_list_append(&node->elements, element); + pm_node_list_append(arena, &node->elements, element); PM_NODE_LENGTH_SET_NODE(node, element); // If the element is not a static literal, then the array is not a static @@ -2246,9 +2246,9 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node node->rest = child; found_rest = true; } else if (found_rest) { - pm_node_list_append(&node->posts, child); + pm_node_list_append(parser->arena, &node->posts, child); } else { - pm_node_list_append(&node->requireds, child); + pm_node_list_append(parser->arena, &node->requireds, child); } } @@ -2318,8 +2318,8 @@ pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *openin } static inline void -pm_array_pattern_node_requireds_append(pm_array_pattern_node_t *node, pm_node_t *inner) { - pm_node_list_append(&node->requireds, inner); +pm_array_pattern_node_requireds_append(pm_arena_t *arena, pm_array_pattern_node_t *node, pm_node_t *inner) { + pm_node_list_append(arena, &node->requireds, inner); } /** @@ -2583,8 +2583,8 @@ pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) * Append a new block-local variable to a BlockParametersNode node. */ static void -pm_block_parameters_node_append_local(pm_block_parameters_node_t *node, const pm_block_local_variable_node_t *local) { - pm_node_list_append(&node->locals, UP(local)); +pm_block_parameters_node_append_local(pm_arena_t *arena, pm_block_parameters_node_t *node, const pm_block_local_variable_node_t *local) { + pm_node_list_append(arena, &node->locals, UP(local)); if (PM_NODE_LENGTH(node) == 0) { PM_NODE_START_SET_NODE(node, local); @@ -2704,7 +2704,7 @@ pm_call_node_binary_create(pm_parser_t *parser, pm_node_t *receiver, pm_token_t node->message_loc = TOK2LOC(parser, operator); pm_arguments_node_t *arguments = pm_arguments_node_create(parser); - pm_arguments_node_arguments_append(arguments, argument); + pm_arguments_node_arguments_append(parser->arena, arguments, argument); node->arguments = arguments; node->name = pm_parser_constant_id_token(parser, operator); @@ -2953,10 +2953,8 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3007,10 +3005,8 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons .value = value }; - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3037,10 +3033,8 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3068,10 +3062,8 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, .value = value }; - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3098,10 +3090,8 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3129,10 +3119,8 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const .value = value }; - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3161,10 +3149,8 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { node->call_operator_loc = target->base.location; } - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3189,10 +3175,8 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { .block = (pm_block_argument_node_t *) target->block, }; - // Here we're going to free the target, since it is no longer necessary. - // However, we don't want to call `pm_node_destroy` because we want to keep - // around all of its children since we just reused them. - xfree_sized(target, sizeof(pm_call_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -3237,10 +3221,10 @@ pm_case_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, pm_node * Append a new condition to a CaseNode node. */ static void -pm_case_node_condition_append(pm_case_node_t *node, pm_node_t *condition) { +pm_case_node_condition_append(pm_arena_t *arena, pm_case_node_t *node, pm_node_t *condition) { assert(PM_NODE_TYPE_P(condition, PM_WHEN_NODE)); - pm_node_list_append(&node->conditions, condition); + pm_node_list_append(arena, &node->conditions, condition); PM_NODE_LENGTH_SET_NODE(node, condition); } @@ -3285,10 +3269,10 @@ pm_case_match_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, p * Append a new condition to a CaseMatchNode node. */ static void -pm_case_match_node_condition_append(pm_case_match_node_t *node, pm_node_t *condition) { +pm_case_match_node_condition_append(pm_arena_t *arena, pm_case_match_node_t *node, pm_node_t *condition) { assert(PM_NODE_TYPE_P(condition, PM_IN_NODE)); - pm_node_list_append(&node->conditions, condition); + pm_node_list_append(arena, &node->conditions, condition); PM_NODE_LENGTH_SET_NODE(node, condition); } @@ -3856,7 +3840,7 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { // much more efficient, as we could instead resize the node list to only point // to 1...-1. for (size_t index = 1; index < nodes->size - 1; index++) { - pm_node_list_append(&node->requireds, nodes->nodes[index]); + pm_node_list_append(parser->arena, &node->requireds, nodes->nodes[index]); } return node; @@ -4021,6 +4005,8 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { xfree_sized(digits, length); pm_integers_reduce(&node->numerator, &node->denominator); + pm_integer_arena_move(parser->arena, &node->numerator); + pm_integer_arena_move(parser->arena, &node->denominator); return node; } @@ -4180,7 +4166,7 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme .closing_loc = { 0 } }; - pm_node_list_concat(&node->elements, elements); + pm_node_list_concat(parser->arena, &node->elements, elements); return node; } @@ -4350,8 +4336,8 @@ pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) { * Append a new element to a hash node. */ static inline void -pm_hash_node_elements_append(pm_hash_node_t *hash, pm_node_t *element) { - pm_node_list_append(&hash->elements, element); +pm_hash_node_elements_append(pm_arena_t *arena, pm_hash_node_t *hash, pm_node_t *element) { + pm_node_list_append(arena, &hash->elements, element); bool static_literal = PM_NODE_TYPE_P(element, PM_ASSOC_NODE); if (static_literal) { @@ -4534,6 +4520,7 @@ pm_integer_node_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token } pm_integer_parse(&node->value, integer_base, token->start, token->end); + pm_integer_arena_move(parser->arena, &node->value); return node; } @@ -4583,6 +4570,7 @@ pm_integer_node_rational_create(pm_parser_t *parser, pm_node_flags_t base, const } pm_integer_parse(&node->numerator, integer_base, token->start, token->end - 1); + pm_integer_arena_move(parser->arena, &node->numerator); return node; } @@ -4736,7 +4724,7 @@ pm_instance_variable_write_node_create(pm_parser_t *parser, pm_instance_variable * literals. */ static void -pm_interpolated_node_append(pm_node_t *node, pm_node_list_t *parts, pm_node_t *part) { +pm_interpolated_node_append(pm_arena_t *arena, pm_node_t *node, pm_node_list_t *parts, pm_node_t *part) { switch (PM_NODE_TYPE(part)) { case PM_STRING_NODE: pm_node_flag_set(part, PM_NODE_FLAG_STATIC_LITERAL | PM_STRING_FLAGS_FROZEN); @@ -4771,7 +4759,7 @@ pm_interpolated_node_append(pm_node_t *node, pm_node_list_t *parts, pm_node_t *p break; } - pm_node_list_append(parts, part); + pm_node_list_append(arena, parts, part); } /** @@ -4792,7 +4780,7 @@ pm_interpolated_regular_expression_node_create(pm_parser_t *parser, const pm_tok } static inline void -pm_interpolated_regular_expression_node_append(pm_interpolated_regular_expression_node_t *node, pm_node_t *part) { +pm_interpolated_regular_expression_node_append(pm_arena_t *arena, pm_interpolated_regular_expression_node_t *node, pm_node_t *part) { if (PM_NODE_START(node) > PM_NODE_START(part)) { PM_NODE_START_SET_NODE(node, part); } @@ -4800,7 +4788,7 @@ pm_interpolated_regular_expression_node_append(pm_interpolated_regular_expressio PM_NODE_LENGTH_SET_NODE(node, part); } - pm_interpolated_node_append(UP(node), &node->parts, part); + pm_interpolated_node_append(arena, UP(node), &node->parts, part); } static inline void @@ -4834,7 +4822,7 @@ pm_interpolated_regular_expression_node_closing_set(pm_parser_t *parser, pm_inte * which could potentially use a chilled string otherwise. */ static inline void -pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_t *part) { +pm_interpolated_string_node_append(pm_arena_t *arena, pm_interpolated_string_node_t *node, pm_node_t *part) { #define CLEAR_FLAGS(node) \ node->base.flags = (pm_node_flags_t) (FL(node) & ~(PM_NODE_FLAG_STATIC_LITERAL | PM_INTERPOLATED_STRING_NODE_FLAGS_FROZEN | PM_INTERPOLATED_STRING_NODE_FLAGS_MUTABLE)) @@ -4919,7 +4907,7 @@ pm_interpolated_string_node_append(pm_interpolated_string_node_t *node, pm_node_ break; } - pm_node_list_append(&node->parts, part); + pm_node_list_append(arena, &node->parts, part); #undef CLEAR_FLAGS #undef MUTABLE_FLAGS @@ -4955,7 +4943,7 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin if (parts != NULL) { pm_node_t *part; PM_NODE_LIST_FOREACH(parts, index, part) { - pm_interpolated_string_node_append(node, part); + pm_interpolated_string_node_append(parser->arena, node, part); } } @@ -4972,12 +4960,12 @@ pm_interpolated_string_node_closing_set(const pm_parser_t *parser, pm_interpolat } static void -pm_interpolated_symbol_node_append(pm_interpolated_symbol_node_t *node, pm_node_t *part) { +pm_interpolated_symbol_node_append(pm_arena_t *arena, pm_interpolated_symbol_node_t *node, pm_node_t *part) { if (node->parts.size == 0 && node->opening_loc.length == 0) { PM_NODE_START_SET_NODE(node, part); } - pm_interpolated_node_append(UP(node), &node->parts, part); + pm_interpolated_node_append(arena, UP(node), &node->parts, part); if (PM_NODE_END(part) > PM_NODE_END(node)) { PM_NODE_LENGTH_SET_NODE(node, part); @@ -5010,7 +4998,7 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin if (parts != NULL) { pm_node_t *part; PM_NODE_LIST_FOREACH(parts, index, part) { - pm_interpolated_symbol_node_append(node, part); + pm_interpolated_symbol_node_append(parser->arena, node, part); } } @@ -5035,8 +5023,8 @@ pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *openi } static inline void -pm_interpolated_xstring_node_append(pm_interpolated_x_string_node_t *node, pm_node_t *part) { - pm_interpolated_node_append(UP(node), &node->parts, part); +pm_interpolated_xstring_node_append(pm_arena_t *arena, pm_interpolated_x_string_node_t *node, pm_node_t *part) { + pm_interpolated_node_append(arena, UP(node), &node->parts, part); PM_NODE_LENGTH_SET_NODE(node, part); } @@ -5093,14 +5081,14 @@ pm_keyword_hash_node_create(pm_parser_t *parser) { * Append an element to a KeywordHashNode node. */ static void -pm_keyword_hash_node_elements_append(pm_keyword_hash_node_t *hash, pm_node_t *element) { +pm_keyword_hash_node_elements_append(pm_arena_t *arena, pm_keyword_hash_node_t *hash, pm_node_t *element) { // If the element being added is not an AssocNode or does not have a symbol // key, then we want to turn the SYMBOL_KEYS flag off. if (!PM_NODE_TYPE_P(element, PM_ASSOC_NODE) || !PM_NODE_TYPE_P(((pm_assoc_node_t *) element)->key, PM_SYMBOL_NODE)) { pm_node_flag_unset(UP(hash), PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS); } - pm_node_list_append(&hash->elements, element); + pm_node_list_append(arena, &hash->elements, element); if (PM_NODE_LENGTH(hash) == 0) { PM_NODE_START_SET_NODE(hash, element); } @@ -5458,19 +5446,19 @@ pm_multi_target_node_targets_append(pm_parser_t *parser, pm_multi_target_node_t node->rest = target; } else { pm_parser_err_node(parser, target, PM_ERR_MULTI_ASSIGN_MULTI_SPLATS); - pm_node_list_append(&node->rights, target); + pm_node_list_append(parser->arena, &node->rights, target); } } else if (PM_NODE_TYPE_P(target, PM_IMPLICIT_REST_NODE)) { if (node->rest == NULL) { node->rest = target; } else { PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, &parser->current, PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST); - pm_node_list_append(&node->rights, target); + pm_node_list_append(parser->arena, &node->rights, target); } } else if (node->rest == NULL) { - pm_node_list_append(&node->lefts, target); + pm_node_list_append(parser->arena, &node->lefts, target); } else { - pm_node_list_append(&node->rights, target); + pm_node_list_append(parser->arena, &node->rights, target); } if (PM_NODE_LENGTH(node) == 0 || (PM_NODE_START(node) > PM_NODE_START(target))) { @@ -5520,9 +5508,8 @@ pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, .value = value }; - // Explicitly do not call pm_node_destroy here because we want to keep - // around all of the information within the MultiWriteNode node. - xfree_sized(target, sizeof(pm_multi_target_node_t)); + // The target is no longer necessary because we've reused its children. + // It is arena-allocated so no explicit free is needed. return node; } @@ -5751,27 +5738,27 @@ pm_parameters_node_location_set(pm_parameters_node_t *params, pm_node_t *param) * Append a required parameter to a ParametersNode node. */ static void -pm_parameters_node_requireds_append(pm_parameters_node_t *params, pm_node_t *param) { +pm_parameters_node_requireds_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) { pm_parameters_node_location_set(params, param); - pm_node_list_append(¶ms->requireds, param); + pm_node_list_append(arena, ¶ms->requireds, param); } /** * Append an optional parameter to a ParametersNode node. */ static void -pm_parameters_node_optionals_append(pm_parameters_node_t *params, pm_optional_parameter_node_t *param) { +pm_parameters_node_optionals_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_optional_parameter_node_t *param) { pm_parameters_node_location_set(params, UP(param)); - pm_node_list_append(¶ms->optionals, UP(param)); + pm_node_list_append(arena, ¶ms->optionals, UP(param)); } /** * Append a post optional arguments parameter to a ParametersNode node. */ static void -pm_parameters_node_posts_append(pm_parameters_node_t *params, pm_node_t *param) { +pm_parameters_node_posts_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) { pm_parameters_node_location_set(params, param); - pm_node_list_append(¶ms->posts, param); + pm_node_list_append(arena, ¶ms->posts, param); } /** @@ -5787,9 +5774,9 @@ pm_parameters_node_rest_set(pm_parameters_node_t *params, pm_node_t *param) { * Append a keyword parameter to a ParametersNode node. */ static void -pm_parameters_node_keywords_append(pm_parameters_node_t *params, pm_node_t *param) { +pm_parameters_node_keywords_append(pm_arena_t *arena, pm_parameters_node_t *params, pm_node_t *param) { pm_parameters_node_location_set(params, param); - pm_node_list_append(¶ms->keywords, param); + pm_node_list_append(arena, ¶ms->keywords, param); } /** @@ -6088,8 +6075,8 @@ pm_rescue_node_subsequent_set(pm_rescue_node_t *node, pm_rescue_node_t *subseque * Append an exception node to a rescue node, and update the location. */ static void -pm_rescue_node_exceptions_append(pm_rescue_node_t *node, pm_node_t *exception) { - pm_node_list_append(&node->exceptions, exception); +pm_rescue_node_exceptions_append(pm_arena_t *arena, pm_rescue_node_t *node, pm_node_t *exception) { + pm_node_list_append(arena, &node->exceptions, exception); PM_NODE_LENGTH_SET_NODE(node, exception); } @@ -6325,7 +6312,7 @@ pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, } } - pm_node_list_append(&node->body, statement); + pm_node_list_append(parser->arena, &node->body, statement); if (newline) pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE); } @@ -6333,9 +6320,9 @@ pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, * Prepend a new node to the given StatementsNode node's body. */ static void -pm_statements_node_body_prepend(pm_statements_node_t *node, pm_node_t *statement) { +pm_statements_node_body_prepend(pm_arena_t *arena, pm_statements_node_t *node, pm_node_t *statement) { pm_statements_node_body_update(node, statement); - pm_node_list_prepend(&node->body, statement); + pm_node_list_prepend(arena, &node->body, statement); pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE); } @@ -6749,10 +6736,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const pm_node_flag_set(UP(new_node), parse_symbol_encoding(parser, &content, &node->unescaped, true)); - // We are explicitly _not_ using pm_node_destroy here because we don't want - // to trash the unescaped string. We could instead copy the string if we - // know that it is owned, but we're taking the fast path for now. - xfree_sized(node, sizeof(pm_string_node_t)); + // The old node is arena-allocated so no explicit free is needed. return new_node; } @@ -6782,10 +6766,7 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { .unescaped = node->unescaped }; - // We are explicitly _not_ using pm_node_destroy here because we don't want - // to trash the unescaped string. We could instead copy the string if we - // know that it is owned, but we're taking the fast path for now. - xfree_sized(node, sizeof(pm_symbol_node_t)); + // The old node is arena-allocated so no explicit free is needed. return new_node; } @@ -6840,9 +6821,9 @@ pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) { * Append a name to an undef node. */ static void -pm_undef_node_append(pm_undef_node_t *node, pm_node_t *name) { +pm_undef_node_append(pm_arena_t *arena, pm_undef_node_t *node, pm_node_t *name) { PM_NODE_LENGTH_SET_NODE(node, name); - pm_node_list_append(&node->names, name); + pm_node_list_append(arena, &node->names, name); } /** @@ -6984,9 +6965,9 @@ pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) { * Append a new condition to a when node. */ static void -pm_when_node_conditions_append(pm_when_node_t *node, pm_node_t *condition) { +pm_when_node_conditions_append(pm_arena_t *arena, pm_when_node_t *node, pm_node_t *condition) { PM_NODE_LENGTH_SET_NODE(node, condition); - pm_node_list_append(&node->conditions, condition); + pm_node_list_append(arena, &node->conditions, condition); } /** @@ -7247,7 +7228,6 @@ pm_parser_scope_pop(pm_parser_t *parser) { pm_scope_t *scope = parser->current_scope; parser->current_scope = scope->previous; pm_locals_free(&scope->locals); - pm_node_list_free(&scope->implicit_parameters); xfree_sized(scope, sizeof(pm_scope_t)); } @@ -9284,7 +9264,11 @@ lex_question_mark(pm_parser_t *parser) { pm_buffer_init_capacity(&buffer, 3); escape_read(parser, &buffer, NULL, PM_ESCAPE_FLAG_SINGLE); - pm_string_owned_init(&parser->current_string, (uint8_t *) buffer.value, buffer.length); + + // Copy buffer data into the arena and free the heap buffer. + void *arena_data = pm_arena_memdup(parser->arena, buffer.value, buffer.length, PRISM_ALIGNOF(uint8_t)); + pm_string_constant_init(&parser->current_string, (const char *) arena_data, buffer.length); + pm_buffer_free(&buffer); return PM_TOKEN_CHARACTER_LITERAL; } else { @@ -9614,12 +9598,16 @@ pm_slice_ascii_only_p(const uint8_t *value, size_t length) { */ static inline void pm_token_buffer_copy(pm_parser_t *parser, pm_token_buffer_t *token_buffer) { - pm_string_owned_init(&parser->current_string, (uint8_t *) pm_buffer_value(&token_buffer->buffer), pm_buffer_length(&token_buffer->buffer)); + // Copy buffer data into the arena and free the heap buffer. + size_t len = pm_buffer_length(&token_buffer->buffer); + void *arena_data = pm_arena_memdup(parser->arena, pm_buffer_value(&token_buffer->buffer), len, PRISM_ALIGNOF(uint8_t)); + pm_string_constant_init(&parser->current_string, (const char *) arena_data, len); + pm_buffer_free(&token_buffer->buffer); } static inline void pm_regexp_token_buffer_copy(pm_parser_t *parser, pm_regexp_token_buffer_t *token_buffer) { - pm_string_owned_init(&parser->current_string, (uint8_t *) pm_buffer_value(&token_buffer->base.buffer), pm_buffer_length(&token_buffer->base.buffer)); + pm_token_buffer_copy(parser, &token_buffer->base); parser->current_regular_expression_ascii_only = pm_slice_ascii_only_p((const uint8_t *) pm_buffer_value(&token_buffer->regexp_buffer), pm_buffer_length(&token_buffer->regexp_buffer)); pm_buffer_free(&token_buffer->regexp_buffer); } @@ -12734,7 +12722,6 @@ parse_unwriteable_target(pm_parser_t *parser, pm_node_t *target) { pm_constant_id_t name = pm_parser_constant_id_raw(parser, parser->start + PM_NODE_START(target), parser->start + PM_NODE_END(target)); pm_local_variable_target_node_t *result = pm_local_variable_target_node_create(parser, &target->location, name, 0); - pm_node_destroy(parser, target); return UP(result); } @@ -12813,7 +12800,6 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p pm_node_t *node = UP(pm_local_variable_target_node_create(parser, &target->location, name, 0)); pm_node_unreference(parser, target); - pm_node_destroy(parser, target); return node; } @@ -12864,7 +12850,6 @@ parse_target(pm_parser_t *parser, pm_node_t *target, bool multiple, bool splat_p // =, so we know it's a local variable write. pm_location_t message_loc = call->message_loc; pm_constant_id_t name = pm_parser_local_add_location(parser, &message_loc, 0); - pm_node_destroy(parser, target); return UP(pm_local_variable_target_node_create(parser, &message_loc, name, 0)); } @@ -12939,11 +12924,9 @@ static pm_node_t * parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_node_t *value) { switch (PM_NODE_TYPE(target)) { case PM_MISSING_NODE: - pm_node_destroy(parser, value); return target; case PM_CLASS_VARIABLE_READ_NODE: { pm_class_variable_write_node_t *node = pm_class_variable_write_node_create(parser, (pm_class_variable_read_node_t *) target, operator, value); - pm_node_destroy(parser, target); return UP(node); } case PM_CONSTANT_PATH_NODE: { @@ -12962,7 +12945,6 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_parser_err_node(parser, node, PM_ERR_WRITE_TARGET_IN_METHOD); } - pm_node_destroy(parser, target); return parse_shareable_constant_write(parser, node); } case PM_BACK_REFERENCE_READ_NODE: @@ -12971,7 +12953,6 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod PRISM_FALLTHROUGH case PM_GLOBAL_VARIABLE_READ_NODE: { pm_global_variable_write_node_t *node = pm_global_variable_write_node_create(parser, target, operator, value); - pm_node_destroy(parser, target); return UP(node); } case PM_LOCAL_VARIABLE_READ_NODE: { @@ -12989,7 +12970,6 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod } pm_locals_unread(&scope->locals, name); - pm_node_destroy(parser, target); return UP(pm_local_variable_write_node_create(parser, name, depth, value, &location, operator)); } @@ -12998,13 +12978,11 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_node_t *node = UP(pm_local_variable_write_node_create(parser, name, 0, value, &target->location, operator)); pm_node_unreference(parser, target); - pm_node_destroy(parser, target); return node; } case PM_INSTANCE_VARIABLE_READ_NODE: { pm_node_t *write_node = UP(pm_instance_variable_write_node_create(parser, (pm_instance_variable_read_node_t *) target, operator, value)); - pm_node_destroy(parser, target); return write_node; } case PM_MULTI_TARGET_NODE: @@ -13049,7 +13027,6 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_refute_numbered_parameter(parser, message_loc.start, message_loc.length); pm_parser_local_add_location(parser, &message_loc, 0); - pm_node_destroy(parser, target); pm_constant_id_t constant_id = pm_parser_constant_id_raw(parser, parser->start + PM_LOCATION_START(&message_loc), parser->start + PM_LOCATION_END(&message_loc)); target = UP(pm_local_variable_write_node_create(parser, constant_id, 0, value, &message_loc, operator)); @@ -13071,7 +13048,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod pm_arguments_node_t *arguments = pm_arguments_node_create(parser); call->arguments = arguments; - pm_arguments_node_arguments_append(arguments, value); + pm_arguments_node_arguments_append(parser->arena, arguments, value); PM_NODE_LENGTH_SET_NODE(call, arguments); call->equal_loc = TOK2LOC(parser, operator); @@ -13090,7 +13067,7 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod call->arguments = pm_arguments_node_create(parser); } - pm_arguments_node_arguments_append(call->arguments, value); + pm_arguments_node_arguments_append(parser->arena, call->arguments, value); PM_NODE_LENGTH_SET_NODE(target, value); // Replace the name with "[]=". @@ -13115,7 +13092,6 @@ parse_write(pm_parser_t *parser, pm_node_t *target, pm_token_t *operator, pm_nod // any implicit parameters from the list of implicit parameters for // the current scope. pm_node_unreference(parser, value); - pm_node_destroy(parser, value); } PRISM_FALLTHROUGH default: @@ -13149,7 +13125,6 @@ parse_unwriteable_write(pm_parser_t *parser, pm_node_t *target, const pm_token_t pm_constant_id_t name = pm_parser_local_add_location(parser, &target->location, 1); pm_local_variable_write_node_t *result = pm_local_variable_write_node_create(parser, name, 0, value, &target->location, equals); - pm_node_destroy(parser, target); return UP(result); } @@ -13472,9 +13447,9 @@ parse_assocs(pm_parser_t *parser, pm_static_literals_t *literals, pm_node_t *nod } if (PM_NODE_TYPE_P(node, PM_HASH_NODE)) { - pm_hash_node_elements_append((pm_hash_node_t *) node, element); + pm_hash_node_elements_append(parser->arena, (pm_hash_node_t *) node, element); } else { - pm_keyword_hash_node_elements_append((pm_keyword_hash_node_t *) node, element); + pm_keyword_hash_node_elements_append(parser->arena, (pm_keyword_hash_node_t *) node, element); } // If there's no comma after the element, then we're done. @@ -13528,7 +13503,7 @@ parse_arguments_append(pm_parser_t *parser, pm_arguments_t *arguments, pm_node_t arguments->arguments = pm_arguments_node_create(parser); } - pm_arguments_node_arguments_append(arguments->arguments, argument); + pm_arguments_node_arguments_append(parser->arena, arguments->arguments, argument); } /** @@ -13697,7 +13672,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_HASH_VALUE, (uint16_t) (depth + 1)); argument = UP(pm_assoc_node_create(parser, argument, NTOK2PTR(operator), value)); - pm_keyword_hash_node_elements_append(bare_hash, argument); + pm_keyword_hash_node_elements_append(parser->arena, bare_hash, argument); argument = UP(bare_hash); // Then parse more if we have a comma @@ -13930,7 +13905,7 @@ parse_parameters_handle_trailing_comma( pm_parameters_node_rest_set(params, param); } else { pm_parser_err_node(parser, UP(param), PM_ERR_PARAMETER_SPLAT_MULTI); - pm_parameters_node_posts_append(params, UP(param)); + pm_parameters_node_posts_append(parser->arena, params, UP(param)); } } else { // foo do |*bar,|; end @@ -13974,9 +13949,9 @@ parse_parameters( pm_node_t *param = UP(parse_required_destructured_parameter(parser)); if (order > PM_PARAMETERS_ORDER_AFTER_OPTIONAL) { - pm_parameters_node_requireds_append(params, param); + pm_parameters_node_requireds_append(parser->arena, params, param); } else { - pm_parameters_node_posts_append(params, param); + pm_parameters_node_posts_append(parser->arena, params, param); } break; } @@ -14012,7 +13987,7 @@ parse_parameters( pm_parameters_node_block_set(params, param); } else { pm_parser_err_node(parser, param, PM_ERR_PARAMETER_BLOCK_MULTI); - pm_parameters_node_posts_append(params, param); + pm_parameters_node_posts_append(parser->arena, params, param); } break; @@ -14032,7 +14007,7 @@ parse_parameters( // If we already have a keyword rest parameter, then we replace it with the // forwarding parameter and move the keyword rest parameter to the posts list. pm_node_t *keyword_rest = params->keyword_rest; - pm_parameters_node_posts_append(params, keyword_rest); + pm_parameters_node_posts_append(parser->arena, params, keyword_rest); if (succeeded) pm_parser_err_previous(parser, PM_ERR_PARAMETER_UNEXPECTED_FWD); params->keyword_rest = NULL; } @@ -14093,7 +14068,7 @@ parse_parameters( if (repeated) { pm_node_flag_set_repeated_parameter(UP(param)); } - pm_parameters_node_optionals_append(params, param); + pm_parameters_node_optionals_append(parser->arena, params, param); // If the value of the parameter increased the number of // reads of that parameter, then we need to warn that we @@ -14116,13 +14091,13 @@ parse_parameters( if (repeated) { pm_node_flag_set_repeated_parameter(UP(param)); } - pm_parameters_node_requireds_append(params, UP(param)); + pm_parameters_node_requireds_append(parser->arena, params, UP(param)); } else { pm_required_parameter_node_t *param = pm_required_parameter_node_create(parser, &name); if (repeated) { pm_node_flag_set_repeated_parameter(UP(param)); } - pm_parameters_node_posts_append(params, UP(param)); + pm_parameters_node_posts_append(parser->arena, params, UP(param)); } break; @@ -14158,7 +14133,7 @@ parse_parameters( pm_node_flag_set_repeated_parameter(param); } - pm_parameters_node_keywords_append(params, param); + pm_parameters_node_keywords_append(parser->arena, params, param); break; } case PM_TOKEN_SEMICOLON: @@ -14175,7 +14150,7 @@ parse_parameters( pm_node_flag_set_repeated_parameter(param); } - pm_parameters_node_keywords_append(params, param); + pm_parameters_node_keywords_append(parser->arena, params, param); break; } default: { @@ -14204,7 +14179,7 @@ parse_parameters( } context_pop(parser); - pm_parameters_node_keywords_append(params, param); + pm_parameters_node_keywords_append(parser->arena, params, param); // If parsing the value of the parameter resulted in error recovery, // then we can put a missing node in its place and stop parsing the @@ -14245,7 +14220,7 @@ parse_parameters( pm_parameters_node_rest_set(params, param); } else { pm_parser_err_node(parser, param, PM_ERR_PARAMETER_SPLAT_MULTI); - pm_parameters_node_posts_append(params, param); + pm_parameters_node_posts_append(parser->arena, params, param); } break; @@ -14287,7 +14262,7 @@ parse_parameters( pm_parameters_node_keyword_rest_set(params, param); } else { pm_parser_err_node(parser, param, PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI); - pm_parameters_node_posts_append(params, param); + pm_parameters_node_posts_append(parser->arena, params, param); } break; @@ -14327,7 +14302,6 @@ parse_parameters( // If we don't have any parameters, return `NULL` instead of an empty `ParametersNode`. if (PM_NODE_START(params) == PM_NODE_END(params)) { - pm_node_destroy(parser, UP(params)); return NULL; } @@ -14486,7 +14460,7 @@ parse_rescues(pm_parser_t *parser, size_t opening_newline_index, const pm_token_ do { pm_node_t *expression = parse_starred_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_RESCUE_EXPRESSION, (uint16_t) (depth + 1)); - pm_rescue_node_exceptions_append(rescue, expression); + pm_rescue_node_exceptions_append(parser->arena, rescue, expression); // If we hit a newline, then this is the end of the rescue expression. We // can continue on to parse the statements. @@ -14725,7 +14699,7 @@ parse_block_parameters( pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); if (repeated) pm_node_flag_set_repeated_parameter(UP(local)); - pm_block_parameters_node_append_local(block_parameters, local); + pm_block_parameters_node_append_local(parser->arena, block_parameters, local); } while (accept1(parser, PM_TOKEN_COMMA)); } } @@ -14972,7 +14946,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept if (arguments->arguments == NULL) { arguments->arguments = pm_arguments_node_create(parser); } - pm_arguments_node_arguments_append(arguments->arguments, arguments->block); + pm_arguments_node_arguments_append(parser->arena, arguments->arguments, arguments->block); } arguments->block = UP(block); } @@ -15116,7 +15090,7 @@ parse_block_exit(pm_parser_t *parser, pm_node_t *node) { // block exit to the list of exits for the expression, and // the node parsing will handle validating it instead. assert(parser->current_block_exits != NULL); - pm_node_list_append(parser->current_block_exits, node); + pm_node_list_append(parser->arena, parser->current_block_exits, node); return; case PM_CONTEXT_BEGIN_ELSE: case PM_CONTEXT_BEGIN_ENSURE: @@ -15207,7 +15181,7 @@ pop_block_exits(pm_parser_t *parser, pm_node_list_t *previous_block_exits) { // However, they could still become valid in a higher level context if // there is another list above this one. In this case we'll push all of // the block exits up to the previous list. - pm_node_list_concat(previous_block_exits, parser->current_block_exits); + pm_node_list_concat(parser->arena, previous_block_exits, parser->current_block_exits); parser->current_block_exits = previous_block_exits; } else { // If we did not match a trailing while/until and this was the last @@ -15365,8 +15339,6 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl } pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return parent; } @@ -15681,11 +15653,11 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s } pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); - if (part) pm_interpolated_symbol_node_append(symbol, part); + if (part) pm_interpolated_symbol_node_append(parser->arena, symbol, part); while (!match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_interpolated_symbol_node_append(symbol, part); + pm_interpolated_symbol_node_append(parser->arena, symbol, part); } } @@ -15720,10 +15692,10 @@ parse_symbol(pm_parser_t *parser, pm_lex_mode_t *lex_mode, pm_lex_state_t next_s if (match1(parser, PM_TOKEN_STRING_CONTENT)) { pm_interpolated_symbol_node_t *symbol = pm_interpolated_symbol_node_create(parser, &opening, NULL, &opening); pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &content, NULL, &unescaped)); - pm_interpolated_symbol_node_append(symbol, part); + pm_interpolated_symbol_node_append(parser->arena, symbol, part); part = UP(pm_string_node_create_unescaped(parser, NULL, &parser->current, NULL, &parser->current_string)); - pm_interpolated_symbol_node_append(symbol, part); + pm_interpolated_symbol_node_append(parser->arena, symbol, part); if (next_state != PM_LEX_STATE_NONE) { lex_state_set(parser, next_state); @@ -15862,12 +15834,12 @@ parse_variable(pm_parser_t *parser) { } pm_node_t *node = UP(pm_local_variable_read_node_create_constant_id(parser, &parser->previous, name_id, 0, false)); - pm_node_list_append(¤t_scope->implicit_parameters, node); + pm_node_list_append(parser->arena, ¤t_scope->implicit_parameters, node); return node; } else if ((parser->version >= PM_OPTIONS_VERSION_CRUBY_3_4) && pm_token_is_it(parser->previous.start, parser->previous.end)) { pm_node_t *node = UP(pm_it_local_variable_read_node_create(parser, &parser->previous)); - pm_node_list_append(¤t_scope->implicit_parameters, node); + pm_node_list_append(parser->arena, ¤t_scope->implicit_parameters, node); return node; } @@ -15923,16 +15895,25 @@ parse_method_definition_name(pm_parser_t *parser) { } static void -parse_heredoc_dedent_string(pm_string_t *string, size_t common_whitespace) { - // Get a reference to the string struct that is being held by the string - // node. This is the value we're going to actually manipulate. - pm_string_ensure_owned(string); +parse_heredoc_dedent_string(pm_arena_t *arena, pm_string_t *string, size_t common_whitespace) { + // Make a writable copy in the arena if the string isn't already writable. + // We keep a mutable pointer to the arena memory so we can memmove into it + // below without casting away const from the string's source field. + uint8_t *writable; + + if (string->type != PM_STRING_OWNED) { + size_t length = pm_string_length(string); + writable = (uint8_t *) pm_arena_memdup(arena, pm_string_source(string), length, PRISM_ALIGNOF(uint8_t)); + pm_string_constant_init(string, (const char *) writable, length); + } else { + writable = (uint8_t *) string->source; + } // Now get the bounds of the existing string. We'll use this as a // destination to move bytes into. We'll also use it for bounds checking // since we don't require that these strings be null terminated. size_t dest_length = pm_string_length(string); - const uint8_t *source_cursor = (uint8_t *) string->source; + const uint8_t *source_cursor = writable; const uint8_t *source_end = source_cursor + dest_length; // We're going to move bytes backward in the string when we get leading @@ -15956,7 +15937,7 @@ parse_heredoc_dedent_string(pm_string_t *string, size_t common_whitespace) { dest_length--; } - memmove((uint8_t *) string->source, source_cursor, (size_t) (source_end - source_cursor)); + memmove(writable, source_cursor, (size_t) (source_end - source_cursor)); string->length = dest_length; } @@ -15999,11 +15980,10 @@ parse_heredoc_dedent(pm_parser_t *parser, pm_node_list_t *nodes, size_t common_w pm_string_node_t *string_node = ((pm_string_node_t *) node); if (dedent_next) { - parse_heredoc_dedent_string(&string_node->unescaped, common_whitespace); + parse_heredoc_dedent_string(parser->arena, &string_node->unescaped, common_whitespace); } if (heredoc_dedent_discard_string_node(parser, string_node)) { - pm_node_destroy(parser, node); } else { nodes->nodes[write_index++] = node; } @@ -16092,18 +16072,16 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 if (match1(parser, PM_TOKEN_STRING_CONTENT)) { pm_node_list_t parts = { 0 }; pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &content, NULL, &unescaped)); - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); do { part = UP(pm_string_node_create_current_string(parser, NULL, &parser->current, NULL)); - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); parser_lex(parser); } while (match1(parser, PM_TOKEN_STRING_CONTENT)); expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_LITERAL_EOF); node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); - - pm_node_list_free(&parts); } else if (accept1(parser, PM_TOKEN_LABEL_END)) { node = UP(pm_symbol_node_create_unescaped(parser, &opening, &content, &parser->previous, &unescaped, parse_symbol_encoding(parser, &content, &unescaped, true))); if (!label_allowed) pm_parser_err_node(parser, node, PM_ERR_UNEXPECTED_LABEL); @@ -16152,11 +16130,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 pm_node_list_t parts = { 0 }; pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &parser->previous, NULL, &unescaped)); pm_node_flag_set(part, parse_unescaped_encoding(parser)); - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); } } @@ -16170,8 +16148,6 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); } - - pm_node_list_free(&parts); } } else { // If we get here, then the first part of the string is not plain @@ -16182,7 +16158,7 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 while (!match3(parser, PM_TOKEN_STRING_END, PM_TOKEN_LABEL_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); } } @@ -16196,8 +16172,6 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 expect1(parser, PM_TOKEN_STRING_END, PM_ERR_STRING_INTERPOLATED_TERM); node = UP(pm_interpolated_string_node_create(parser, &opening, &parts, &parser->previous)); } - - pm_node_list_free(&parts); } if (current == NULL) { @@ -16228,11 +16202,11 @@ parse_strings(pm_parser_t *parser, pm_node_t *current, bool accepts_label, uint1 concating = true; pm_interpolated_string_node_t *container = pm_interpolated_string_node_create(parser, NULL, NULL, NULL); - pm_interpolated_string_node_append(container, current); + pm_interpolated_string_node_append(parser->arena, container, current); current = UP(container); } - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, node); + pm_interpolated_string_node_append(parser->arena, (pm_interpolated_string_node_t *) current, node); } } @@ -16259,7 +16233,7 @@ parse_pattern_capture(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_c if (pm_constant_id_list_includes(captures, capture)) { pm_parser_err(parser, location->start, location->length, PM_ERR_PATTERN_CAPTURE_DUPLICATE); } else { - pm_constant_id_list_append(captures, capture); + pm_constant_id_list_append(parser->arena, captures, capture); } } @@ -16379,7 +16353,7 @@ parse_pattern_constant_path(pm_parser_t *parser, pm_constant_id_list_t *captures // attaching its constant. In this case we'll create an array pattern and // attach our constant to it. pm_array_pattern_node_t *pattern_node = pm_array_pattern_node_constant_create(parser, node, &opening, &closing); - pm_array_pattern_node_requireds_append(pattern_node, inner); + pm_array_pattern_node_requireds_append(parser->arena, pattern_node, inner); return UP(pattern_node); } @@ -16557,7 +16531,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node } pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value)); - pm_node_list_append(&assocs, assoc); + pm_node_list_append(parser->arena, &assocs, assoc); break; } } @@ -16572,7 +16546,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node pm_node_t *value = UP(pm_missing_node_create(parser, PM_NODE_START(first_node), PM_NODE_LENGTH(first_node))); pm_node_t *assoc = UP(pm_assoc_node_create(parser, first_node, NULL, value)); - pm_node_list_append(&assocs, assoc); + pm_node_list_append(parser->arena, &assocs, assoc); break; } } @@ -16596,7 +16570,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node rest = assoc; } else { pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST); - pm_node_list_append(&assocs, assoc); + pm_node_list_append(parser->arena, &assocs, assoc); } } else { pm_node_t *key; @@ -16637,12 +16611,12 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node pm_parser_err_node(parser, assoc, PM_ERR_PATTERN_EXPRESSION_AFTER_REST); } - pm_node_list_append(&assocs, assoc); + pm_node_list_append(parser->arena, &assocs, assoc); } } pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest); - xfree_sized(assocs.nodes, assocs.capacity * sizeof(pm_node_t *)); + // assocs.nodes is arena-allocated; no explicit free needed. pm_static_literals_free(&keys); return node; @@ -16724,7 +16698,7 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm } pm_array_pattern_node_t *node = pm_array_pattern_node_empty_create(parser, &opening, &closing); - pm_array_pattern_node_requireds_append(node, inner); + pm_array_pattern_node_requireds_append(parser->arena, node, inner); return UP(node); } case PM_TOKEN_BRACE_LEFT: { @@ -16809,7 +16783,6 @@ parse_pattern_primitive(pm_parser_t *parser, pm_constant_id_list_t *captures, pm pm_missing_node_t *missing_node = pm_missing_node_create(parser, PM_NODE_START(node), PM_NODE_LENGTH(node)); pm_node_unreference(parser, node); - pm_node_destroy(parser, node); return UP(missing_node); } @@ -17121,14 +17094,14 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag // or a find pattern. We need to parse all of the patterns, put them // into a big list, and then determine which type of node we have. pm_node_list_t nodes = { 0 }; - pm_node_list_append(&nodes, node); + pm_node_list_append(parser->arena, &nodes, node); // Gather up all of the patterns into the list. while (accept1(parser, PM_TOKEN_COMMA)) { // Break early here in case we have a trailing comma. if (match7(parser, PM_TOKEN_KEYWORD_THEN, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_AND, PM_TOKEN_KEYWORD_OR)) { node = UP(pm_implicit_rest_node_create(parser, &parser->previous)); - pm_node_list_append(&nodes, node); + pm_node_list_append(parser->arena, &nodes, node); trailing_rest = true; break; } @@ -17148,7 +17121,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag node = parse_pattern_primitives(parser, captures, NULL, PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA, (uint16_t) (depth + 1)); } - pm_node_list_append(&nodes, node); + pm_node_list_append(parser->arena, &nodes, node); } // If the first pattern and the last pattern are rest patterns, then we @@ -17169,7 +17142,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag } } - xfree_sized(nodes.nodes, nodes.capacity * sizeof(pm_node_t *)); + // nodes.nodes is arena-allocated; no explicit free needed. } else if (leading_rest) { // Otherwise, if we parsed a single splat pattern, then we know we have // an array pattern, so we can go ahead and create that node. @@ -17587,7 +17560,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_HASH_VALUE, (uint16_t) (depth + 1)); pm_node_t *assoc = UP(pm_assoc_node_create(parser, element, NTOK2PTR(operator), value)); - pm_keyword_hash_node_elements_append(hash, assoc); + pm_keyword_hash_node_elements_append(parser->arena, hash, assoc); element = UP(hash); if (accept1(parser, PM_TOKEN_COMMA) && !match1(parser, PM_TOKEN_BRACKET_RIGHT)) { @@ -17599,7 +17572,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - pm_array_node_elements_append(array, element); + pm_array_node_elements_append(parser->arena, array, element); if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break; } @@ -17637,10 +17610,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // we have an empty parentheses node, and we can immediately return. if (match2(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_TOKEN_EOF)) { expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_EXPECT_RPAREN); - pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_parentheses_node_create(parser, &opening, NULL, &parser->previous, flags)); } @@ -17683,9 +17653,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_accepts_block_stack_pop(parser); - pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); if (PM_NODE_TYPE_P(statement, PM_MULTI_TARGET_NODE) || PM_NODE_TYPE_P(statement, PM_SPLAT_NODE)) { // If we have a single statement and are ending on a right @@ -17821,8 +17789,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - pm_void_statements_check(parser, statements, true); return UP(pm_parentheses_node_create(parser, &opening, UP(statements), &parser->previous, flags)); } @@ -18064,7 +18030,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - pm_node_destroy(parser, node); return UP(fcall); } } @@ -18129,7 +18094,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (lex_mode.indent == PM_HEREDOC_INDENT_TILDE && (common_whitespace != (size_t) -1) && (common_whitespace != 0)) { - parse_heredoc_dedent_string(&cast->unescaped, common_whitespace); + parse_heredoc_dedent_string(parser->arena, &cast->unescaped, common_whitespace); } node = UP(cast); @@ -18139,11 +18104,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // so we'll need to create an interpolated string node to hold // them all. pm_node_list_t parts = { 0 }; - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); while (!match2(parser, PM_TOKEN_HEREDOC_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_node_list_append(&parts, part); + pm_node_list_append(parser->arena, &parts, part); } } @@ -18160,7 +18125,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b node = UP(cast); } else { pm_interpolated_string_node_t *cast = pm_interpolated_string_node_create(parser, &opening, &parts, &opening); - pm_node_list_free(&parts); expect1_heredoc_term(parser, lex_mode.ident_start, lex_mode.ident_length); pm_interpolated_string_node_closing_set(parser, cast, &parser->previous); @@ -18289,10 +18253,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (match1(parser, PM_TOKEN_KEYWORD_END)) { parser_warn_indentation_mismatch(parser, opening_newline_index, &case_keyword, false, false); parser_lex(parser); - pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - pm_parser_err_token(parser, &case_keyword, PM_ERR_CASE_MISSING_CONDITIONS); return UP(pm_case_node_create(parser, &case_keyword, predicate, &parser->previous)); } @@ -18321,12 +18282,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_EXPECT_EXPRESSION_AFTER_STAR, (uint16_t) (depth + 1)); pm_splat_node_t *splat_node = pm_splat_node_create(parser, &operator, expression); - pm_when_node_conditions_append(when_node, UP(splat_node)); + pm_when_node_conditions_append(parser->arena, when_node, UP(splat_node)); if (PM_NODE_TYPE_P(expression, PM_MISSING_NODE)) break; } else { pm_node_t *condition = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, false, PM_ERR_CASE_EXPRESSION_AFTER_WHEN, (uint16_t) (depth + 1)); - pm_when_node_conditions_append(when_node, condition); + pm_when_node_conditions_append(parser->arena, when_node, condition); // If we found a missing node, then this is a syntax // error and we should stop looping. @@ -18360,7 +18321,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } - pm_case_node_condition_append(case_node, UP(when_node)); + pm_case_node_condition_append(parser->arena, case_node, UP(when_node)); } // If we didn't parse any conditions (in or when) then we need @@ -18399,7 +18360,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_IN, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; - pm_constant_id_list_free(&captures); // Since we're in the top-level of the case-in node we need // to check for guard clauses in the form of `if` or @@ -18439,7 +18399,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // Now that we have the full pattern and statements, we can // create the node and attach it to the case node. pm_node_t *condition = UP(pm_in_node_create(parser, pattern, statements, &in_keyword, NTOK2PTR(then_keyword))); - pm_case_match_node_condition_append(case_node, condition); + pm_case_match_node_condition_append(parser->arena, case_node, condition); } // If we didn't parse any conditions (in or when) then we need @@ -18479,8 +18439,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return node; } case PM_TOKEN_KEYWORD_BEGIN: { @@ -18507,10 +18465,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b PM_NODE_LENGTH_SET_TOKEN(parser, begin_node, &parser->previous); pm_begin_node_end_keyword_set(parser, begin_node, &parser->previous); - pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(begin_node); } case PM_TOKEN_KEYWORD_BEGIN_UPCASE: { @@ -18535,8 +18490,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } flush_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_pre_execution_node_create(parser, &keyword, &opening, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_BREAK: @@ -18616,7 +18569,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (arguments.block != NULL) { pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); pm_node_unreference(parser, arguments.block); - pm_node_destroy(parser, arguments.block); arguments.block = NULL; } @@ -18667,8 +18619,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_do_loop_stack_pop(parser); flush_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_singleton_class_node_create(parser, &locals, &class_keyword, &operator, expression, statements, &parser->previous)); } @@ -18732,8 +18682,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_class_node_create(parser, &locals, &class_keyword, constant_path, &name, NTOK2PTR(inheritance_operator), superclass, statements, &parser->previous)); } case PM_TOKEN_KEYWORD_DEF: { @@ -19069,7 +19017,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_constant_id_t name_id = pm_parser_constant_id_raw(parser, name.start, parse_operator_symbol_name(&name)); flush_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); return UP(pm_def_node_create( parser, @@ -19229,9 +19176,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *name = parse_undef_argument(parser, (uint16_t) (depth + 1)); if (PM_NODE_TYPE_P(name, PM_MISSING_NODE)) { - pm_node_destroy(parser, name); } else { - pm_undef_node_append(undef, name); + pm_undef_node_append(parser->arena, undef, name); while (match1(parser, PM_TOKEN_COMMA)) { lex_state_set(parser, PM_LEX_STATE_FNAME | PM_LEX_STATE_FITEM); @@ -19239,11 +19185,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b name = parse_undef_argument(parser, (uint16_t) (depth + 1)); if (PM_NODE_TYPE_P(name, PM_MISSING_NODE)) { - pm_node_destroy(parser, name); break; } - pm_undef_node_append(undef, name); + pm_undef_node_append(parser->arena, undef, name); } } @@ -19314,7 +19259,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // the name of the module, then we'll handle that here. if (PM_NODE_TYPE_P(constant_path, PM_MISSING_NODE)) { pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); pm_token_t missing = (pm_token_t) { .type = 0, .start = parser->previous.end, .end = parser->previous.end }; return UP(pm_module_node_create(parser, NULL, &module_keyword, constant_path, &missing, NULL, &missing)); @@ -19363,7 +19307,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); return UP(pm_module_node_create(parser, &locals, &module_keyword, constant_path, &name, statements, &parser->previous)); } @@ -19478,7 +19421,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_SYMBOL_NODE)) { pm_node_t *string = UP(pm_string_node_create_current_string(parser, NULL, &parser->current, NULL)); parser_lex(parser); - pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, string); + pm_interpolated_symbol_node_append(parser->arena, (pm_interpolated_symbol_node_t *) current, string); } else if (PM_NODE_TYPE_P(current, PM_SYMBOL_NODE)) { pm_symbol_node_t *cast = (pm_symbol_node_t *) current; pm_token_t content = { .type = PM_TOKEN_STRING_CONTENT, .start = parser->start + cast->value_loc.start, .end = parser->start + cast->value_loc.start + cast->value_loc.length }; @@ -19487,10 +19430,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, NULL, NULL, NULL); - pm_interpolated_symbol_node_append(interpolated, first_string); - pm_interpolated_symbol_node_append(interpolated, second_string); + pm_interpolated_symbol_node_append(parser->arena, interpolated, first_string); + pm_interpolated_symbol_node_append(parser->arena, interpolated, second_string); - xfree_sized(current, sizeof(pm_symbol_node_t)); + // current is arena-allocated so no explicit free is needed. current = UP(interpolated); } else { assert(false && "unreachable"); @@ -19498,7 +19441,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (current) { - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); current = NULL; } else { expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_I_LOWER_ELEMENT); @@ -19534,7 +19477,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } else { // If we hit a separator after we've hit content, then we need to // append that content to the list and reset the current node. - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); current = NULL; } @@ -19555,7 +19498,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *string = UP(pm_string_node_create_current_string(parser, NULL, &parser->current, NULL)); parser_lex(parser); - pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, string); + pm_interpolated_symbol_node_append(parser->arena, (pm_interpolated_symbol_node_t *) current, string); } else if (PM_NODE_TYPE_P(current, PM_SYMBOL_NODE)) { // If we hit string content and the current node is a symbol node, // then we need to convert the current node into an interpolated @@ -19572,10 +19515,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b parser_lex(parser); pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, NULL, NULL, NULL); - pm_interpolated_symbol_node_append(interpolated, first_string); - pm_interpolated_symbol_node_append(interpolated, second_string); + pm_interpolated_symbol_node_append(parser->arena, interpolated, first_string); + pm_interpolated_symbol_node_append(parser->arena, interpolated, second_string); - xfree_sized(current, sizeof(pm_symbol_node_t)); + // current is arena-allocated so no explicit free is needed. current = UP(interpolated); } else { assert(false && "unreachable"); @@ -19597,7 +19540,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, NULL, NULL, NULL); current = UP(pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current)); - pm_interpolated_symbol_node_append(interpolated, current); + pm_interpolated_symbol_node_append(parser->arena, interpolated, current); PM_NODE_START_SET_NODE(interpolated, current); start_location_set = true; current = UP(interpolated); @@ -19607,7 +19550,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser, (uint16_t) (depth + 1)); - pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, part); + pm_interpolated_symbol_node_append(parser->arena, (pm_interpolated_symbol_node_t *) current, part); if (!start_location_set) { PM_NODE_START_SET_NODE(current, part); } @@ -19628,7 +19571,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_t *interpolated = pm_interpolated_symbol_node_create(parser, NULL, NULL, NULL); current = UP(pm_symbol_node_to_string_node(parser, (pm_symbol_node_t *) current)); - pm_interpolated_symbol_node_append(interpolated, current); + pm_interpolated_symbol_node_append(parser->arena, interpolated, current); PM_NODE_START_SET_NODE(interpolated, current); start_location_set = true; current = UP(interpolated); @@ -19640,7 +19583,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser, (uint16_t) (depth + 1)); - pm_interpolated_symbol_node_append((pm_interpolated_symbol_node_t *) current, part); + pm_interpolated_symbol_node_append(parser->arena, (pm_interpolated_symbol_node_t *) current, part); if (!start_location_set) { PM_NODE_START_SET_NODE(current, part); } @@ -19655,7 +19598,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we have a current node, then we need to append it to the list. if (current) { - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); } pm_token_t closing = parser->current; @@ -19688,11 +19631,11 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b if (current == NULL) { current = string; } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_STRING_NODE)) { - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, string); + pm_interpolated_string_node_append(parser->arena, (pm_interpolated_string_node_t *) current, string); } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, NULL, NULL, NULL); - pm_interpolated_string_node_append(interpolated, current); - pm_interpolated_string_node_append(interpolated, string); + pm_interpolated_string_node_append(parser->arena, interpolated, current); + pm_interpolated_string_node_append(parser->arena, interpolated, string); current = UP(interpolated); } else { assert(false && "unreachable"); @@ -19701,7 +19644,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } if (current) { - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); current = NULL; } else { expect1(parser, PM_TOKEN_STRING_CONTENT, PM_ERR_LIST_W_LOWER_ELEMENT); @@ -19742,7 +19685,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we hit a separator after we've hit content, // then we need to append that content to the list // and reset the current node. - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); current = NULL; } @@ -19764,15 +19707,15 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we hit string content and the current node is // an interpolated string, then we need to append // the string content to the list of child nodes. - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, string); + pm_interpolated_string_node_append(parser->arena, (pm_interpolated_string_node_t *) current, string); } else if (PM_NODE_TYPE_P(current, PM_STRING_NODE)) { // If we hit string content and the current node is // a string node, then we need to convert the // current node into an interpolated string and add // the string content to the list of child nodes. pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, NULL, NULL, NULL); - pm_interpolated_string_node_append(interpolated, current); - pm_interpolated_string_node_append(interpolated, string); + pm_interpolated_string_node_append(parser->arena, interpolated, current); + pm_interpolated_string_node_append(parser->arena, interpolated, string); current = UP(interpolated); } else { assert(false && "unreachable"); @@ -19793,7 +19736,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // current into an interpolated string and add the // string node to the list of parts. pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, NULL, NULL, NULL); - pm_interpolated_string_node_append(interpolated, current); + pm_interpolated_string_node_append(parser->arena, interpolated, current); current = UP(interpolated); } else { // If we hit an embedded variable and the current @@ -19802,7 +19745,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser, (uint16_t) (depth + 1)); - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, part); + pm_interpolated_string_node_append(parser->arena, (pm_interpolated_string_node_t *) current, part); break; } case PM_TOKEN_EMBEXPR_BEGIN: { @@ -19818,7 +19761,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // current into an interpolated string and add the // string node to the list of parts. pm_interpolated_string_node_t *interpolated = pm_interpolated_string_node_create(parser, NULL, NULL, NULL); - pm_interpolated_string_node_append(interpolated, current); + pm_interpolated_string_node_append(parser->arena, interpolated, current); current = UP(interpolated); } else if (PM_NODE_TYPE_P(current, PM_INTERPOLATED_STRING_NODE)) { // If we hit an embedded expression and the current @@ -19829,7 +19772,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_node_t *part = parse_string_part(parser, (uint16_t) (depth + 1)); - pm_interpolated_string_node_append((pm_interpolated_string_node_t *) current, part); + pm_interpolated_string_node_append(parser->arena, (pm_interpolated_string_node_t *) current, part); break; } default: @@ -19841,7 +19784,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // If we have a current node, then we need to append it to the list. if (current) { - pm_array_node_elements_append(array, current); + pm_array_node_elements_append(parser->arena, array, current); } pm_token_t closing = parser->current; @@ -19919,7 +19862,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_flag_set(part, PM_STRING_FLAGS_FORCED_BINARY_ENCODING); } - pm_interpolated_regular_expression_node_append(interpolated, part); + pm_interpolated_regular_expression_node_append(parser->arena, interpolated, part); } else { // If the first part of the body of the regular expression is not a // string content, then we have interpolation and we need to create an @@ -19932,7 +19875,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *part; while (!match2(parser, PM_TOKEN_REGEXP_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_interpolated_regular_expression_node_append(interpolated, part); + pm_interpolated_regular_expression_node_append(parser->arena, interpolated, part); } } @@ -19995,7 +19938,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *part = UP(pm_string_node_create_unescaped(parser, NULL, &parser->previous, NULL, &unescaped)); pm_node_flag_set(part, parse_unescaped_encoding(parser)); - pm_interpolated_xstring_node_append(node, part); + pm_interpolated_xstring_node_append(parser->arena, node, part); } else { // If the first part of the body of the string is not a string // content, then we have interpolation and we need to create an @@ -20006,7 +19949,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *part; while (!match2(parser, PM_TOKEN_STRING_END, PM_TOKEN_EOF)) { if ((part = parse_string_part(parser, (uint16_t) (depth + 1))) != NULL) { - pm_interpolated_xstring_node_append(node, part); + pm_interpolated_xstring_node_append(parser->arena, node, part); } } @@ -20340,13 +20283,13 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding single_value = false; pm_array_node_t *array = pm_array_node_create(parser, NULL); - pm_array_node_elements_append(array, value); + pm_array_node_elements_append(parser->arena, array, value); value = UP(array); while (accept1(parser, PM_TOKEN_COMMA)) { pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT, (uint16_t) (depth + 1)); - pm_array_node_elements_append(array, element); + pm_array_node_elements_append(parser->arena, array, element); if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break; parse_assignment_value_local(parser, element); @@ -20393,14 +20336,12 @@ parse_call_operator_write(pm_parser_t *parser, pm_call_node_t *call_node, const if (call_node->arguments != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_ARGUMENTS); pm_node_unreference(parser, UP(call_node->arguments)); - pm_node_destroy(parser, UP(call_node->arguments)); call_node->arguments = NULL; } if (call_node->block != NULL) { pm_parser_err_token(parser, operator, PM_ERR_OPERATOR_WRITE_BLOCK); pm_node_unreference(parser, UP(call_node->block)); - pm_node_destroy(parser, UP(call_node->block)); call_node->block = NULL; } } @@ -20614,7 +20555,7 @@ parse_regular_expression_named_capture(const pm_string_t *capture, void *data) { // Add this name to the list of constants if it is valid, not duplicated, // and not a keyword. if (name != 0 && !pm_constant_id_list_includes(names, name)) { - pm_constant_id_list_append(names, name); + pm_constant_id_list_append(parser->arena, names, name); int depth; if ((depth = pm_parser_local_depth_constant_id(parser, name)) == -1) { @@ -20639,7 +20580,7 @@ parse_regular_expression_named_capture(const pm_string_t *capture, void *data) { // Next, create the local variable target and add it to the list of // targets for the match. pm_node_t *target = UP(pm_local_variable_target_node_create(parser, &TOK2LOC(parser, &((pm_token_t) { .type = 0, .start = start, .end = end })), name, depth == -1 ? 0 : (uint32_t) depth)); - pm_node_list_append(&callback_data->match->targets, target); + pm_node_list_append(parser->arena, &callback_data->match->targets, target); } pm_buffer_free(&unescaped); @@ -20666,7 +20607,6 @@ parse_regular_expression_named_captures(pm_parser_t *parser, const pm_string_t * }; pm_regexp_parse(parser, pm_string_source(content), pm_string_length(content), extended_mode, parse_regular_expression_named_capture, &callback_data, parse_regular_expression_error, &error_data); - pm_constant_id_list_free(&callback_data.names); if (callback_data.match != NULL) { return UP(callback_data.match); @@ -20752,7 +20692,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_global_variable_and_write_node_create(parser, node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CLASS_VARIABLE_READ_NODE: { @@ -20761,7 +20700,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_class_variable_and_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CONSTANT_PATH_NODE: { @@ -20778,7 +20716,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *write = UP(pm_constant_and_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { @@ -20787,7 +20724,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_instance_variable_and_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_IT_LOCAL_VARIABLE_READ_NODE: { @@ -20798,7 +20734,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); - pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { @@ -20813,7 +20748,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, node, &token, value, cast->name, cast->depth)); - pm_node_destroy(parser, node); return result; } case PM_CALL_NODE: { @@ -20830,7 +20764,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_and_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, UP(cast)); return result; } @@ -20884,7 +20817,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_global_variable_or_write_node_create(parser, node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CLASS_VARIABLE_READ_NODE: { @@ -20893,7 +20825,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_class_variable_or_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CONSTANT_PATH_NODE: { @@ -20910,7 +20841,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *write = UP(pm_constant_or_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { @@ -20919,7 +20849,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_instance_variable_or_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_IT_LOCAL_VARIABLE_READ_NODE: { @@ -20930,7 +20859,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); - pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { @@ -20945,7 +20873,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, node, &token, value, cast->name, cast->depth)); - pm_node_destroy(parser, node); return result; } case PM_CALL_NODE: { @@ -20962,7 +20889,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_or_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, UP(cast)); return result; } @@ -21026,7 +20952,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_global_variable_operator_write_node_create(parser, node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CLASS_VARIABLE_READ_NODE: { @@ -21035,7 +20960,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_class_variable_operator_write_node_create(parser, (pm_class_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_CONSTANT_PATH_NODE: { @@ -21052,7 +20976,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *write = UP(pm_constant_operator_write_node_create(parser, (pm_constant_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return parse_shareable_constant_write(parser, write); } case PM_INSTANCE_VARIABLE_READ_NODE: { @@ -21061,7 +20984,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_instance_variable_operator_write_node_create(parser, (pm_instance_variable_read_node_t *) node, &token, value)); - pm_node_destroy(parser, node); return result; } case PM_IT_LOCAL_VARIABLE_READ_NODE: { @@ -21072,7 +20994,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, node, &token, value, name, 0)); pm_node_unreference(parser, node); - pm_node_destroy(parser, node); return result; } case PM_LOCAL_VARIABLE_READ_NODE: { @@ -21087,7 +21008,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, node, &token, value, cast->name, cast->depth)); - pm_node_destroy(parser, node); return result; } case PM_CALL_NODE: { @@ -21103,7 +21023,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR, (uint16_t) (depth + 1)); pm_node_t *result = UP(pm_local_variable_operator_write_node_create(parser, UP(cast), &token, value, constant_id, 0)); - pm_node_destroy(parser, UP(cast)); return result; } @@ -21418,8 +21337,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t context_pop(parser); pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression)); } @@ -21431,8 +21348,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t context_pop(parser); pop_block_exits(parser, previous_block_exits); - pm_node_list_free(¤t_block_exits); - return UP(pm_if_node_ternary_create(parser, node, &qmark, true_expression, &colon, false_expression)); } case PM_TOKEN_COLON_COLON: { @@ -21554,7 +21469,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t if (arguments.arguments == NULL) { arguments.arguments = pm_arguments_node_create(parser); } - pm_arguments_node_arguments_append(arguments.arguments, arguments.block); + pm_arguments_node_arguments_append(parser->arena, arguments.arguments, arguments.block); } arguments.block = UP(block); @@ -21575,7 +21490,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_IN, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; - pm_constant_id_list_free(&captures); return UP(pm_match_predicate_node_create(parser, node, pattern, &operator)); } @@ -21592,7 +21506,6 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_node_t *pattern = parse_pattern(parser, &captures, PM_PARSE_PATTERN_TOP | PM_PARSE_PATTERN_MULTI, PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET, (uint16_t) (depth + 1)); parser->pattern_matching_newlines = previous_pattern_matching_newlines; - pm_constant_id_list_free(&captures); return UP(pm_match_required_node_create(parser, node, pattern, &operator)); } @@ -21842,6 +21755,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( + parser->arena, arguments, UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2))) ); @@ -21861,6 +21775,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( + parser->arena, arguments, UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$;", 2))) ); @@ -21874,25 +21789,26 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { UP(call) ); - pm_statements_node_body_prepend(statements, UP(write)); + pm_statements_node_body_prepend(parser->arena, statements, UP(write)); } pm_arguments_node_t *arguments = pm_arguments_node_create(parser); pm_arguments_node_arguments_append( + parser->arena, arguments, UP(pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$/", 2))) ); if (PM_PARSER_COMMAND_LINE_OPTION_L(parser)) { pm_keyword_hash_node_t *keywords = pm_keyword_hash_node_create(parser); - pm_keyword_hash_node_elements_append(keywords, UP(pm_assoc_node_create( + pm_keyword_hash_node_elements_append(parser->arena, keywords, UP(pm_assoc_node_create( parser, UP(pm_symbol_node_synthesized_create(parser, "chomp")), NULL, UP(pm_true_node_synthesized_create(parser)) ))); - pm_arguments_node_arguments_append(arguments, UP(keywords)); + pm_arguments_node_arguments_append(parser->arena, arguments, UP(keywords)); pm_node_flag_set(UP(arguments), PM_ARGUMENTS_NODE_FLAGS_CONTAINS_KEYWORDS); } @@ -21947,8 +21863,6 @@ parse_program(pm_parser_t *parser) { flush_block_exits(parser, previous_block_exits); } - pm_node_list_free(¤t_block_exits); - // If this is an empty file, then we're still going to parse all of the // statements in order to gather up all of the comments and such. Here we'll // correct the location information. @@ -22031,10 +21945,12 @@ pm_parser_init_shebang(pm_parser_t *parser, const pm_options_t *options, const c * Initialize a parser with the given start and end pointers. */ PRISM_EXPORTED_FUNCTION void -pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options) { +pm_parser_init(pm_arena_t *arena, pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options) { + assert(arena != NULL); assert(source != NULL); *parser = (pm_parser_t) { + .arena = arena, .node_id = 0, .lex_state = PM_LEX_STATE_BEG, .enclosure_nesting = 0, @@ -22421,20 +22337,20 @@ pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t * can stream stdin in to Ruby so we need to support a streaming API. */ PRISM_EXPORTED_FUNCTION pm_node_t * -pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options) { +pm_parse_stream(pm_arena_t *arena, pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options) { pm_buffer_init(buffer); bool eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof); - pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); + pm_parser_init(arena, parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); pm_node_t *node = pm_parse(parser); while (!eof && parser->error_list.size > 0) { - pm_node_destroy(parser, node); eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof); pm_parser_free(parser); - pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); + pm_arena_free(arena); + pm_parser_init(arena, parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options); node = pm_parse(parser); } @@ -22449,14 +22365,15 @@ pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); - pm_node_t *node = pm_parse(&parser); - pm_node_destroy(&parser, node); + pm_parse(&parser); bool result = parser.error_list.size == 0; pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); return result; @@ -22500,8 +22417,9 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); pm_node_t *node = pm_parse(&parser); @@ -22509,8 +22427,8 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons pm_serialize_content(&parser, node, buffer); pm_buffer_append_byte(buffer, '\0'); - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); } @@ -22520,19 +22438,20 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons */ PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const char *data) { + pm_arena_t arena = { 0 }; pm_parser_t parser; pm_options_t options = { 0 }; pm_options_read(&options, data); pm_buffer_t parser_buffer; - pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, stream_feof, &options); + pm_node_t *node = pm_parse_stream(&arena, &parser, &parser_buffer, stream, stream_fgets, stream_feof, &options); pm_serialize_header(buffer); pm_serialize_content(&parser, node, buffer); pm_buffer_append_byte(buffer, '\0'); - pm_node_destroy(&parser, node); pm_buffer_free(&parser_buffer); pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); } @@ -22544,17 +22463,18 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); - pm_node_t *node = pm_parse(&parser); + pm_parse(&parser); pm_serialize_header(buffer); pm_serialize_encoding(parser.encoding, buffer); pm_buffer_append_varsint(buffer, parser.start_line); pm_serialize_comment_list(&parser.comment_list, buffer); - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); } diff --git a/prism/prism.h b/prism/prism.h index a71ccfef369b22..76733b8aaf9f35 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -11,6 +11,7 @@ extern "C" { #endif #include "prism/defines.h" +#include "prism/util/pm_arena.h" #include "prism/util/pm_buffer.h" #include "prism/util/pm_char.h" #include "prism/util/pm_integer.h" @@ -52,8 +53,11 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); /** * Initialize a parser with the given start and end pointers. * - * The resulting parser must eventually be freed with `pm_parser_free()`. + * The resulting parser must eventually be freed with `pm_parser_free()`. The + * arena is caller-owned and must outlive the parser — `pm_parser_free()` does + * not free the arena. * + * @param arena The arena to use for all AST-lifetime allocations. * @param parser The parser to initialize. * @param source The source to parse. * @param size The size of the source. @@ -62,7 +66,7 @@ PRISM_EXPORTED_FUNCTION const char * pm_version(void); * * \public \memberof pm_parser */ -PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); +PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_arena_t *arena, pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options); /** * Register a callback that will be called whenever prism changes the encoding @@ -114,6 +118,7 @@ typedef int (pm_parse_stream_feof_t)(void *stream); /** * Parse a stream of Ruby source and return the tree. * + * @param arena The arena to use for all AST-lifetime allocations. * @param parser The parser to use. * @param buffer The buffer to use. * @param stream The stream to parse. @@ -124,7 +129,7 @@ typedef int (pm_parse_stream_feof_t)(void *stream); * * \public \memberof pm_parser */ -PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options); +PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_arena_t *arena, pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options); // We optionally support serializing to a binary string. For systems that don't // want or need this functionality, it can be turned off with the @@ -333,24 +338,26 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * In order to parse Ruby code, the structures and functions that you're going * to want to use and be aware of are: * + * * `pm_arena_t` - the arena allocator for AST-lifetime memory * * `pm_parser_t` - the main parser structure * * `pm_parser_init()` - initialize a parser * * `pm_parse()` - parse and return the root node - * * `pm_node_destroy()` - deallocate the root node returned by `pm_parse()` * * `pm_parser_free()` - free the internal memory of the parser + * * `pm_arena_free()` - free all AST-lifetime memory * * Putting all of this together would look something like: * * ```c * void parse(const uint8_t *source, size_t length) { + * pm_arena_t arena = { 0 }; * pm_parser_t parser; - * pm_parser_init(&parser, source, length, NULL); + * pm_parser_init(&arena, &parser, source, length, NULL); * * pm_node_t *root = pm_parse(&parser); * printf("PARSED!\n"); * - * pm_node_destroy(&parser, root); * pm_parser_free(&parser); + * pm_arena_free(&arena); * } * ``` * @@ -391,8 +398,9 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * * ```c * void prettyprint(const uint8_t *source, size_t length) { + * pm_arena_t arena = { 0 }; * pm_parser_t parser; - * pm_parser_init(&parser, source, length, NULL); + * pm_parser_init(&arena, &parser, source, length, NULL); * * pm_node_t *root = pm_parse(&parser); * pm_buffer_t buffer = { 0 }; @@ -401,8 +409,8 @@ PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint * printf("%*.s\n", (int) buffer.length, buffer.value); * * pm_buffer_free(&buffer); - * pm_node_destroy(&parser, root); * pm_parser_free(&parser); + * pm_arena_free(&arena); * } * ``` */ diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index e42a8e5b700e2d..5806742612066c 100644 --- a/prism/templates/src/node.c.erb +++ b/prism/templates/src/node.c.erb @@ -3,150 +3,73 @@ /** * Attempts to grow the node list to the next size. If there is already - * capacity in the list, this function does nothing. Otherwise it reallocates - * the list to be twice as large as it was before. If the reallocation fails, - * this function returns false, otherwise it returns true. + * capacity in the list, this function does nothing. Otherwise it allocates a + * new array from the arena (abandon-and-copy strategy) and copies the existing + * data into it. */ -static bool -pm_node_list_grow(pm_node_list_t *list, size_t size) { +static void +pm_node_list_grow(pm_arena_t *arena, pm_node_list_t *list, size_t size) { size_t requested_size = list->size + size; - // If the requested size caused overflow, return false. - if (requested_size < list->size) return false; + // Guard against overflow on the addition. + if (requested_size < list->size) abort(); - // If the requested size is within the existing capacity, return true. - if (requested_size < list->capacity) return true; + // If the requested size is within the existing capacity, return. + if (requested_size <= list->capacity) return; - // Otherwise, reallocate the list to be twice as large as it was before. + // Otherwise, compute the next capacity by doubling. size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2; - // If multiplying by 2 caused overflow, return false. - if (next_capacity < list->capacity) return false; - - // If we didn't get enough by doubling, keep doubling until we do. + // Guard against overflow on the doubling. while (requested_size > next_capacity) { - size_t double_capacity = next_capacity * 2; - - // Ensure we didn't overflow by multiplying by 2. - if (double_capacity < next_capacity) return false; - next_capacity = double_capacity; + if (next_capacity == 0) abort(); + next_capacity *= 2; } - pm_node_t **nodes = (pm_node_t **) xrealloc_sized( - list->nodes, - sizeof(pm_node_t *) * next_capacity, - sizeof(pm_node_t *) * list->capacity - ); - if (nodes == NULL) return false; + // Allocate a new array from the arena (old array is abandoned). + pm_node_t **nodes = (pm_node_t **) pm_arena_alloc(arena, sizeof(pm_node_t *) * next_capacity, PRISM_ALIGNOF(pm_node_t *)); + + // Copy old data into the new array. + if (list->size > 0) { + memcpy(nodes, list->nodes, list->size * sizeof(pm_node_t *)); + } list->nodes = nodes; list->capacity = next_capacity; - return true; } /** * Append a new node onto the end of the node list. */ void -pm_node_list_append(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list, 1)) { - list->nodes[list->size++] = node; - } +pm_node_list_append(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node) { + pm_node_list_grow(arena, list, 1); + list->nodes[list->size++] = node; } /** * Prepend a new node onto the beginning of the node list. */ void -pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) { - if (pm_node_list_grow(list, 1)) { - memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *)); - list->nodes[0] = node; - list->size++; - } +pm_node_list_prepend(pm_arena_t *arena, pm_node_list_t *list, pm_node_t *node) { + pm_node_list_grow(arena, list, 1); + memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *)); + list->nodes[0] = node; + list->size++; } /** * Concatenate the given node list onto the end of the other node list. */ void -pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) { - if (other->size > 0 && pm_node_list_grow(list, other->size)) { +pm_node_list_concat(pm_arena_t *arena, pm_node_list_t *list, pm_node_list_t *other) { + if (other->size > 0) { + pm_node_list_grow(arena, list, other->size); memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *)); list->size += other->size; } } -/** - * Free the internal memory associated with the given node list. - */ -void -pm_node_list_free(pm_node_list_t *list) { - if (list->capacity > 0) { - xfree_sized(list->nodes, sizeof(pm_node_t *) * list->capacity); - *list = (pm_node_list_t) { 0 }; - } -} - -PRISM_EXPORTED_FUNCTION void -pm_node_destroy(pm_parser_t *parser, pm_node_t *node); - -/** - * Destroy the nodes that are contained within the given node list. - */ -static void -pm_node_list_destroy(pm_parser_t *parser, pm_node_list_t *list) { - pm_node_t *node; - PM_NODE_LIST_FOREACH(list, index, node) pm_node_destroy(parser, node); - pm_node_list_free(list); -} - -/** - * Deallocate the space for a pm_node_t. Similarly to pm_node_alloc, we're not - * using the parser argument, but it's there to allow for the future possibility - * of pre-allocating larger memory pools. - */ -PRISM_EXPORTED_FUNCTION void -pm_node_destroy(pm_parser_t *parser, pm_node_t *node) { - switch (PM_NODE_TYPE(node)) { - <%- nodes.each do |node| -%> -#line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>" - case <%= node.type %>: { - <%- if node.fields.any? { |field| ![Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField].include?(field.class) } -%> - pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node; - <%- end -%> - <%- node.fields.each do |field| -%> - <%- case field -%> - <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField -%> - <%- when Prism::Template::NodeField -%> - pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>); - <%- when Prism::Template::OptionalNodeField -%> - if (cast-><%= field.name %> != NULL) { - pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>); - } - <%- when Prism::Template::StringField -%> - pm_string_free(&cast-><%= field.name %>); - <%- when Prism::Template::NodeListField -%> - pm_node_list_destroy(parser, &cast-><%= field.name %>); - <%- when Prism::Template::ConstantListField -%> - pm_constant_id_list_free(&cast-><%= field.name %>); - <%- when Prism::Template::IntegerField -%> - pm_integer_free(&cast-><%= field.name %>); - <%- else -%> - <%- raise -%> - <%- end -%> - <%- end -%> - xfree_sized(node, sizeof(pm_<%= node.human %>_t)); - break; - } - <%- end -%> -#line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>" - default: - assert(false && "unreachable"); - break; - } -} - /** * Returns a string representation of the given node type. */ diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 54a499d8bb4580..e58bb81f0ac89a 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -350,8 +350,9 @@ pm_serialize_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); pm_lex_callback_t lex_callback = (pm_lex_callback_t) { .data = (void *) buffer, @@ -359,15 +360,15 @@ pm_serialize_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const }; parser.lex_callback = &lex_callback; - pm_node_t *node = pm_parse(&parser); + pm_parse(&parser); // Append 0 to mark end of tokens. pm_buffer_append_byte(buffer, 0); pm_serialize_metadata(&parser, buffer); - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); } @@ -380,8 +381,9 @@ pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, pm_options_t options = { 0 }; pm_options_read(&options, data); + pm_arena_t arena = { 0 }; pm_parser_t parser; - pm_parser_init(&parser, source, size, &options); + pm_parser_init(&arena, &parser, source, size, &options); pm_lex_callback_t lex_callback = (pm_lex_callback_t) { .data = (void *) buffer, @@ -394,8 +396,8 @@ pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, pm_buffer_append_byte(buffer, 0); pm_serialize(&parser, node, buffer); - pm_node_destroy(&parser, node); pm_parser_free(&parser); + pm_arena_free(&arena); pm_options_free(&options); } diff --git a/prism/util/pm_arena.c b/prism/util/pm_arena.c new file mode 100644 index 00000000000000..a9b69b3c8d83d8 --- /dev/null +++ b/prism/util/pm_arena.c @@ -0,0 +1,105 @@ +#include "prism/util/pm_arena.h" + +/** + * Compute the block allocation size using offsetof so it is correct regardless + * of PM_FLEX_ARY_LEN. + */ +#define PM_ARENA_BLOCK_SIZE(data_size) (offsetof(pm_arena_block_t, data) + (data_size)) + +/** Initial block data size: 8 KB. */ +#define PM_ARENA_INITIAL_SIZE 8192 + +/** Double the block size every this many blocks. */ +#define PM_ARENA_GROWTH_INTERVAL 8 + +/** Maximum block data size: 1 MB. */ +#define PM_ARENA_MAX_SIZE (1024 * 1024) + +/** + * Compute the data size for the next block. + */ +static size_t +pm_arena_next_block_size(const pm_arena_t *arena, size_t min_size) { + size_t size = PM_ARENA_INITIAL_SIZE; + + for (size_t i = PM_ARENA_GROWTH_INTERVAL; i <= arena->block_count; i += PM_ARENA_GROWTH_INTERVAL) { + if (size < PM_ARENA_MAX_SIZE) size *= 2; + } + + return size > min_size ? size : min_size; +} + +/** + * Allocate memory from the arena. The returned memory is NOT zeroed. This + * function is infallible — it aborts on allocation failure. + */ +void * +pm_arena_alloc(pm_arena_t *arena, size_t size, size_t alignment) { + // Try current block. + if (arena->current != NULL) { + size_t used_aligned = (arena->current->used + alignment - 1) & ~(alignment - 1); + size_t needed = used_aligned + size; + + // Guard against overflow in the alignment or size arithmetic. + if (used_aligned >= arena->current->used && needed >= used_aligned && needed <= arena->current->capacity) { + arena->current->used = needed; + return arena->current->data + used_aligned; + } + } + + // Allocate new block via xmalloc — memory is NOT zeroed. + // New blocks from xmalloc are max-aligned, so data[] starts aligned for + // any C type. No padding needed at the start. + size_t block_data_size = pm_arena_next_block_size(arena, size); + pm_arena_block_t *block = (pm_arena_block_t *) xmalloc(PM_ARENA_BLOCK_SIZE(block_data_size)); + + if (block == NULL) { + fprintf(stderr, "prism: out of memory; aborting\n"); + abort(); + } + + block->capacity = block_data_size; + block->used = size; + block->prev = arena->current; + arena->current = block; + arena->block_count++; + + return block->data; +} + +/** + * Allocate zero-initialized memory from the arena. This function is infallible + * — it aborts on allocation failure. + */ +void * +pm_arena_zalloc(pm_arena_t *arena, size_t size, size_t alignment) { + void *ptr = pm_arena_alloc(arena, size, alignment); + memset(ptr, 0, size); + return ptr; +} + +/** + * Allocate memory from the arena and copy the given data into it. + */ +void * +pm_arena_memdup(pm_arena_t *arena, const void *src, size_t size, size_t alignment) { + void *dst = pm_arena_alloc(arena, size, alignment); + memcpy(dst, src, size); + return dst; +} + +/** + * Free all blocks in the arena. + */ +void +pm_arena_free(pm_arena_t *arena) { + pm_arena_block_t *block = arena->current; + + while (block != NULL) { + pm_arena_block_t *prev = block->prev; + xfree_sized(block, PM_ARENA_BLOCK_SIZE(block->capacity)); + block = prev; + } + + *arena = (pm_arena_t) { 0 }; +} diff --git a/prism/util/pm_arena.h b/prism/util/pm_arena.h new file mode 100644 index 00000000000000..f376d134590afe --- /dev/null +++ b/prism/util/pm_arena.h @@ -0,0 +1,89 @@ +/** + * @file pm_arena.h + * + * A bump allocator for the prism parser. + */ +#ifndef PRISM_ARENA_H +#define PRISM_ARENA_H + +#include "prism/defines.h" + +#include +#include +#include +#include + +/** + * A single block of memory in the arena. Blocks are linked via prev pointers so + * they can be freed by walking the chain. + */ +typedef struct pm_arena_block { + /** The previous block in the chain (for freeing). */ + struct pm_arena_block *prev; + + /** The total usable bytes in data[]. */ + size_t capacity; + + /** The number of bytes consumed so far. */ + size_t used; + + /** The block's data. */ + char data[PM_FLEX_ARY_LEN]; +} pm_arena_block_t; + +/** + * A bump allocator. Allocations are made by bumping a pointer within the + * current block. When a block is full, a new block is allocated and linked to + * the previous one. All blocks are freed at once by walking the chain. + */ +typedef struct { + /** The active block (allocate from here). */ + pm_arena_block_t *current; + + /** The number of blocks allocated. */ + size_t block_count; +} pm_arena_t; + +/** + * Allocate memory from the arena. The returned memory is NOT zeroed. This + * function is infallible — it aborts on allocation failure. + * + * @param arena The arena to allocate from. + * @param size The number of bytes to allocate. + * @param alignment The required alignment (must be a power of 2). + * @returns A pointer to the allocated memory. + */ +void * pm_arena_alloc(pm_arena_t *arena, size_t size, size_t alignment); + +/** + * Allocate zero-initialized memory from the arena. This function is infallible + * — it aborts on allocation failure. + * + * @param arena The arena to allocate from. + * @param size The number of bytes to allocate. + * @param alignment The required alignment (must be a power of 2). + * @returns A pointer to the allocated, zero-initialized memory. + */ +void * pm_arena_zalloc(pm_arena_t *arena, size_t size, size_t alignment); + +/** + * Allocate memory from the arena and copy the given data into it. This is a + * convenience wrapper around pm_arena_alloc + memcpy. + * + * @param arena The arena to allocate from. + * @param src The source data to copy. + * @param size The number of bytes to allocate and copy. + * @param alignment The required alignment (must be a power of 2). + * @returns A pointer to the allocated copy. + */ +void * pm_arena_memdup(pm_arena_t *arena, const void *src, size_t size, size_t alignment); + +/** + * Free all blocks in the arena. After this call, all pointers returned by + * pm_arena_alloc and pm_arena_zalloc are invalid. + * + * @param arena The arena to free. + */ +PRISM_EXPORTED_FUNCTION void pm_arena_free(pm_arena_t *arena); + +#endif diff --git a/prism/util/pm_constant_pool.c b/prism/util/pm_constant_pool.c index bde7f959ea4318..f7173dd062ecaf 100644 --- a/prism/util/pm_constant_pool.c +++ b/prism/util/pm_constant_pool.c @@ -1,4 +1,5 @@ #include "prism/util/pm_constant_pool.h" +#include "prism/util/pm_arena.h" /** * Initialize a list of constant ids. @@ -14,10 +15,9 @@ pm_constant_id_list_init(pm_constant_id_list_t *list) { * Initialize a list of constant ids with a given capacity. */ void -pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity) { +pm_constant_id_list_init_capacity(pm_arena_t *arena, pm_constant_id_list_t *list, size_t capacity) { if (capacity) { - list->ids = xcalloc(capacity, sizeof(pm_constant_id_t)); - if (list->ids == NULL) abort(); + list->ids = (pm_constant_id_t *) pm_arena_zalloc(arena, capacity * sizeof(pm_constant_id_t), PRISM_ALIGNOF(pm_constant_id_t)); } else { list->ids = NULL; } @@ -27,24 +27,23 @@ pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity) } /** - * Append a constant id to a list of constant ids. Returns false if any - * potential reallocations fail. + * Append a constant id to a list of constant ids. */ -bool -pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id) { +void +pm_constant_id_list_append(pm_arena_t *arena, pm_constant_id_list_t *list, pm_constant_id_t id) { if (list->size >= list->capacity) { - const size_t original_capacity = list->capacity; - list->capacity = list->capacity == 0 ? 8 : list->capacity * 2; - list->ids = (pm_constant_id_t *) xrealloc_sized( - list->ids, - sizeof(pm_constant_id_t) * list->capacity, - sizeof(pm_constant_id_t) * original_capacity - ); - if (list->ids == NULL) return false; + size_t new_capacity = list->capacity == 0 ? 8 : list->capacity * 2; + pm_constant_id_t *new_ids = (pm_constant_id_t *) pm_arena_alloc(arena, sizeof(pm_constant_id_t) * new_capacity, PRISM_ALIGNOF(pm_constant_id_t)); + + if (list->size > 0) { + memcpy(new_ids, list->ids, list->size * sizeof(pm_constant_id_t)); + } + + list->ids = new_ids; + list->capacity = new_capacity; } list->ids[list->size++] = id; - return true; } /** @@ -70,16 +69,6 @@ pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id) { return false; } -/** - * Free the memory associated with a list of constant ids. - */ -void -pm_constant_id_list_free(pm_constant_id_list_t *list) { - if (list->ids != NULL) { - xfree_sized(list->ids, list->capacity * sizeof(pm_constant_id_t)); - } -} - /** * A relatively simple hash function (djb2) that is used to hash strings. We are * optimizing here for simplicity and speed. diff --git a/prism/util/pm_constant_pool.h b/prism/util/pm_constant_pool.h index 6df23f8f501d38..1d4922a661dc37 100644 --- a/prism/util/pm_constant_pool.h +++ b/prism/util/pm_constant_pool.h @@ -11,6 +11,7 @@ #define PRISM_CONSTANT_POOL_H #include "prism/defines.h" +#include "prism/util/pm_arena.h" #include #include @@ -54,20 +55,20 @@ void pm_constant_id_list_init(pm_constant_id_list_t *list); /** * Initialize a list of constant ids with a given capacity. * + * @param arena The arena to allocate from. * @param list The list to initialize. * @param capacity The initial capacity of the list. */ -void pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity); +void pm_constant_id_list_init_capacity(pm_arena_t *arena, pm_constant_id_list_t *list, size_t capacity); /** - * Append a constant id to a list of constant ids. Returns false if any - * potential reallocations fail. + * Append a constant id to a list of constant ids. * + * @param arena The arena to allocate from. * @param list The list to append to. * @param id The id to append. - * @return Whether the append succeeded. */ -bool pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id); +void pm_constant_id_list_append(pm_arena_t *arena, pm_constant_id_list_t *list, pm_constant_id_t id); /** * Insert a constant id into a list of constant ids at the specified index. @@ -87,13 +88,6 @@ void pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_co */ bool pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id); -/** - * Free the memory associated with a list of constant ids. - * - * @param list The list to free. - */ -void pm_constant_id_list_free(pm_constant_id_list_t *list); - /** * The type of bucket in the constant pool hash map. This determines how the * bucket should be freed. diff --git a/prism_compile.c b/prism_compile.c index af0badd028645d..c7facf6e22548f 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -10555,16 +10555,13 @@ pm_iseq_compile_node(rb_iseq_t *iseq, pm_scope_node_t *node) void pm_parse_result_free(pm_parse_result_t *result) { - if (result->node.ast_node != NULL) { - pm_node_destroy(&result->parser, result->node.ast_node); - } - if (result->parsed) { SIZED_FREE_N(result->node.constants, result->node.parser->constant_pool.size); pm_scope_node_destroy(&result->node); } pm_parser_free(&result->parser); + pm_arena_free(&result->arena); pm_string_free(&result->input); pm_options_free(&result->options); } @@ -11440,7 +11437,7 @@ pm_parse_file(pm_parse_result_t *result, VALUE filepath, VALUE *script_lines) pm_options_version_for_current_ruby_set(&result->options); - pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); + pm_parser_init(&result->arena, &result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); pm_node_t *node = pm_parse(&result->parser); VALUE error = pm_parse_process(result, node, script_lines); @@ -11500,7 +11497,7 @@ pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE * pm_options_version_for_current_ruby_set(&result->options); - pm_parser_init(&result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); + pm_parser_init(&result->arena, &result->parser, pm_string_source(&result->input), pm_string_length(&result->input), &result->options); pm_node_t *node = pm_parse(&result->parser); return pm_parse_process(result, node, script_lines); @@ -11570,7 +11567,7 @@ pm_parse_stdin(pm_parse_result_t *result) }; pm_buffer_t buffer; - pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) &wrapped_stdin, pm_parse_stdin_fgets, pm_parse_stdin_eof, &result->options); + pm_node_t *node = pm_parse_stream(&result->arena, &result->parser, &buffer, (void *) &wrapped_stdin, pm_parse_stdin_fgets, pm_parse_stdin_eof, &result->options); // Copy the allocated buffer contents into the input string so that it gets // freed. At this point we've handed over ownership, so we don't need to diff --git a/prism_compile.h b/prism_compile.h index e588122205948b..ae1ec6bfd4f607 100644 --- a/prism_compile.h +++ b/prism_compile.h @@ -67,6 +67,9 @@ void pm_scope_node_init(const pm_node_t *node, pm_scope_node_t *scope, pm_scope_ void pm_scope_node_destroy(pm_scope_node_t *scope_node); typedef struct { + /** The arena allocator for AST-lifetime memory. */ + pm_arena_t arena; + /** The parser that will do the actual parsing. */ pm_parser_t parser; diff --git a/vm_eval.c b/vm_eval.c index 165472c969d31a..ea0972bc98be81 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1876,7 +1876,7 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, st_insert(parent_scope->index_lookup_table, (st_data_t) constant_id, (st_data_t) local_index); } - pm_constant_id_list_append(&parent_scope->locals, constant_id); + pm_constant_id_list_append(&result.arena, &parent_scope->locals, constant_id); } node->previous = parent_scope; @@ -1899,7 +1899,6 @@ pm_eval_make_iseq(VALUE src, VALUE fname, int line, pm_scope_node_t *prev = result.node.previous; while (prev) { pm_scope_node_t *next = prev->previous; - pm_constant_id_list_free(&prev->locals); pm_scope_node_destroy(prev); SIZED_FREE(prev); prev = next; From b78e0a6ddf7df8a7568ea71284f593423c739551 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 5 Mar 2026 12:23:07 -0500 Subject: [PATCH 12/87] ZJIT: Normalize to non-raw string literals in snapshots with `s/@r"/@"/` Since cargo-insta [version 1.45.0], raw string literals are only used when snapshot contents require them. This creates unnecessary diff hunks when updating older snapshots that still use raw literals. Normalize all snapshots to use non-raw string literals to reduce diff noise in future changes. [version 1.45.0]: https://github.com/mitsuhiko/insta/blob/1.45.0/CHANGELOG.md#1450 --- zjit/src/asm/arm64/mod.rs | 6 +- zjit/src/asm/x86_64/tests.rs | 84 ++--- zjit/src/backend/arm64/mod.rs | 66 ++-- zjit/src/backend/x86_64/mod.rs | 34 +- zjit/src/hir/opt_tests.rs | 550 ++++++++++++++++----------------- zjit/src/hir/tests.rs | 156 +++++----- 6 files changed, 448 insertions(+), 448 deletions(-) diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index 154e7ebd195da3..a360d7738b2dbf 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -1447,7 +1447,7 @@ mod tests { cbz(cb, X0, offset); cbz(cb, W0, offset); }); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cbz x0, #0xfffffffffffffffc 0x4: cbz w0, #0 "); @@ -1461,7 +1461,7 @@ mod tests { cbnz(cb, X20, offset); cbnz(cb, W20, offset); }); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: cbnz x20, #8 0x4: cbnz w20, #0xc "); @@ -1614,7 +1614,7 @@ mod tests { ldurh(cb, W10, A64Opnd::new_mem(64, X1, 0)); ldurh(cb, W10, A64Opnd::new_mem(64, X1, 123)); }); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldurh w10, [x1] 0x4: ldurh w10, [x1, #0x7b] "); diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs index 268fe6b1c093af..35bd3b005d4a15 100644 --- a/zjit/src/asm/x86_64/tests.rs +++ b/zjit/src/asm/x86_64/tests.rs @@ -38,7 +38,7 @@ fn test_add() { let cb15 = compile(|cb| add(cb, ECX, imm_opnd(8))); let cb16 = compile(|cb| add(cb, ECX, imm_opnd(255))); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @" 0x0: add cl, 3 0x0: add cl, bl 0x0: add cl, spl @@ -57,7 +57,7 @@ fn test_add() { 0x0: add ecx, 0xff "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16), @" 80c103 00d9 4000e1 @@ -92,7 +92,7 @@ fn test_add_unsigned() { let cb7 = compile(|cb| add(cb, R8, uimm_opnd(1))); let cb8 = compile(|cb| add(cb, R8, uimm_opnd(i32::MAX.try_into().unwrap()))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 0x0: add r8b, 1 0x0: add r8b, 0x7f 0x0: add r8w, 1 @@ -103,7 +103,7 @@ fn test_add_unsigned() { 0x0: add r8, 0x7fffffff "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 4180c001 4180c07f 664183c001 @@ -120,12 +120,12 @@ fn test_and() { let cb1 = compile(|cb| and(cb, EBP, R12D)); let cb2 = compile(|cb| and(cb, mem_opnd(64, RAX, 0), imm_opnd(0x08))); - assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @" 0x0: and ebp, r12d 0x0: and qword ptr [rax], 8 "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @" 4421e5 48832008 "); @@ -175,7 +175,7 @@ fn test_cmovcc() { let cb4 = compile(|cb| cmovl(cb, RBX, RBP)); let cb5 = compile(|cb| cmovle(cb, ESI, mem_opnd(32, RSP, 4))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @" 0x0: cmovg esi, edi 0x0: cmovg esi, dword ptr [rbp + 0xc] 0x0: cmovl eax, ecx @@ -183,7 +183,7 @@ fn test_cmovcc() { 0x0: cmovle esi, dword ptr [rsp + 4] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @" 0f4ff7 0f4f750c 0f4cc1 @@ -200,7 +200,7 @@ fn test_cmp() { let cb4 = compile(|cb| cmp(cb, RAX, imm_opnd(2))); let cb5 = compile(|cb| cmp(cb, ECX, uimm_opnd(0x8000_0000))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @" 0x0: cmp cl, dl 0x0: cmp ecx, edi 0x0: cmp rdx, qword ptr [r12] @@ -208,7 +208,7 @@ fn test_cmp() { 0x0: cmp ecx, 0x80000000 "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @" 38d1 39f9 493b1424 @@ -229,12 +229,12 @@ fn test_imul() { let cb1 = compile(|cb| imul(cb, RAX, RBX)); let cb2 = compile(|cb| imul(cb, RDX, mem_opnd(64, RAX, 0))); - assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @" 0x0: imul rax, rbx 0x0: imul rdx, qword ptr [rax] "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @" 480fafc3 480faf10 "); @@ -278,12 +278,12 @@ fn test_jmp_label() { cb.link_labels().unwrap(); }); - assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @" 0x0: jmp 5 0x0: jmp 0 "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @" e900000000 e9fbffffff "); @@ -315,14 +315,14 @@ fn test_lea() { let cb3 = compile(|cb| lea(cb, RAX, mem_opnd(8, RIP, 5))); let cb4 = compile(|cb| lea(cb, RDI, mem_opnd(8, RIP, 5))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @" 0x0: lea rdx, [rcx + 8] 0x0: lea rax, [rip] 0x0: lea rax, [rip + 5] 0x0: lea rdi, [rip + 5] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @" 488d5108 488d0500000000 488d0505000000 @@ -364,7 +364,7 @@ fn test_mov() { assert_disasm_snapshot!(disasms!( cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21, cb22, cb23, cb24, cb25, cb26, - ), @r" + ), @" 0x0: mov eax, 7 0x0: mov eax, 0xfffffffd 0x0: mov r15d, 3 @@ -396,7 +396,7 @@ fn test_mov() { assert_snapshot!(hexdumps!( cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21, cb22, cb23, cb24, cb25, cb26 - ), @r" + ), @" b807000000 b8fdffffff 41bf03000000 @@ -431,12 +431,12 @@ fn test_movabs() { let cb1 = compile(|cb| movabs(cb, R8, 0x34)); let cb2 = compile(|cb| movabs(cb, R8, 0x80000000)); - assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @" 0x0: movabs r8, 0x34 0x0: movabs r8, 0x80000000 "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @" 49b83400000000000000 49b80000008000000000 "); @@ -476,7 +476,7 @@ fn test_mov_unsigned() { // MOV r64, imm64, will not move down into 32 bit since it does not fit into 32 bits let cb21 = compile(|cb| mov(cb, R8, uimm_opnd(u64::MAX))); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @" 0x0: mov al, 1 0x0: mov al, 0xff 0x0: mov ax, 1 @@ -500,7 +500,7 @@ fn test_mov_unsigned() { 0x0: movabs r8, 0xffffffffffffffff "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @" b001 b0ff 66b80100 @@ -533,7 +533,7 @@ fn test_mov_iprel() { let cb4 = compile(|cb| mov(cb, RAX, mem_opnd(64, RIP, 5))); let cb5 = compile(|cb| mov(cb, RDI, mem_opnd(64, RIP, 5))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @" 0x0: mov eax, dword ptr [rip] 0x0: mov eax, dword ptr [rip + 5] 0x0: mov rax, qword ptr [rip] @@ -541,7 +541,7 @@ fn test_mov_iprel() { 0x0: mov rdi, qword ptr [rip + 5] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @" 8b0500000000 8b0505000000 488b0500000000 @@ -561,7 +561,7 @@ fn test_movsx() { let cb7 = compile(|cb| movsx(cb, RAX, mem_opnd(8, RSP, 0))); let cb8 = compile(|cb| movsx(cb, RDX, mem_opnd(16, R13, 4))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 0x0: movsx ax, al 0x0: movsx edx, al 0x0: movsx rax, bl @@ -572,7 +572,7 @@ fn test_movsx() { 0x0: movsx rdx, word ptr [r13 + 4] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 660fbec0 0fbed0 480fbec3 @@ -599,7 +599,7 @@ fn test_nop() { let cb11 = compile(|cb| nop(cb, 11)); let cb12 = compile(|cb| nop(cb, 12)); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @" 0x0: nop 0x0: nop 0x0: nop dword ptr [rax] @@ -617,7 +617,7 @@ fn test_nop() { 0x9: nop dword ptr [rax] "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12), @" 90 6690 0f1f00 @@ -653,7 +653,7 @@ fn test_not() { let cb16 = compile(|cb| not(cb, mem_opnd(32, RDX, -55))); let cb17 = compile(|cb| not(cb, mem_opnd(32, RDX, -555))); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @" 0x0: not ax 0x0: not eax 0x0: not qword ptr [r12] @@ -673,7 +673,7 @@ fn test_not() { 0x0: not dword ptr [rdx - 0x22b] "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17), @" 66f7d0 f7d0 49f71424 @@ -714,7 +714,7 @@ fn test_pop() { let cb09 = compile(|cb| pop(cb, mem_opnd_sib(64, RAX, RCX, 8, 3))); let cb10 = compile(|cb| pop(cb, mem_opnd_sib(64, R8, RCX, 8, 3))); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @" 0x0: pop rax 0x0: pop rbx 0x0: pop rsp @@ -727,7 +727,7 @@ fn test_pop() { 0x0: pop qword ptr [r8 + rcx*8 + 3] "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10), @" 58 5b 5c @@ -752,7 +752,7 @@ fn test_push() { let cb7 = compile(|cb| push(cb, mem_opnd_sib(64, RAX, RCX, 8, 3))); let cb8 = compile(|cb| push(cb, mem_opnd_sib(64, R8, RCX, 8, 3))); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 0x0: push rax 0x0: push rbx 0x0: push r12 @@ -763,7 +763,7 @@ fn test_push() { 0x0: push qword ptr [r8 + rcx*8 + 3] "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5, cb6, cb7, cb8), @" 50 53 4154 @@ -790,7 +790,7 @@ fn test_sal() { let cb4 = compile(|cb| sal(cb, mem_opnd(32, RSP, 68), uimm_opnd(1))); let cb5 = compile(|cb| sal(cb, RCX, CL)); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4, cb5), @" 0x0: shl cx, 1 0x0: shl ecx, 1 0x0: shl ebp, 5 @@ -798,7 +798,7 @@ fn test_sal() { 0x0: shl rcx, cl "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4, cb5), @" 66d1e1 d1e1 c1e505 @@ -826,12 +826,12 @@ fn test_sub() { let cb1 = compile(|cb| sub(cb, EAX, imm_opnd(1))); let cb2 = compile(|cb| sub(cb, RAX, imm_opnd(2))); - assert_disasm_snapshot!(disasms!(cb1, cb2), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2), @" 0x0: sub eax, 1 0x0: sub rax, 2 "); - assert_snapshot!(hexdumps!(cb1, cb2), @r" + assert_snapshot!(hexdumps!(cb1, cb2), @" 83e801 4883e802 "); @@ -867,7 +867,7 @@ fn test_test() { let cb18 = compile(|cb| test(cb, mem_opnd(64, RSI, 64), imm_opnd(0x08))); let cb19 = compile(|cb| test(cb, RCX, imm_opnd(0x08))); - assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @r" + assert_disasm_snapshot!(disasms!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @" 0x0: test al, al 0x0: test ax, ax 0x0: test cl, 8 @@ -889,7 +889,7 @@ fn test_test() { 0x0: test rcx, 8 "); - assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @r" + assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19), @" 84c0 6685c0 f6c108 @@ -919,14 +919,14 @@ fn test_xchg() { let cb3 = compile(|cb| xchg(cb, RCX, RBX)); let cb4 = compile(|cb| xchg(cb, R9, R15)); - assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @r" + assert_disasm_snapshot!(disasms!(cb1, cb2, cb3, cb4), @" 0x0: xchg rcx, rax 0x0: xchg r13, rax 0x0: xchg rcx, rbx 0x0: xchg r9, r15 "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3, cb4), @" 4891 4995 4887d9 diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 20e80ebcd1b173..8e7e7b6e2a7170 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1799,7 +1799,7 @@ mod tests { asm.write_label(forward); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [sp] 0x4: cmp x0, #0 0x8: b.gt #0x10 @@ -1882,7 +1882,7 @@ mod tests { asm.compile_with_regs(&mut cb, vec![X3_REG]).unwrap(); // Assert that only 2 instructions were written. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adds x3, x0, x1 0x4: stur x3, [x2] "); @@ -1898,7 +1898,7 @@ mod tests { // Testing that we pad the string to the nearest 4-byte boundary to make // it easier to jump over. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldnp d8, d25, [x10, #-0x140] 0x4: .byte 0x6f, 0x2c, 0x20, 0x77 0x8: .byte 0x6f, 0x72, 0x6c, 0x64 @@ -1915,7 +1915,7 @@ mod tests { asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: stp x29, x30, [sp, #-0x10]! 0x4: mov x29, sp 0x8: mov sp, x29 @@ -1958,7 +1958,7 @@ mod tests { cb }; - assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2, cb3), @r" + assert_disasm_snapshot!(disasms_with!("\n", cb1, cb2, cb3), @" 0x0: stp x29, x30, [sp, #-0x10]! 0x4: mov x29, sp 0x8: stp x20, x19, [sp, #-0x10]! @@ -1989,7 +1989,7 @@ mod tests { 0x1c: mov sp, x29 0x20: ldp x29, x30, [sp], #0x10 "); - assert_snapshot!(hexdumps!(cb1, cb2, cb3), @r" + assert_snapshot!(hexdumps!(cb1, cb2, cb3), @" fd7bbfa9fd030091f44fbfa9f5831ff8ff8300d1b44f7fa9b5835ef8bf030091fd7bc1a8 fd7bbfa9fd030091f44fbfa9f5831ff8ffc300d1b44f7fa9b5835ef8bf030091fd7bc1a8 fd7bbfa9fd030091f44fbfa9f657bfa9ff8300d1b44f7fa9b6577ea9bf030091fd7bc1a8 @@ -2005,7 +2005,7 @@ mod tests { asm.je(Target::CodePtr(target)); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: b.eq #0x50 0x4: nop 0x8: nop @@ -2026,7 +2026,7 @@ mod tests { asm.je(Target::CodePtr(target)); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: b.ne #8 0x4: b #0x200000 0x8: nop @@ -2052,7 +2052,7 @@ mod tests { } asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #0x7fffffff 0x4: add x0, sp, x0 0x8: mov x0, #8 @@ -2084,7 +2084,7 @@ mod tests { asm.store(Opnd::mem(VALUE_BITS, NATIVE_STACK_PTR, 0), result); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [sp] 0x4: mov x16, #0x1f40 0x8: add x0, x0, x16, uxtx @@ -2106,7 +2106,7 @@ mod tests { asm.store(large_mem, large_mem); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: sub x16, sp, #0x305 0x4: ldur x16, [x16] 0x8: stur x16, [x0] @@ -2152,7 +2152,7 @@ mod tests { asm.store(Opnd::mem(64, scratch_reg, 0), 0x83902.into()); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, #0x3902 0x4: movk x16, #8, lsl #16 0x8: stur x16, [x15] @@ -2194,7 +2194,7 @@ mod tests { asm.store(Opnd::mem(64, SP, 0), opnd); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: adr x16, #8 0x4: mov x0, x16 0x8: ldnp d8, d25, [x10, #-0x140] @@ -2215,7 +2215,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that two instructions were written: LDUR and STUR. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur x0, [x21] 0x4: stur x0, [x21] "); @@ -2231,7 +2231,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that three instructions were written: ADD, LDUR, and STUR. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x21, #0x400 0x4: ldur x0, [x0] 0x8: stur x0, [x21] @@ -2248,7 +2248,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that three instructions were written: MOVZ, ADD, LDUR, and STUR. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #0x1001 0x4: add x0, x21, x0, uxtx 0x8: ldur x0, [x0] @@ -2267,7 +2267,7 @@ mod tests { // Assert that only two instructions were written since the value is an // immediate. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #4 0x4: stur x0, [x21] "); @@ -2284,7 +2284,7 @@ mod tests { // Assert that five instructions were written since the value is not an // immediate and needs to be loaded into a register. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x0, #8 0x4: b #0x10 0x8: eon x0, x0, x30, ror #0 @@ -2303,7 +2303,7 @@ mod tests { // so this needs one register asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #0xffffffff 0x4: tst w0, w0 "); @@ -2329,7 +2329,7 @@ mod tests { asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr x0, x0, x1 0x4: stur x0, [x2] "); @@ -2344,7 +2344,7 @@ mod tests { asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: lsl x0, x0, #5 0x4: stur x0, [x2] "); @@ -2359,7 +2359,7 @@ mod tests { asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: asr x0, x0, #5 0x4: stur x0, [x2] "); @@ -2374,7 +2374,7 @@ mod tests { asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), opnd); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: lsr x0, x0, #5 0x4: stur x0, [x2] "); @@ -2413,7 +2413,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that a load and a test instruction were written. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #5 0x4: tst x0, x0 "); @@ -2440,7 +2440,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); // Assert that a load and a test instruction were written. - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x0, #5 0x4: tst x0, x0 "); @@ -2467,7 +2467,7 @@ mod tests { asm.cmp(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldur w0, [x0, #6] 0x4: mov x1, #0x1001 0x8: cmp w0, w1 @@ -2483,7 +2483,7 @@ mod tests { asm.store(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, #0x1001 0x4: sturh w16, [x0] "); @@ -2498,7 +2498,7 @@ mod tests { asm.store(shape_opnd, Opnd::UImm(4097)); asm.compile_with_num_regs(&mut cb, 2); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, #0x1001 0x4: stur w16, [x0, #6] "); @@ -2640,7 +2640,7 @@ mod tests { asm.store(Opnd::mem(8, C_RET_OPND, 0), Opnd::mem(8, C_RET_OPND, 8)); asm.compile_with_num_regs(&mut cb, 0); // spill every VReg - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldurb w16, [x0, #8] 0x4: sturb w16, [x0] "); @@ -2846,7 +2846,7 @@ mod tests { asm.mov(C_RET_OPND, out_vreg); asm.compile_with_num_regs(&mut cb, 0); // spill every VReg - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x16, #1 0x4: stur x16, [x29, #-8] 0x8: ldur x15, [x29, #-8] @@ -2864,7 +2864,7 @@ mod tests { let _ = asm.load(Opnd::mem(16, C_RET_OPND, 0x200)); asm.compile(&mut cb).unwrap(); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x0, #0x200 0x4: ldurh w0, [x0] "); @@ -2878,7 +2878,7 @@ mod tests { let _ = asm.load(Opnd::mem(32, C_RET_OPND, 0x200)); asm.compile(&mut cb).unwrap(); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x0, #0x200 0x4: ldur w0, [x0] "); @@ -2892,7 +2892,7 @@ mod tests { let _ = asm.load(Opnd::mem(64, C_RET_OPND, 0x200)); asm.compile(&mut cb).unwrap(); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: add x0, x0, #0x200 0x4: ldur x0, [x0] "); diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 138df69008fe2c..ee9240dbd70746 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -1198,7 +1198,7 @@ mod tests { let _ = asm.add(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: add rax, 0xff "); @@ -1213,7 +1213,7 @@ mod tests { let _ = asm.add(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: add rax, r11 @@ -1229,7 +1229,7 @@ mod tests { let _ = asm.and(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: and rax, 0xff "); @@ -1244,7 +1244,7 @@ mod tests { let _ = asm.and(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: and rax, r11 @@ -1270,7 +1270,7 @@ mod tests { asm.cmp(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movabs r11, 0xffffffffffff 0xa: cmp rax, r11 "); @@ -1322,7 +1322,7 @@ mod tests { let _ = asm.or(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: or rax, 0xff "); @@ -1337,7 +1337,7 @@ mod tests { let _ = asm.or(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: or rax, r11 @@ -1353,7 +1353,7 @@ mod tests { let _ = asm.sub(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: sub rax, 0xff "); @@ -1368,7 +1368,7 @@ mod tests { let _ = asm.sub(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: sub rax, r11 @@ -1394,7 +1394,7 @@ mod tests { asm.test(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movabs r11, 0xffffffffffff 0xa: test rax, r11 "); @@ -1409,7 +1409,7 @@ mod tests { let _ = asm.xor(Opnd::Reg(RAX_REG), Opnd::UImm(0xFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: xor rax, 0xff "); @@ -1424,7 +1424,7 @@ mod tests { let _ = asm.xor(Opnd::Reg(RAX_REG), Opnd::UImm(0xFFFF_FFFF_FFFF)); asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, rax 0x3: movabs r11, 0xffffffffffff 0xd: xor rax, r11 @@ -1453,7 +1453,7 @@ mod tests { asm.mov(Opnd::mem(64, SP, 0), sp); // should NOT be merged to lea asm.compile_with_num_regs(&mut cb, 1); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: movabs r11, 0xffffffffffff 0xa: cmp rax, r11 "); @@ -1471,7 +1471,7 @@ mod tests { asm.mov(Opnd::Reg(RAX_REG), result); asm.compile_with_num_regs(&mut cb, 2); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov rax, qword ptr [rbx + 8] 0x4: test rax, rax 0x7: mov eax, 0x14 @@ -1575,7 +1575,7 @@ mod tests { ]); asm.compile_with_num_regs(&mut cb, ALLOC_REGS.len()); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov eax, 0 0x5: call rax "); @@ -1869,7 +1869,7 @@ mod tests { asm.cret(C_RET_OPND); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: push rbp 0x1: mov rbp, rsp 0x4: push r13 @@ -1894,7 +1894,7 @@ mod tests { asm.frame_teardown(&[]); asm.compile_with_num_regs(&mut cb, 0); - assert_disasm_snapshot!(cb.disasm(), @r" + assert_disasm_snapshot!(cb.disasm(), @" 0x0: push rbp 0x1: mov rbp, rsp 0x4: sub rsp, 0x30 diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 16298db6a28871..b7b11a47f77d2e 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -38,7 +38,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -72,7 +72,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -101,7 +101,7 @@ mod hir_opt_tests { 1 + 2 + 3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -130,7 +130,7 @@ mod hir_opt_tests { 5 - 3 - 1 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -159,7 +159,7 @@ mod hir_opt_tests { 0 - 1073741825 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -187,7 +187,7 @@ mod hir_opt_tests { 6 * 7 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -255,7 +255,7 @@ mod hir_opt_tests { 0 % 0 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -283,7 +283,7 @@ mod hir_opt_tests { 11 % 0 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -311,7 +311,7 @@ mod hir_opt_tests { 0 % 11 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -339,7 +339,7 @@ mod hir_opt_tests { 11 % 3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -367,7 +367,7 @@ mod hir_opt_tests { -7 % 3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -395,7 +395,7 @@ mod hir_opt_tests { 7 % -3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -423,7 +423,7 @@ mod hir_opt_tests { -7 % -3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -663,7 +663,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -697,7 +697,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -734,7 +734,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -768,7 +768,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -805,7 +805,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -839,7 +839,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -873,7 +873,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -908,7 +908,7 @@ mod hir_opt_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -957,7 +957,7 @@ mod hir_opt_tests { custom.count "); assert_eq!(VALUE::fixnum_from_usize(2), result); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:13: bb1(): EntryPoint interpreter @@ -1120,7 +1120,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -1149,7 +1149,7 @@ mod hir_opt_tests { def test = baz test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -1178,7 +1178,7 @@ mod hir_opt_tests { def test = baz test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -1216,7 +1216,7 @@ mod hir_opt_tests { def test = foo(1) test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:8: bb1(): EntryPoint interpreter @@ -1282,7 +1282,7 @@ mod hir_opt_tests { def test(o) = o.bar { |x| x } test C.new; test C.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -1352,7 +1352,7 @@ mod hir_opt_tests { test; test undef :foo "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -1379,7 +1379,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -1407,7 +1407,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1437,7 +1437,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -1470,7 +1470,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -1501,7 +1501,7 @@ mod hir_opt_tests { def test = foo test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1528,7 +1528,7 @@ mod hir_opt_tests { def test = foo 3 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1556,7 +1556,7 @@ mod hir_opt_tests { def test = foo 3, 4 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1586,7 +1586,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1628,7 +1628,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1818,7 +1818,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1844,7 +1844,7 @@ mod hir_opt_tests { eval(" def test = 1[2, 3] "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -1870,7 +1870,7 @@ mod hir_opt_tests { eval(r#" def test = 1["x"] "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -1992,7 +1992,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2023,7 +2023,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2173,7 +2173,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2273,7 +2273,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2302,7 +2302,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2367,7 +2367,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2433,7 +2433,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2462,7 +2462,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2492,7 +2492,7 @@ mod hir_opt_tests { end test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2520,7 +2520,7 @@ mod hir_opt_tests { end test; test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2937,7 +2937,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3019,7 +3019,7 @@ mod hir_opt_tests { eval(" def test = [].itself "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3047,7 +3047,7 @@ mod hir_opt_tests { 1 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3081,7 +3081,7 @@ mod hir_opt_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -3116,7 +3116,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3144,7 +3144,7 @@ mod hir_opt_tests { def test = C test # Warm the constant cache "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3169,7 +3169,7 @@ mod hir_opt_tests { def test = [String, Class, Module, BasicObject] test # Warm the constant cache "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3201,7 +3201,7 @@ mod hir_opt_tests { def test = [Enumerable, Kernel] test # Warm the constant cache "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3231,7 +3231,7 @@ mod hir_opt_tests { def test = MY_MODULE test # Warm the constant cache "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -3258,7 +3258,7 @@ mod hir_opt_tests { 5 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3287,7 +3287,7 @@ mod hir_opt_tests { test rescue 0 "); // Not specialized - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3312,7 +3312,7 @@ mod hir_opt_tests { def test = block_given? test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3340,7 +3340,7 @@ mod hir_opt_tests { TEST = proc { block_given? } TEST.call "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn block in @:2: bb1(): EntryPoint interpreter @@ -3370,7 +3370,7 @@ mod hir_opt_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3462,7 +3462,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3499,7 +3499,7 @@ mod hir_opt_tests { test c "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -3531,7 +3531,7 @@ mod hir_opt_tests { test "); assert_eq!(VALUE::fixnum_from_usize(3), result); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3565,7 +3565,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -3599,7 +3599,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3625,7 +3625,7 @@ mod hir_opt_tests { def test = foo(10) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3652,7 +3652,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3682,7 +3682,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3713,7 +3713,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3744,7 +3744,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3774,7 +3774,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3814,7 +3814,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3854,7 +3854,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3899,7 +3899,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3928,7 +3928,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3955,7 +3955,7 @@ mod hir_opt_tests { begin; test; rescue ArgumentError; end begin; test; rescue ArgumentError; end "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3982,7 +3982,7 @@ mod hir_opt_tests { begin; test; rescue ArgumentError; end begin; test; rescue ArgumentError; end "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4009,7 +4009,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4038,7 +4038,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4066,7 +4066,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4091,7 +4091,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4157,7 +4157,7 @@ mod hir_opt_tests { eval(" def test = Kernel "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4181,7 +4181,7 @@ mod hir_opt_tests { test Kernel = 5 "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4204,7 +4204,7 @@ mod hir_opt_tests { def test = Kernel test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4235,7 +4235,7 @@ mod hir_opt_tests { def test = Foo::Bar::C test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:8: bb1(): EntryPoint interpreter @@ -4261,7 +4261,7 @@ mod hir_opt_tests { def test = C.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4299,7 +4299,7 @@ mod hir_opt_tests { def test = C.new 1 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -4332,7 +4332,7 @@ mod hir_opt_tests { def test = Object.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4365,7 +4365,7 @@ mod hir_opt_tests { def test = BasicObject.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4398,7 +4398,7 @@ mod hir_opt_tests { def test = Hash.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4430,7 +4430,7 @@ mod hir_opt_tests { def test = Array.new 1 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4461,7 +4461,7 @@ mod hir_opt_tests { def test = Set.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4494,7 +4494,7 @@ mod hir_opt_tests { def test = String.new test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4524,7 +4524,7 @@ mod hir_opt_tests { def test = Regexp.new '' test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4557,7 +4557,7 @@ mod hir_opt_tests { eval(" def test(a,b) = [a,b].length "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4589,7 +4589,7 @@ mod hir_opt_tests { eval(" def test(a,b) = [a,b].size "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4621,7 +4621,7 @@ mod hir_opt_tests { eval(" def test(&block) = tap(&block) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4721,7 +4721,7 @@ mod hir_opt_tests { eval(" def test = @foo "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4745,7 +4745,7 @@ mod hir_opt_tests { eval(" def test = @foo = 1 "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4772,7 +4772,7 @@ mod hir_opt_tests { def test = defined?(@foo) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4798,7 +4798,7 @@ mod hir_opt_tests { def test = defined?(@foo) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4827,7 +4827,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -4860,7 +4860,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -4893,7 +4893,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -4926,7 +4926,7 @@ mod hir_opt_tests { end test p "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -5027,7 +5027,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5060,7 +5060,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5094,7 +5094,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "#); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5119,7 +5119,7 @@ mod hir_opt_tests { def test = @foo = 5 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5148,7 +5148,7 @@ mod hir_opt_tests { def test = @foo = 5 test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5182,7 +5182,7 @@ mod hir_opt_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5227,7 +5227,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5262,7 +5262,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5298,7 +5298,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "#); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5331,7 +5331,7 @@ mod hir_opt_tests { AboutToBeTooComplex.new.test TEST = AboutToBeTooComplex.instance_method(:test) "#); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5356,7 +5356,7 @@ mod hir_opt_tests { eval(" def test = {}.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5382,7 +5382,7 @@ mod hir_opt_tests { end def test = {}.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -5402,7 +5402,7 @@ mod hir_opt_tests { eval(" def test = {}.freeze.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5425,7 +5425,7 @@ mod hir_opt_tests { eval(" def test = {}.dup.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5451,7 +5451,7 @@ mod hir_opt_tests { eval(" def test = {}.freeze(nil) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5475,7 +5475,7 @@ mod hir_opt_tests { eval(" def test = [].freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5498,7 +5498,7 @@ mod hir_opt_tests { eval(" def test = [].freeze.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5521,7 +5521,7 @@ mod hir_opt_tests { eval(" def test = [].dup.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5547,7 +5547,7 @@ mod hir_opt_tests { eval(" def test = [].freeze(nil) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5571,7 +5571,7 @@ mod hir_opt_tests { eval(" def test = ''.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5594,7 +5594,7 @@ mod hir_opt_tests { eval(" def test = ''.freeze.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5617,7 +5617,7 @@ mod hir_opt_tests { eval(" def test = ''.dup.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5644,7 +5644,7 @@ mod hir_opt_tests { eval(" def test = ''.freeze(nil) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5669,7 +5669,7 @@ mod hir_opt_tests { eval(" def test = -'' "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5692,7 +5692,7 @@ mod hir_opt_tests { eval(" def test = -''.freeze "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5716,7 +5716,7 @@ mod hir_opt_tests { eval(" def test = -''.dup "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5743,7 +5743,7 @@ mod hir_opt_tests { eval(r##" def test = "#{('foo')}" "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5768,7 +5768,7 @@ mod hir_opt_tests { eval(r##" def test = "#{1}" "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -5900,7 +5900,7 @@ mod hir_opt_tests { end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5930,7 +5930,7 @@ mod hir_opt_tests { end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5960,7 +5960,7 @@ mod hir_opt_tests { def test = S[0] test "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -5996,7 +5996,7 @@ mod hir_opt_tests { eval(r##" def test = [4,5,6].freeze[1] "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6029,7 +6029,7 @@ mod hir_opt_tests { eval(r##" def test = [4,5,6].freeze[-3] "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6062,7 +6062,7 @@ mod hir_opt_tests { eval(r##" def test = [4,5,6].freeze[-10] "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6095,7 +6095,7 @@ mod hir_opt_tests { eval(r##" def test = [4,5,6].freeze[10] "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6131,7 +6131,7 @@ mod hir_opt_tests { end def test = [4,5,6].freeze[10] "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -6194,7 +6194,7 @@ mod hir_opt_tests { end def test = [4,5,6].max "##); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -6225,7 +6225,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -6249,7 +6249,7 @@ mod hir_opt_tests { eval(" def test = /a/ "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6275,7 +6275,7 @@ mod hir_opt_tests { def test = one(zero) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -6307,7 +6307,7 @@ mod hir_opt_tests { def test = identity(100) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -6332,7 +6332,7 @@ mod hir_opt_tests { def test = (bmethod {}) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -6360,7 +6360,7 @@ mod hir_opt_tests { def test = Foo.identity(100) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -6388,7 +6388,7 @@ mod hir_opt_tests { eval(" def test = nil.nil? "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6416,7 +6416,7 @@ mod hir_opt_tests { 1 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -6441,7 +6441,7 @@ mod hir_opt_tests { eval(" def test = 1.nil? "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -6469,7 +6469,7 @@ mod hir_opt_tests { 2 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -6909,7 +6909,7 @@ mod hir_opt_tests { test(C.new, C.new) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7011,7 +7011,7 @@ mod hir_opt_tests { test; test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7047,7 +7047,7 @@ mod hir_opt_tests { test O test O "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:10: bb1(): EntryPoint interpreter @@ -7090,7 +7090,7 @@ mod hir_opt_tests { test O test O "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:13: bb1(): EntryPoint interpreter @@ -7125,7 +7125,7 @@ mod hir_opt_tests { end M.test "); - assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + assert_snapshot!(hir_string_proc("M.method(:test)"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -7160,7 +7160,7 @@ mod hir_opt_tests { end M.test "#); - assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + assert_snapshot!(hir_string_proc("M.method(:test)"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -7193,7 +7193,7 @@ mod hir_opt_tests { end C.test "); - assert_snapshot!(hir_string_proc("C.method(:test)"), @r" + assert_snapshot!(hir_string_proc("C.method(:test)"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -7228,7 +7228,7 @@ mod hir_opt_tests { end C.test "#); - assert_snapshot!(hir_string_proc("C.method(:test)"), @r" + assert_snapshot!(hir_string_proc("C.method(:test)"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -7264,7 +7264,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7299,7 +7299,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7335,7 +7335,7 @@ mod hir_opt_tests { obj.test TEST = C.instance_method(:test) "); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7368,7 +7368,7 @@ mod hir_opt_tests { Ractor.new {}.value M.test "); - assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + assert_snapshot!(hir_string_proc("M.method(:test)"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -7396,7 +7396,7 @@ mod hir_opt_tests { Ractor.new {}.value M.test "); - assert_snapshot!(hir_string_proc("M.method(:test)"), @r" + assert_snapshot!(hir_string_proc("M.method(:test)"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -7439,7 +7439,7 @@ mod hir_opt_tests { test O1 test O2 "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:20: bb1(): EntryPoint interpreter @@ -7495,7 +7495,7 @@ mod hir_opt_tests { def test(o) = o.foo test obj "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:12: bb1(): EntryPoint interpreter @@ -7596,7 +7596,7 @@ mod hir_opt_tests { def test(&block) = [].map(&block) test { |x| x }; test { |x| x } "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -7668,7 +7668,7 @@ mod hir_opt_tests { end test; test "#); - assert_snapshot!(hir_string_proc("test"), @r" + assert_snapshot!(hir_string_proc("test"), @" fn block in test@:4: bb1(): EntryPoint interpreter @@ -7704,7 +7704,7 @@ mod hir_opt_tests { test; test "#); assert_eq!(VALUE::fixnum_from_usize(2), result); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -7736,7 +7736,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -7772,7 +7772,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -7807,7 +7807,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -7843,7 +7843,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -7879,7 +7879,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -7918,7 +7918,7 @@ mod hir_opt_tests { test C.new test C.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -7954,7 +7954,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -7985,7 +7985,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8020,7 +8020,7 @@ mod hir_opt_tests { test C.new test C.new "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -8052,7 +8052,7 @@ mod hir_opt_tests { test C.new, value test C.new, value "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8089,7 +8089,7 @@ mod hir_opt_tests { test C.new, value test C.new, value "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8123,7 +8123,7 @@ mod hir_opt_tests { eval(r#" def test = [].reverse "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8151,7 +8151,7 @@ mod hir_opt_tests { 5 end "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8176,7 +8176,7 @@ mod hir_opt_tests { eval(r#" def test = [].join "," "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8203,7 +8203,7 @@ mod hir_opt_tests { eval(r#" def test = "".to_s "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8229,7 +8229,7 @@ mod hir_opt_tests { eval(r#" def test = "foo".to_s "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8313,7 +8313,7 @@ mod hir_opt_tests { def test(x) = x.to_s test (2**65) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8373,7 +8373,7 @@ mod hir_opt_tests { arr[0] end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8492,7 +8492,7 @@ mod hir_opt_tests { arr[1] end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8592,7 +8592,7 @@ mod hir_opt_tests { def test = H[:a] test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8624,7 +8624,7 @@ mod hir_opt_tests { h[1] = 3 end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -8728,7 +8728,7 @@ mod hir_opt_tests { def test = Thread.current test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -8990,7 +8990,7 @@ mod hir_opt_tests { test = PushSubArray.new test << 1 "); - assert_snapshot!(hir_string_proc("PushSubArray.new.method(:<<)"), @r" + assert_snapshot!(hir_string_proc("PushSubArray.new.method(:<<)"), @" fn <<@:3: bb1(): EntryPoint interpreter @@ -9027,7 +9027,7 @@ mod hir_opt_tests { test = PopSubArray.new([1]) test.pop "); - assert_snapshot!(hir_string_proc("PopSubArray.new.method(:pop)"), @r" + assert_snapshot!(hir_string_proc("PopSubArray.new.method(:pop)"), @" fn pop@:3: bb1(): EntryPoint interpreter @@ -9068,7 +9068,7 @@ mod hir_opt_tests { test = ArefSubArray.new([1]) test[0] "); - assert_snapshot!(hir_string_proc("ArefSubArray.new.method(:[])"), @r" + assert_snapshot!(hir_string_proc("ArefSubArray.new.method(:[])"), @" fn []@:3: bb1(): EntryPoint interpreter @@ -9116,7 +9116,7 @@ mod hir_opt_tests { test = ArefSubArrayRange.new([1, 2, 3]) test[0..1] "); - assert_snapshot!(hir_string_proc("ArefSubArrayRange.new.method(:[])"), @r" + assert_snapshot!(hir_string_proc("ArefSubArrayRange.new.method(:[])"), @" fn []@:3: bb1(): EntryPoint interpreter @@ -9149,7 +9149,7 @@ mod hir_opt_tests { test([]) "); assert_contains_opcode("test", YARVINSN_opt_length); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -9181,7 +9181,7 @@ mod hir_opt_tests { test([]) "); assert_contains_opcode("test", YARVINSN_opt_size); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -9549,7 +9549,7 @@ mod hir_opt_tests { test(4 << 70) "); assert_contains_opcode("test", YARVINSN_opt_succ); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -9950,7 +9950,7 @@ mod hir_opt_tests { eval(r#" def test = "iron" << :a "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -9978,7 +9978,7 @@ mod hir_opt_tests { public def foo(lead, opt=raise) = opt def test = 0.foo(3, 3, 3) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10035,7 +10035,7 @@ mod hir_opt_tests { public def foo(lead, opt=raise) = opt def test = 0.foo "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10058,7 +10058,7 @@ mod hir_opt_tests { eval(" def test = 4.succ 1 "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -10150,7 +10150,7 @@ mod hir_opt_tests { def test(x, y) = x ^ y test(4 << 70, 1) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -10327,7 +10327,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -10361,7 +10361,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -10398,7 +10398,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -10434,7 +10434,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, false) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -10471,7 +10471,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, nil) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -10508,7 +10508,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, true) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -10544,7 +10544,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, 4) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -10580,7 +10580,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo, nil) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -10615,7 +10615,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -10653,7 +10653,7 @@ mod hir_opt_tests { def test(o) = o.respond_to?(:foo) test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:7: bb1(): EntryPoint interpreter @@ -10684,7 +10684,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10712,7 +10712,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -10740,7 +10740,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10768,7 +10768,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10796,7 +10796,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10824,7 +10824,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10852,7 +10852,7 @@ mod hir_opt_tests { def test = callee test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10880,7 +10880,7 @@ mod hir_opt_tests { def test = callee 3 test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10908,7 +10908,7 @@ mod hir_opt_tests { def test = callee 1, 2, 3 test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -10945,7 +10945,7 @@ mod hir_opt_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -11043,7 +11043,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -11074,7 +11074,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -11105,7 +11105,7 @@ mod hir_opt_tests { end test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -11136,7 +11136,7 @@ mod hir_opt_tests { end puts test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -11833,7 +11833,7 @@ mod hir_opt_tests { def test = 5.is_a?(Integer) test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -11862,7 +11862,7 @@ mod hir_opt_tests { def test = 5.is_a?(String) test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -11893,7 +11893,7 @@ mod hir_opt_tests { def test = O.is_a?(Array) test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -11926,7 +11926,7 @@ mod hir_opt_tests { def test = O.is_a?(C) test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -11958,7 +11958,7 @@ mod hir_opt_tests { def test = O.is_a?(Symbol) test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -11989,7 +11989,7 @@ mod hir_opt_tests { def test = fancy(1) test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -12017,7 +12017,7 @@ mod hir_opt_tests { def call_forwardable = forwardable call_forwardable "); - assert_snapshot!(hir_string("call_forwardable"), @r" + assert_snapshot!(hir_string("call_forwardable"), @" fn call_forwardable@:3: bb1(): EntryPoint interpreter @@ -12088,7 +12088,7 @@ mod hir_opt_tests { C.new # warm up TEST = C.instance_method(:initialize) "#); - assert_snapshot!(hir_string_proc("TEST"), @r" + assert_snapshot!(hir_string_proc("TEST"), @" fn initialize@:9: bb1(): EntryPoint interpreter @@ -12126,7 +12126,7 @@ mod hir_opt_tests { def test(o) = o.class.name test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -12162,7 +12162,7 @@ mod hir_opt_tests { def test(o) = o.class test(C.new) "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -12193,7 +12193,7 @@ mod hir_opt_tests { def test = 5.class test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -12220,7 +12220,7 @@ mod hir_opt_tests { def test = self.class test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -12322,7 +12322,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12363,7 +12363,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:13: bb1(): EntryPoint interpreter @@ -12402,7 +12402,7 @@ mod hir_opt_tests { test test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12441,7 +12441,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12480,7 +12480,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12521,7 +12521,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12560,7 +12560,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12599,7 +12599,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -12637,7 +12637,7 @@ mod hir_opt_tests { test o test o "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:9: bb1(): EntryPoint interpreter @@ -12680,7 +12680,7 @@ mod hir_opt_tests { test test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:12: bb1(): EntryPoint interpreter @@ -12716,7 +12716,7 @@ mod hir_opt_tests { def test = S.bytesize test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -12749,7 +12749,7 @@ mod hir_opt_tests { end C.new.callprivate "#); - assert_snapshot!(hir_string_proc("C.instance_method(:callprivate)"), @r" + assert_snapshot!(hir_string_proc("C.instance_method(:callprivate)"), @" fn callprivate@:3: bb1(): EntryPoint interpreter @@ -12780,7 +12780,7 @@ mod hir_opt_tests { def test = Obj.secret rescue $! test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -12808,7 +12808,7 @@ mod hir_opt_tests { end Obj = BasicObject.new.callprivate "#); - assert_snapshot!(hir_string_proc("BasicObject.instance_method(:callprivate)"), @r" + assert_snapshot!(hir_string_proc("BasicObject.instance_method(:callprivate)"), @" fn callprivate@:3: bb1(): EntryPoint interpreter @@ -12836,7 +12836,7 @@ mod hir_opt_tests { def test = Obj.initialize rescue $! test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -12864,7 +12864,7 @@ mod hir_opt_tests { def test = Obj.toplevel_method rescue $! test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:4: bb1(): EntryPoint interpreter @@ -12893,7 +12893,7 @@ mod hir_opt_tests { end C.new.callprotected "#); - assert_snapshot!(hir_string_proc("C.instance_method(:callprotected)"), @r" + assert_snapshot!(hir_string_proc("C.instance_method(:callprotected)"), @" fn callprotected@:3: bb1(): EntryPoint interpreter @@ -12924,7 +12924,7 @@ mod hir_opt_tests { def test = Obj.secret rescue $! test "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -13009,7 +13009,7 @@ mod hir_opt_tests { assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn foo@:10: bb1(): EntryPoint interpreter @@ -13039,7 +13039,7 @@ mod hir_opt_tests { itself "); - assert_snapshot!(hir_string("itself"), @r" + assert_snapshot!(hir_string("itself"), @" fn block in @:2: bb1(): EntryPoint interpreter @@ -13078,7 +13078,7 @@ mod hir_opt_tests { assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn foo@:10: bb1(): EntryPoint interpreter @@ -13175,7 +13175,7 @@ mod hir_opt_tests { assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for block literal:\n{hir}"); // With a block, we don't optimize to SendDirect - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn foo@:10: bb1(): EntryPoint interpreter @@ -13207,7 +13207,7 @@ mod hir_opt_tests { let hir = hir_string_proc("C.new.method(:size)"); assert!(!hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn size@:4: bb1(): EntryPoint interpreter @@ -13242,7 +13242,7 @@ mod hir_opt_tests { C.new "); - assert_snapshot!(hir_string_proc("C.instance_method(:initialize)"), @r" + assert_snapshot!(hir_string_proc("C.instance_method(:initialize)"), @" fn initialize@:4: bb1(): EntryPoint interpreter @@ -13282,7 +13282,7 @@ mod hir_opt_tests { assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to CCallVariadic but got:\n{hir}"); assert!(hir.contains("CCallVariadic"), "Should optimize to CCallVariadic for variadic cfunc:\n{hir}"); - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn byteindex@:3: bb1(): EntryPoint interpreter @@ -13346,7 +13346,7 @@ mod hir_opt_tests { assert!(hir.contains("InvokeSuper "), "Expected unoptimized InvokeSuper but got:\n{hir}"); assert!(!hir.contains("SendDirect"), "Should not optimize to SendDirect for explicit blockarg:\n{hir}"); - assert_snapshot!(hir, @r" + assert_snapshot!(hir, @" fn foo@:10: bb1(): EntryPoint interpreter @@ -13544,7 +13544,7 @@ mod hir_opt_tests { test C.new; test D.new; test C.new; test D.new "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:11: bb1(): EntryPoint interpreter @@ -13599,7 +13599,7 @@ mod hir_opt_tests { test C.new; test 3; test C.new; test 4 "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -13647,7 +13647,7 @@ mod hir_opt_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -13716,7 +13716,7 @@ mod hir_opt_tests { test_ep_escape([1], lambda { }) { |x| } } "); - assert_snapshot!(hir_string("test_ep_escape"), @r" + assert_snapshot!(hir_string("test_ep_escape"), @" fn test_ep_escape@:3: bb1(): EntryPoint interpreter @@ -13805,7 +13805,7 @@ mod hir_opt_tests { #[test] fn test_array_each() { eval("[1, 2, 3].each { |x| x }"); - assert_snapshot!(hir_string_proc("Array.instance_method(:each)"), @r" + assert_snapshot!(hir_string_proc("Array.instance_method(:each)"), @" fn each@: bb1(): EntryPoint interpreter diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 358cae1f6d053c..8e55b83c1835e3 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -31,7 +31,7 @@ mod snapshot_tests { test test "); - assert_snapshot!(optimized_hir_string("test"), @r" + assert_snapshot!(optimized_hir_string("test"), @" fn test@:2: bb0(): Entries bb1, bb2 @@ -101,7 +101,7 @@ mod snapshot_tests { test test "); - assert_snapshot!(optimized_hir_string("test"), @r" + assert_snapshot!(optimized_hir_string("test"), @" fn test@:3: bb0(): Entries bb1, bb2 @@ -140,7 +140,7 @@ mod snapshot_tests { test test "); - assert_snapshot!(optimized_hir_string("test"), @r" + assert_snapshot!(optimized_hir_string("test"), @" fn test@:3: bb0(): Entries bb1, bb2 @@ -177,7 +177,7 @@ mod snapshot_tests { test test "); - assert_snapshot!(optimized_hir_string("test"), @r" + assert_snapshot!(optimized_hir_string("test"), @" fn test@:3: bb0(): Entries bb1, bb2 @@ -326,7 +326,7 @@ pub mod hir_build_tests { fn test_putobject() { eval("def test = 123"); assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -347,7 +347,7 @@ pub mod hir_build_tests { fn test_new_array() { eval("def test = []"); assert_contains_opcode("test", YARVINSN_newarray); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -520,7 +520,7 @@ pub mod hir_build_tests { fn test_array_dup() { eval("def test = [1, 2, 3]"); assert_contains_opcode("test", YARVINSN_duparray); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -542,7 +542,7 @@ pub mod hir_build_tests { fn test_hash_dup() { eval("def test = {a: 1, b: 2}"); assert_contains_opcode("test", YARVINSN_duphash); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -564,7 +564,7 @@ pub mod hir_build_tests { fn test_new_hash_empty() { eval("def test = {}"); assert_contains_opcode("test", YARVINSN_newhash); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -613,7 +613,7 @@ pub mod hir_build_tests { fn test_string_copy() { eval("def test = \"hello\""); assert_contains_opcode("test", YARVINSN_putchilledstring); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -635,7 +635,7 @@ pub mod hir_build_tests { fn test_bignum() { eval("def test = 999999999999999999999999999999999999"); assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -656,7 +656,7 @@ pub mod hir_build_tests { fn test_flonum() { eval("def test = 1.5"); assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -677,7 +677,7 @@ pub mod hir_build_tests { fn test_heap_float() { eval("def test = 1.7976931348623157e+308"); assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -698,7 +698,7 @@ pub mod hir_build_tests { fn test_static_sym() { eval("def test = :foo"); assert_contains_opcode("test", YARVINSN_putobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -719,7 +719,7 @@ pub mod hir_build_tests { fn test_opt_plus() { eval("def test = 1+2"); assert_contains_opcode("test", YARVINSN_opt_plus); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -744,7 +744,7 @@ pub mod hir_build_tests { def test = {}.freeze "); assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -771,7 +771,7 @@ pub mod hir_build_tests { def test = {}.freeze "); assert_contains_opcode("test", YARVINSN_opt_hash_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -792,7 +792,7 @@ pub mod hir_build_tests { def test = [].freeze "); assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -819,7 +819,7 @@ pub mod hir_build_tests { def test = [].freeze "); assert_contains_opcode("test", YARVINSN_opt_ary_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -840,7 +840,7 @@ pub mod hir_build_tests { def test = ''.freeze "); assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -867,7 +867,7 @@ pub mod hir_build_tests { def test = ''.freeze "); assert_contains_opcode("test", YARVINSN_opt_str_freeze); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -888,7 +888,7 @@ pub mod hir_build_tests { def test = -'' "); assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -915,7 +915,7 @@ pub mod hir_build_tests { def test = -'' "); assert_contains_opcode("test", YARVINSN_opt_str_uminus); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:5: bb1(): EntryPoint interpreter @@ -939,7 +939,7 @@ pub mod hir_build_tests { end "); assert_contains_opcodes("test", &[YARVINSN_getlocal_WC_0, YARVINSN_setlocal_WC_0]); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -980,7 +980,7 @@ pub mod hir_build_tests { "test", &[YARVINSN_getlocal_WC_1, YARVINSN_setlocal_WC_1, YARVINSN_getlocal, YARVINSN_setlocal]); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn block (3 levels) in @:10: bb1(): EntryPoint interpreter @@ -1164,7 +1164,7 @@ pub mod hir_build_tests { def test = defined?(@foo) "); assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -1193,7 +1193,7 @@ pub mod hir_build_tests { end "); assert_contains_opcode("test", YARVINSN_definedivar); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1226,7 +1226,7 @@ pub mod hir_build_tests { def test = return defined?(SeaChange), defined?(favourite), defined?($ruby) "); assert_contains_opcode("test", YARVINSN_defined); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -1254,7 +1254,7 @@ pub mod hir_build_tests { def test = defined?(yield) "); assert_contains_opcode("test", YARVINSN_defined); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -1278,7 +1278,7 @@ pub mod hir_build_tests { define_method(:test) { defined?(yield) } "); assert_contains_opcode("test", YARVINSN_defined); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn block in @:2: bb1(): EntryPoint interpreter @@ -1686,7 +1686,7 @@ pub mod hir_build_tests { end test "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1766,7 +1766,7 @@ pub mod hir_build_tests { end end "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1806,7 +1806,7 @@ pub mod hir_build_tests { end "); assert_contains_opcode("test", YARVINSN_opt_send_without_block); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:6: bb1(): EntryPoint interpreter @@ -1866,7 +1866,7 @@ pub mod hir_build_tests { end "#); assert_contains_opcode("test", YARVINSN_intern); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -1893,7 +1893,7 @@ pub mod hir_build_tests { eval("def test = unknown_method([0], [1], '2', '2')"); // The 2 string literals have the same address because they're deduped. - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:1: bb1(): EntryPoint interpreter @@ -2027,7 +2027,7 @@ pub mod hir_build_tests { eval(" def test = super() "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2049,7 +2049,7 @@ pub mod hir_build_tests { eval(" def test = super "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2071,7 +2071,7 @@ pub mod hir_build_tests { eval(" def test = super(&nil) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2310,7 +2310,7 @@ pub mod hir_build_tests { eval(" def test(a, ...) = foo(a, ...) "); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2351,7 +2351,7 @@ pub mod hir_build_tests { def test = C.new "); assert_contains_opcode("test", YARVINSN_opt_new); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -2386,7 +2386,7 @@ pub mod hir_build_tests { "); // TODO(max): Rewrite to nil assert_contains_opcode("test", YARVINSN_opt_newarray_send); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2955,7 +2955,7 @@ pub mod hir_build_tests { test "); assert_contains_opcode("test", YARVINSN_getinstancevariable); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -2980,7 +2980,7 @@ pub mod hir_build_tests { test "); assert_contains_opcode("test", YARVINSN_setinstancevariable); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3037,7 +3037,7 @@ pub mod hir_build_tests { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable"); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3064,7 +3064,7 @@ pub mod hir_build_tests { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test")); assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable"); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3089,7 +3089,7 @@ pub mod hir_build_tests { test "); assert_contains_opcode("test", YARVINSN_setglobal); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3114,7 +3114,7 @@ pub mod hir_build_tests { test "); assert_contains_opcode("test", YARVINSN_getglobal); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -3211,7 +3211,7 @@ pub mod hir_build_tests { def test(**kw, &b) = foo(**kw, &b) "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3245,7 +3245,7 @@ pub mod hir_build_tests { test(1) "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3290,7 +3290,7 @@ pub mod hir_build_tests { test(&proc {}) "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3327,7 +3327,7 @@ pub mod hir_build_tests { test(a: 1, &proc {}) "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3366,7 +3366,7 @@ pub mod hir_build_tests { test(1, b: 2) "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3410,7 +3410,7 @@ pub mod hir_build_tests { test(obj) { 2 } "); assert_contains_opcode("test", YARVINSN_splatkw); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3781,7 +3781,7 @@ pub mod hir_build_tests { end "); assert_contains_opcode("test", YARVINSN_putspecialobject); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -3817,7 +3817,7 @@ pub mod hir_build_tests { "); assert_contains_opcode("reverse_odd", YARVINSN_opt_reverse); assert_contains_opcode("reverse_even", YARVINSN_opt_reverse); - assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @r" + assert_snapshot!(hir_strings!("reverse_odd", "reverse_even"), @" fn reverse_odd@:3: bb1(): EntryPoint interpreter @@ -4059,7 +4059,7 @@ pub mod hir_build_tests { #[test] fn test_invokebuiltin_cexpr_annotated() { assert_contains_opcode("class", YARVINSN_opt_invokebuiltin_delegate_leave); - assert_snapshot!(hir_string("class"), @r" + assert_snapshot!(hir_string("class"), @" fn class@: bb1(): EntryPoint interpreter @@ -4084,7 +4084,7 @@ pub mod hir_build_tests { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Dir", "open")); assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate), "iseq Dir.open does not contain invokebuiltin"); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn open@: bb1(): EntryPoint interpreter @@ -4135,7 +4135,7 @@ pub mod hir_build_tests { let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("GC", "enable")); assert!(iseq_contains_opcode(iseq, YARVINSN_opt_invokebuiltin_delegate_leave), "iseq GC.enable does not contain invokebuiltin"); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn enable@: bb1(): EntryPoint interpreter @@ -4191,7 +4191,7 @@ pub mod hir_build_tests { fn test_invoke_leaf_builtin_symbol_name() { let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "name")); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn name@: bb1(): EntryPoint interpreter @@ -4214,7 +4214,7 @@ pub mod hir_build_tests { fn test_invoke_leaf_builtin_symbol_to_s() { let iseq = crate::cruby::with_rubyvm(|| get_instance_method_iseq("Symbol", "to_s")); let function = iseq_to_hir(iseq).unwrap(); - assert_snapshot!(hir_string_function(&function), @r" + assert_snapshot!(hir_string_function(&function), @" fn to_s@: bb1(): EntryPoint interpreter @@ -4278,7 +4278,7 @@ pub mod hir_build_tests { def test = \"#{1}\" "); assert_contains_opcode("test", YARVINSN_objtostring); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4305,7 +4305,7 @@ pub mod hir_build_tests { def test = "#{1}#{2}#{3}" "##); assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4337,7 +4337,7 @@ pub mod hir_build_tests { def test = "#{}" "##); assert_contains_opcode("test", YARVINSN_concatstrings); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4364,7 +4364,7 @@ pub mod hir_build_tests { def test = /#{1}#{2}#{3}/ "##); assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4396,7 +4396,7 @@ pub mod hir_build_tests { def test = /#{1}#{2}/mixn "##); assert_contains_opcode("test", YARVINSN_toregexp); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:2: bb1(): EntryPoint interpreter @@ -4427,7 +4427,7 @@ pub mod hir_build_tests { "); assert_contains_opcode("throw_return", YARVINSN_throw); assert_contains_opcode("throw_break", YARVINSN_throw); - assert_snapshot!(hir_strings!("throw_return", "throw_break"), @r" + assert_snapshot!(hir_strings!("throw_return", "throw_break"), @" fn block in @:2: bb1(): EntryPoint interpreter @@ -4463,7 +4463,7 @@ pub mod hir_build_tests { yield end "#); - assert_snapshot!(hir_string("test"), @r" + assert_snapshot!(hir_string("test"), @" fn test@:3: bb1(): EntryPoint interpreter @@ -4749,7 +4749,7 @@ pub mod hir_build_tests { #[test] fn test_array_each() { - assert_snapshot!(hir_string_proc("Array.instance_method(:each)"), @r" + assert_snapshot!(hir_string_proc("Array.instance_method(:each)"), @" fn each@: bb1(): EntryPoint interpreter @@ -4916,7 +4916,7 @@ pub mod hir_build_tests { function.push_insn(bb3, Insn::Return { val: retval }); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb2() @@ -4958,7 +4958,7 @@ pub mod hir_build_tests { function.push_insn(bb3, Insn::Return { val: retval }); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): v0:Any = Const Value(false) @@ -5017,7 +5017,7 @@ pub mod hir_build_tests { function.push_insn(bb7, Insn::Return { val: retval }); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb2() @@ -5086,7 +5086,7 @@ pub mod hir_build_tests { function.push_insn(bb3, Insn::Return { val: retval }); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): v0:Any = Const Value(false) @@ -5137,7 +5137,7 @@ pub mod hir_build_tests { function.push_insn(bb2, Insn::Return { val: retval }); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb3() @@ -5199,7 +5199,7 @@ mod loop_info_tests { let dominators = Dominators::new(&function); let loop_info = LoopInfo::new(&cfi, &dominators); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb3() @@ -5266,7 +5266,7 @@ mod loop_info_tests { let dominators = Dominators::new(&function); let loop_info = LoopInfo::new(&cfi, &dominators); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb2() @@ -5354,7 +5354,7 @@ mod loop_info_tests { let dominators = Dominators::new(&function); let loop_info = LoopInfo::new(&cfi, &dominators); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): v0:Any = Const Value(false) @@ -5429,7 +5429,7 @@ mod loop_info_tests { let dominators = Dominators::new(&function); let loop_info = LoopInfo::new(&cfi, &dominators); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): Jump bb2() @@ -5498,7 +5498,7 @@ mod loop_info_tests { let _ = function.push_insn(bb5, Insn::IfTrue {val: cond, target: edge(bb0)}); function.seal_entries(); - assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @r" + assert_snapshot!(format!("{}", FunctionPrinter::without_snapshot(&function)), @" fn : bb1(): v0:Any = Const Value(false) From 192fc37962a5a4e4c15f9be6ce23f83534082b80 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 5 Mar 2026 12:24:00 -0500 Subject: [PATCH 13/87] Add previous parent commit to .git-blame-ignore-revs [ci skip] --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 9580a50a0acd7d..1eed0e2b5fc96f 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -46,3 +46,6 @@ e90282be7ba1bc8e3119f6e1a2c80356ceb3f80a # Win32: EOL code of batch files 23f9a0d655c4d405bb2397a147a1523436205486 b839989fd22fef85e2af19de1bc83aa72a5b22bd + +# ZJIT cargo-insta snapshot raw string literals +b78e0a6ddf7df8a7568ea71284f593423c739551 From 407dd02c1b52b05ba55a179554b29a14e44a4b82 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 23 Feb 2026 18:40:29 -0800 Subject: [PATCH 14/87] Map M:N thread stack chunks initially as PROT_NONE Previously we initially mapped the full 512MB chunk as PROT_READ|PROD_WRITE and then set a guard page to PROT_NONE the first time a new thread stack is needed. Usually that's okay as we don't touch that memory until it is needed and so it doesn't count towards RSS. However, on Linux even with vm.overcommit_memory=0 (the default) if on a system (like a tiny cloud VM) with <512MB of RAM+swap that would error with. Thread#initialize': can't create Thread: Cannot allocate memory (ThreadError) This changes the chunk to be mapped initially with PROT_NONE, then instead of mapping the guard pages we map in the machine and VM stacks using mprotect. This ensures we don't commit stack memory until it is first used, and as a side benefit any stray pointers into unused stack should segfault. When a stack is freed/reused there is no change from the previous behaviour, we just use madvise and leave the same regions in place. [Bug #21944] --- thread_pthread_mn.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index 69e81e5fbcf00f..a17d5597d39028 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -194,7 +194,7 @@ nt_alloc_thread_stack_chunk(void) mmap_flags |= MAP_STACK; #endif - const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); + const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, mmap_flags, -1, 0); if (m == MAP_FAILED) { return NULL; } @@ -213,6 +213,12 @@ nt_alloc_thread_stack_chunk(void) VM_ASSERT(stack_count <= UINT16_MAX); + // Enable read/write for the header pages + if (mprotect((void *)m, (size_t)header_page_cnt * MSTACK_PAGE_SIZE, PROT_READ | PROT_WRITE) != 0) { + munmap((void *)m, MSTACK_CHUNK_SIZE); + return NULL; + } + struct nt_stack_chunk_header *ch = (struct nt_stack_chunk_header *)m; ch->start_page = header_page_cnt; @@ -241,7 +247,7 @@ nt_stack_chunk_get_msf(const rb_vm_t *vm, const char *mstack) return (struct nt_machine_stack_footer *)&mstack[msz - sizeof(struct nt_machine_stack_footer)]; } -static void * +static void nt_stack_chunk_get_stack(const rb_vm_t *vm, struct nt_stack_chunk_header *ch, size_t idx, void **vm_stack, void **machine_stack) { // TODO: only support stack going down @@ -266,8 +272,6 @@ nt_stack_chunk_get_stack(const rb_vm_t *vm, struct nt_stack_chunk_header *ch, si *vm_stack = (void *)vstack; *machine_stack = (void *)mstack; - - return (void *)guard_page; } RBIMPL_ATTR_MAYBE_UNUSED() @@ -290,17 +294,6 @@ nt_stack_chunk_dump(void) } } -static int -nt_guard_page(const char *p, size_t len) -{ - if (mprotect((void *)p, len, PROT_NONE) != -1) { - return 0; - } - else { - return errno; - } -} - static int nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack) { @@ -319,8 +312,20 @@ nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack) RUBY_DEBUG_LOG("uninitialized_stack_count:%d", ch->uninitialized_stack_count); size_t idx = ch->stack_count - ch->uninitialized_stack_count--; - void *guard_page = nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack); - err = nt_guard_page(guard_page, MSTACK_PAGE_SIZE); + + // The chunk was mapped PROT_NONE; enable the VM stack and + // machine stack pages, leaving the guard page as PROT_NONE. + char *stack_start = nt_stack_chunk_get_stack_start(ch, idx); + size_t vm_stack_size = vm->default_params.thread_vm_stack_size; + size_t mstack_size = nt_thread_stack_size() - vm_stack_size - MSTACK_PAGE_SIZE; + + if (mprotect(stack_start, vm_stack_size, PROT_READ | PROT_WRITE) != 0 || + mprotect(stack_start + vm_stack_size + MSTACK_PAGE_SIZE, mstack_size, PROT_READ | PROT_WRITE) != 0) { + err = errno; + } + else { + nt_stack_chunk_get_stack(vm, ch, idx, vm_stack, machine_stack); + } } else { nt_free_stack_chunks = ch->prev_free_chunk; From 6a4402c905c6b33d1ea640de543cdde5a416170f Mon Sep 17 00:00:00 2001 From: Jacob Date: Thu, 5 Mar 2026 15:51:08 -0500 Subject: [PATCH 15/87] ZJIT: Optimize load store forwarding (#16228) This PR introduces an almost verbatim implementation of load_store_forward optimization as described in Max's [blog post](https://bernsteinbear.com/blog/toy-load-store/). After this PR is merged, we will add type based alias analysis. --- zjit/src/hir.rs | 71 ++++++++++++++++- zjit/src/hir/opt_tests.rs | 155 +++++++++++++++++++++++++++++++++++--- zjit/src/stats.rs | 1 + 3 files changed, 216 insertions(+), 11 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7d8e6041717117..a71946b3c915e3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1482,7 +1482,9 @@ impl Insn { Insn::LoadSelf { .. } => Effect::read_write(abstract_heaps::Frame, abstract_heaps::Empty), Insn::LoadField { .. } => Effect::read_write(abstract_heaps::Memory, abstract_heaps::Empty), Insn::StoreField { .. } => effects::Any, - Insn::WriteBarrier { .. } => effects::Any, + // WriteBarrier can write to object flags and mark bits in Allocator memory. + // This is why WriteBarrier writes to the "Memory" effect. We do not yet have a more granular specialization for flags + Insn::WriteBarrier { .. } => Effect::read_write(abstract_heaps::Allocator, abstract_heaps::Allocator.union(abstract_heaps::Memory)), Insn::SetLocal { .. } => effects::Any, Insn::GetSpecialSymbol { .. } => effects::Any, Insn::GetSpecialNumber { .. } => effects::Any, @@ -3385,6 +3387,10 @@ impl Function { } pub fn load_rbasic_flags(&mut self, block: BlockId, recv: InsnId) -> InsnId { + // Technically this also includes the shape (_shape_id) because the (shape, flags) tuple is + // a (u32, u32) inside a u64 at RUBY_OFFSET_RBASIC_FLAGS (offset 0). It's fine to load the + // shape alongside the flags, but make sure not to *store* the shape accidentally by + // writing a u64. self.push_insn(block, Insn::LoadField { recv, id: ID!(_rbasic_flags), offset: RUBY_OFFSET_RBASIC_FLAGS, return_type: types::CUInt64 }) } @@ -4902,6 +4908,66 @@ impl Function { self.infer_types(); } + fn optimize_load_store(&mut self) { + let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new(); + for block in self.rpo() { + let old_insns = std::mem::take(&mut self.blocks[block.0].insns); + let mut new_insns = vec![]; + for insn_id in old_insns { + let replacement_insn: InsnId = match self.find(insn_id) { + Insn::StoreField { recv, offset, val, .. } => { + let key = (self.chase_insn(recv), offset); + let heap_entry = compile_time_heap.get(&key).copied(); + // TODO(Jacob): Switch from actual to partial equality + if Some(val) == heap_entry { + // If the value is already stored, short circuit and don't add an instruction to the block + continue + } + // TODO(Jacob): Add TBAA to avoid removing so many entries + compile_time_heap.retain(|(_, off), _| *off != offset); + compile_time_heap.insert(key, val); + insn_id + }, + Insn::LoadField { recv, offset, .. } => { + let key = (self.chase_insn(recv), offset); + match compile_time_heap.entry(key) { + std::collections::hash_map::Entry::Occupied(entry) => { + // If the value is stored already, we should short circuit. + // However, we need to replace insn_id with its representative in the SSA union. + self.make_equal_to(insn_id, *entry.get()); + continue + } + std::collections::hash_map::Entry::Vacant(_) => { + // If the value has not been accessed, cache a copy to optimize future loads or stores. + compile_time_heap.insert(key, insn_id); + } + } + insn_id + } + Insn::WriteBarrier { .. } => { + // Currently, WriteBarrier write effects are Allocator and Memory when we'd really like them to be flags. + // We don't use LoadField for mark bits so we can ignore them for now. + // But flags does not exist in our effects abstract heap modeling and we don't want to add special casing to effects. + // This special casing in this pass here should be removed once we refine our effects system to provide greater granularity for WriteBarrier. + // TODO: use TBAA + let offset = RUBY_OFFSET_RBASIC_FLAGS; + compile_time_heap.retain(|(_, off), _| *off != offset); + insn_id + }, + insn => { + // If an instruction affects memory and we haven't modeled it, the compile_time_heap is invalidated + if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) { + compile_time_heap.clear(); + } + insn_id + } + }; + new_insns.push(replacement_insn); + } + self.blocks[block.0].insns = new_insns; + } + } + /// Fold a binary operator on fixnums. fn fold_fixnum_bop(&mut self, insn_id: InsnId, left: InsnId, right: InsnId, f: impl FnOnce(Option, Option) -> Option) -> InsnId { f(self.type_of(left).fixnum_value(), self.type_of(right).fixnum_value()) @@ -5471,6 +5537,8 @@ impl Function { || ident_equal!($name, optimize_getivar) || ident_equal!($name, optimize_c_calls) { Counter::compile_hir_strength_reduce_time_ns + } else if ident_equal!($name, optimize_load_store) { + Counter::compile_hir_optimize_load_store_time_ns } else if ident_equal!($name, fold_constants) { Counter::compile_hir_fold_constants_time_ns } else if ident_equal!($name, clean_cfg) { @@ -5501,6 +5569,7 @@ impl Function { run_pass!(inline); run_pass!(optimize_getivar); run_pass!(optimize_c_calls); + run_pass!(optimize_load_store); run_pass!(fold_constants); run_pass!(clean_cfg); run_pass!(remove_redundant_patch_points); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index b7b11a47f77d2e..239e3c65949860 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -5205,8 +5205,6 @@ mod hir_opt_tests { v14:HeapBasicObject = RefineType v6, HeapBasicObject v17:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - v36:CShape = LoadField v14, :_shape_id@0x1000 - v37:CShape[0x1003] = GuardBitEquals v36, CShape(0x1003) StoreField v14, :@bar@0x1004, v17 WriteBarrier v14, v17 v40:CShape[0x1005] = Const CShape(0x1005) @@ -8782,8 +8780,7 @@ mod hir_opt_tests { v33:ArrayExact = GuardType v10, ArrayExact v34:CUInt64 = LoadField v33, :_rbasic_flags@0x1040 v35:CUInt64 = GuardNoBitsSet v34, RUBY_FL_FREEZE=CUInt64(2048) - v36:CUInt64 = LoadField v33, :_rbasic_flags@0x1040 - v37:CUInt64 = GuardNoBitsSet v36, RUBY_ELTS_SHARED=CUInt64(4096) + v37:CUInt64 = GuardNoBitsSet v34, RUBY_ELTS_SHARED=CUInt64(4096) v38:CInt64[1] = UnboxFixnum v17 v39:CInt64 = ArrayLength v33 v40:CInt64[1] = GuardLess v38, v39 @@ -8829,8 +8826,7 @@ mod hir_opt_tests { v38:Fixnum = GuardType v15, Fixnum v39:CUInt64 = LoadField v37, :_rbasic_flags@0x1040 v40:CUInt64 = GuardNoBitsSet v39, RUBY_FL_FREEZE=CUInt64(2048) - v41:CUInt64 = LoadField v37, :_rbasic_flags@0x1040 - v42:CUInt64 = GuardNoBitsSet v41, RUBY_ELTS_SHARED=CUInt64(4096) + v42:CUInt64 = GuardNoBitsSet v39, RUBY_ELTS_SHARED=CUInt64(4096) v43:CInt64 = UnboxFixnum v38 v44:CInt64 = ArrayLength v37 v45:CInt64 = GuardLess v43, v44 @@ -13670,8 +13666,6 @@ mod hir_opt_tests { v14:HeapBasicObject = RefineType v6, HeapBasicObject v17:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - v50:CShape = LoadField v14, :_shape_id@0x1000 - v51:CShape[0x1003] = GuardBitEquals v50, CShape(0x1003) StoreField v14, :@b@0x1004, v17 WriteBarrier v14, v17 v54:CShape[0x1005] = Const CShape(0x1005) @@ -13679,8 +13673,6 @@ mod hir_opt_tests { v21:HeapBasicObject = RefineType v14, HeapBasicObject v24:Fixnum[3] = Const Value(3) PatchPoint SingleRactorMode - v57:CShape = LoadField v21, :_shape_id@0x1000 - v58:CShape[0x1005] = GuardBitEquals v57, CShape(0x1005) StoreField v21, :@c@0x1006, v24 WriteBarrier v21, v24 v61:CShape[0x1007] = Const CShape(0x1007) @@ -13851,4 +13843,147 @@ mod hir_opt_tests { Jump bb8(v67, v94) "); } + + #[test] + fn test_delete_duplicate_store() { + eval(" + class C + def initialize + a = 1 + @a = a + @a = a + end + end + + C.new + "); + assert_snapshot!(hir_string_proc("C.instance_method(:initialize)"), @" + fn initialize@:4: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:NilClass = Const Value(nil) + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + v35:HeapBasicObject = GuardType v8, HeapBasicObject + v36:CShape = LoadField v35, :_shape_id@0x1000 + v37:CShape[0x1001] = GuardBitEquals v36, CShape(0x1001) + StoreField v35, :@a@0x1002, v13 + WriteBarrier v35, v13 + v40:CShape[0x1003] = Const CShape(0x1003) + StoreField v35, :_shape_id@0x1000, v40 + v20:HeapBasicObject = RefineType v8, HeapBasicObject + PatchPoint NoEPEscape(initialize) + PatchPoint SingleRactorMode + WriteBarrier v20, v13 + CheckInterrupts + Return v13 + "); + } + + #[test] + fn test_remove_duplicate_store_with_non_effectful_insns_between() { + eval(" + class C + def initialize + a = 1 + @a = a + b = 5 + b += a + @a = a + end + end + + C.new + "); + assert_snapshot!(hir_string_proc("C.instance_method(:initialize)"), @" + fn initialize@:4: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + v3:NilClass = Const Value(nil) + Jump bb3(v1, v2, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:NilClass = Const Value(nil) + v8:NilClass = Const Value(nil) + Jump bb3(v6, v7, v8) + bb3(v10:BasicObject, v11:NilClass, v12:NilClass): + v16:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + v49:HeapBasicObject = GuardType v10, HeapBasicObject + v50:CShape = LoadField v49, :_shape_id@0x1000 + v51:CShape[0x1001] = GuardBitEquals v50, CShape(0x1001) + StoreField v49, :@a@0x1002, v16 + WriteBarrier v49, v16 + v54:CShape[0x1003] = Const CShape(0x1003) + StoreField v49, :_shape_id@0x1000, v54 + v23:HeapBasicObject = RefineType v10, HeapBasicObject + v26:Fixnum[5] = Const Value(5) + PatchPoint NoEPEscape(initialize) + PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) + v65:Fixnum[6] = Const Value(6) + IncrCounter inline_cfunc_optimized_send_count + PatchPoint SingleRactorMode + WriteBarrier v23, v16 + CheckInterrupts + Return v16 + "); + } + + #[test] + fn test_remove_two_stores() { + eval(" + class C + def initialize + a = 1 + @a = a + @a = a + @a = a + end + end + + C.new + "); + assert_snapshot!(hir_string_proc("C.instance_method(:initialize)"), @" + fn initialize@:4: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:NilClass = Const Value(nil) + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:NilClass = Const Value(nil) + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:NilClass): + v13:Fixnum[1] = Const Value(1) + PatchPoint SingleRactorMode + v43:HeapBasicObject = GuardType v8, HeapBasicObject + v44:CShape = LoadField v43, :_shape_id@0x1000 + v45:CShape[0x1001] = GuardBitEquals v44, CShape(0x1001) + StoreField v43, :@a@0x1002, v13 + WriteBarrier v43, v13 + v48:CShape[0x1003] = Const CShape(0x1003) + StoreField v43, :_shape_id@0x1000, v48 + v20:HeapBasicObject = RefineType v8, HeapBasicObject + PatchPoint NoEPEscape(initialize) + PatchPoint SingleRactorMode + WriteBarrier v20, v13 + v28:HeapBasicObject = RefineType v20, HeapBasicObject + WriteBarrier v28, v13 + CheckInterrupts + Return v13 + "); + } } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 6b5f46a0b06bfa..42dd39fdd047e9 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -167,6 +167,7 @@ make_counters! { compile_hir_time_ns, compile_hir_build_time_ns, compile_hir_strength_reduce_time_ns, + compile_hir_optimize_load_store_time_ns, compile_hir_fold_constants_time_ns, compile_hir_clean_cfg_time_ns, compile_hir_remove_redundant_patch_points_time_ns, From a81136b646f189acc0e08f34ca7b90120c2dac8a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 5 Mar 2026 21:42:44 +0100 Subject: [PATCH 16/87] [ruby/prism] Revert "Recompile when config.yml changes" * This reverts commit https://github.com/ruby/prism/commit/ffe8f7a6e236. * No longer necessary as the Makefile depends on all *.c and *.h. https://github.com/ruby/prism/commit/5a33460adc --- lib/prism/prism.gemspec | 2 -- prism/depend | 2 -- 2 files changed, 4 deletions(-) delete mode 100644 prism/depend diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 74d54127319aca..b85eeab85516c1 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -42,8 +42,6 @@ Gem::Specification.new do |spec| "docs/serialization.md", "docs/testing.md", "ext/prism/api_node.c", - "ext/prism/depend", - "ext/prism/extconf.rb", "ext/prism/extension.c", "ext/prism/extension.h", "include/prism.h", diff --git a/prism/depend b/prism/depend deleted file mode 100644 index ac0e7a9bb378ed..00000000000000 --- a/prism/depend +++ /dev/null @@ -1,2 +0,0 @@ -$(OBJS): $(HDRS) $(ruby_headers) -$(OBJS): $(srcdir)/../../config.yml From d789558994b9fc891b88a7751b781cfde521afcf Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 5 Mar 2026 21:43:56 +0100 Subject: [PATCH 17/87] [ruby/prism] Include ext/prism/extconf.rb explicitly in prism.gemspec * For consistency. https://github.com/ruby/prism/commit/d4575f651a --- lib/prism/prism.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index b85eeab85516c1..ae5c56a45de643 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| "docs/serialization.md", "docs/testing.md", "ext/prism/api_node.c", + "ext/prism/extconf.rb", "ext/prism/extension.c", "ext/prism/extension.h", "include/prism.h", From 54eb330f8cc9f5a5b67ccfa5247cd8b98fb1ef44 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Thu, 5 Mar 2026 20:41:21 +0100 Subject: [PATCH 18/87] [ruby/prism] Correctly handle `and?` and similar on ruby 4.0 It gets confused for syntax introduced in https://bugs.ruby-lang.org/issues/20925 But it actually should be a plain method call. `!`/`?` are not valid as part of an identifier, methods however allow them as the last character. Fixes [Bug #21946] https://github.com/ruby/prism/commit/5d80bc5e1a --- prism/prism.c | 21 +++++++++++++++++++-- test/prism/fixtures/4.0/leading_logical.txt | 5 ----- test/prism/fixtures/and_or_with_suffix.txt | 17 +++++++++++++++++ test/prism/ruby/ripper_test.rb | 7 +++++++ 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 test/prism/fixtures/and_or_with_suffix.txt diff --git a/prism/prism.c b/prism/prism.c index e10a7710af3de6..b6bc90ebd77234 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -10034,8 +10034,21 @@ parser_lex(pm_parser_t *parser) { following && ( (peek_at(parser, following) == '&' && peek_at(parser, following + 1) == '&') || (peek_at(parser, following) == '|' && peek_at(parser, following + 1) == '|') || - (peek_at(parser, following) == 'a' && peek_at(parser, following + 1) == 'n' && peek_at(parser, following + 2) == 'd' && !char_is_identifier(parser, following + 3, parser->end - (following + 3))) || - (peek_at(parser, following) == 'o' && peek_at(parser, following + 1) == 'r' && !char_is_identifier(parser, following + 2, parser->end - (following + 2))) + ( + peek_at(parser, following) == 'a' && + peek_at(parser, following + 1) == 'n' && + peek_at(parser, following + 2) == 'd' && + peek_at(parser, next_content + 3) != '!' && + peek_at(parser, next_content + 3) != '?' && + !char_is_identifier(parser, following + 3, parser->end - (following + 3)) + ) || + ( + peek_at(parser, following) == 'o' && + peek_at(parser, following + 1) == 'r' && + peek_at(parser, next_content + 2) != '!' && + peek_at(parser, next_content + 2) != '?' && + !char_is_identifier(parser, following + 2, parser->end - (following + 2)) + ) ) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); @@ -10106,6 +10119,8 @@ parser_lex(pm_parser_t *parser) { peek_at(parser, next_content) == 'a' && peek_at(parser, next_content + 1) == 'n' && peek_at(parser, next_content + 2) == 'd' && + peek_at(parser, next_content + 3) != '!' && + peek_at(parser, next_content + 3) != '?' && !char_is_identifier(parser, next_content + 3, parser->end - (next_content + 3)) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); @@ -10122,6 +10137,8 @@ parser_lex(pm_parser_t *parser) { if ( peek_at(parser, next_content) == 'o' && peek_at(parser, next_content + 1) == 'r' && + peek_at(parser, next_content + 2) != '!' && + peek_at(parser, next_content + 2) != '?' && !char_is_identifier(parser, next_content + 2, parser->end - (next_content + 2)) ) { if (!lexed_comment) parser_lex_ignored_newline(parser); diff --git a/test/prism/fixtures/4.0/leading_logical.txt b/test/prism/fixtures/4.0/leading_logical.txt index feb5ee245c8b2f..ee87e00d4f10c8 100644 --- a/test/prism/fixtures/4.0/leading_logical.txt +++ b/test/prism/fixtures/4.0/leading_logical.txt @@ -14,8 +14,3 @@ and 3 or 2 or 3 -1 -andfoo - -2 -orfoo diff --git a/test/prism/fixtures/and_or_with_suffix.txt b/test/prism/fixtures/and_or_with_suffix.txt new file mode 100644 index 00000000000000..59ee4d0b88bfe0 --- /dev/null +++ b/test/prism/fixtures/and_or_with_suffix.txt @@ -0,0 +1,17 @@ +foo +and? + +foo +or? + +foo +and! + +foo +or! + +foo +andbar + +foo +orbar diff --git a/test/prism/ruby/ripper_test.rb b/test/prism/ruby/ripper_test.rb index ba01732bcb2823..92aa1ad0b31ecc 100644 --- a/test/prism/ruby/ripper_test.rb +++ b/test/prism/ruby/ripper_test.rb @@ -37,6 +37,13 @@ class RipperTest < TestCase ] end + if RUBY_VERSION.start_with?("4.") + incorrect += [ + # https://bugs.ruby-lang.org/issues/21945 + "and_or_with_suffix.txt", + ] + end + # https://bugs.ruby-lang.org/issues/21669 incorrect << "4.1/void_value.txt" # https://bugs.ruby-lang.org/issues/19107 From 45f030f9d7777ba326cb90cf2161d5cb493b35ad Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 5 Mar 2026 17:29:09 -0500 Subject: [PATCH 19/87] [PRISM] Template out node creation functions --- depend | 1 + lib/prism/prism.gemspec | 1 + prism/node_new.h | 4071 ++++++++++++++++++ prism/prism.c | 3206 +++++++------- prism/srcs.mk | 7 + prism/templates/include/prism/node_new.h.erb | 42 + prism/templates/template.rb | 50 + 7 files changed, 5742 insertions(+), 1636 deletions(-) create mode 100644 prism/node_new.h create mode 100644 prism/templates/include/prism/node_new.h.erb diff --git a/depend b/depend index 4d204c82f23a92..ced1dfd6ad8a75 100644 --- a/depend +++ b/depend @@ -11686,6 +11686,7 @@ prism/prism.$(OBJEXT): $(top_srcdir)/prism/defines.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/diagnostic.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/encoding.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/node.h +prism/prism.$(OBJEXT): $(top_srcdir)/prism/node_new.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/options.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/parser.h prism/prism.$(OBJEXT): $(top_srcdir)/prism/prettyprint.h diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index ae5c56a45de643..d8b86c6fbaa01c 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -52,6 +52,7 @@ Gem::Specification.new do |spec| "include/prism/diagnostic.h", "include/prism/encoding.h", "include/prism/node.h", + "include/prism/node_new.h", "include/prism/options.h", "include/prism/parser.h", "include/prism/prettyprint.h", diff --git a/prism/node_new.h b/prism/node_new.h new file mode 100644 index 00000000000000..09bfc1a9dc065b --- /dev/null +++ b/prism/node_new.h @@ -0,0 +1,4071 @@ +/*----------------------------------------------------------------------------*/ +/* This file is generated by the templates/template.rb script and should not */ +/* be modified manually. See */ +/* templates/include/prism/node_new.h.erb */ +/* if you are looking to modify the */ +/* template */ +/*----------------------------------------------------------------------------*/ + +/** + * @file node_new.h + * + * Static inline functions for allocating and initializing AST nodes. + * + * -- + */ +#ifndef PRISM_NODE_NEW_H +#define PRISM_NODE_NEW_H + +#include "prism/node.h" + +/** + * Allocate and initialize a new AliasGlobalVariableNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param new_name Represents the new name of the global variable that can be used after aliasing\. + * @param old_name Represents the old name of the global variable that can be used before aliasing\. + * @param keyword_loc The Location of the \`alias\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_alias_global_variable_node_t * +pm_alias_global_variable_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *new_name, struct pm_node *old_name, pm_location_t keyword_loc) { + pm_alias_global_variable_node_t *node = (pm_alias_global_variable_node_t *) pm_arena_alloc(arena, sizeof(pm_alias_global_variable_node_t), PRISM_ALIGNOF(pm_alias_global_variable_node_t)); + + *node = (pm_alias_global_variable_node_t) { + .base = { .type = PM_ALIAS_GLOBAL_VARIABLE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .new_name = new_name, + .old_name = old_name, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new AliasMethodNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param new_name Represents the new name of the method that will be aliased\. + * @param old_name Represents the old name of the method that will be aliased\. + * @param keyword_loc Represents the Location of the \`alias\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_alias_method_node_t * +pm_alias_method_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *new_name, struct pm_node *old_name, pm_location_t keyword_loc) { + pm_alias_method_node_t *node = (pm_alias_method_node_t *) pm_arena_alloc(arena, sizeof(pm_alias_method_node_t), PRISM_ALIGNOF(pm_alias_method_node_t)); + + *node = (pm_alias_method_node_t) { + .base = { .type = PM_ALIAS_METHOD_NODE, .flags = flags, .node_id = node_id, .location = location }, + .new_name = new_name, + .old_name = old_name, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new AlternationPatternNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param left Represents the left side of the expression\. + * @param right Represents the right side of the expression\. + * @param operator_loc Represents the alternation operator Location\. + * @return The newly allocated and initialized node. + */ +static inline pm_alternation_pattern_node_t * +pm_alternation_pattern_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *left, struct pm_node *right, pm_location_t operator_loc) { + pm_alternation_pattern_node_t *node = (pm_alternation_pattern_node_t *) pm_arena_alloc(arena, sizeof(pm_alternation_pattern_node_t), PRISM_ALIGNOF(pm_alternation_pattern_node_t)); + + *node = (pm_alternation_pattern_node_t) { + .base = { .type = PM_ALTERNATION_PATTERN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .left = left, + .right = right, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new AndNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param left Represents the left side of the expression\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param right Represents the right side of the expression\. + * @param operator_loc The Location of the \`and\` keyword or the \`&&\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_and_node_t * +pm_and_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *left, struct pm_node *right, pm_location_t operator_loc) { + pm_and_node_t *node = (pm_and_node_t *) pm_arena_alloc(arena, sizeof(pm_and_node_t), PRISM_ALIGNOF(pm_and_node_t)); + + *node = (pm_and_node_t) { + .base = { .type = PM_AND_NODE, .flags = flags, .node_id = node_id, .location = location }, + .left = left, + .right = right, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ArgumentsNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param arguments The list of arguments, if present\. These can be any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @return The newly allocated and initialized node. + */ +static inline pm_arguments_node_t * +pm_arguments_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t arguments) { + pm_arguments_node_t *node = (pm_arguments_node_t *) pm_arena_alloc(arena, sizeof(pm_arguments_node_t), PRISM_ALIGNOF(pm_arguments_node_t)); + + *node = (pm_arguments_node_t) { + .base = { .type = PM_ARGUMENTS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .arguments = arguments + }; + + return node; +} + +/** + * Allocate and initialize a new ArrayNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param elements Represent the list of zero or more [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression) within the array\. + * @param opening_loc Represents the optional source Location for the opening token\. + * @param closing_loc Represents the optional source Location for the closing token\. + * @return The newly allocated and initialized node. + */ +static inline pm_array_node_t * +pm_array_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t elements, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_array_node_t *node = (pm_array_node_t *) pm_arena_alloc(arena, sizeof(pm_array_node_t), PRISM_ALIGNOF(pm_array_node_t)); + + *node = (pm_array_node_t) { + .base = { .type = PM_ARRAY_NODE, .flags = flags, .node_id = node_id, .location = location }, + .elements = elements, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ArrayPatternNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param constant Represents the optional constant preceding the Array + * @param requireds Represents the required elements of the array pattern\. + * @param rest Represents the rest element of the array pattern\. + * @param posts Represents the elements after the rest element of the array pattern\. + * @param opening_loc Represents the opening Location of the array pattern\. + * @param closing_loc Represents the closing Location of the array pattern\. + * @return The newly allocated and initialized node. + */ +static inline pm_array_pattern_node_t * +pm_array_pattern_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *constant, pm_node_list_t requireds, struct pm_node *rest, pm_node_list_t posts, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_array_pattern_node_t *node = (pm_array_pattern_node_t *) pm_arena_alloc(arena, sizeof(pm_array_pattern_node_t), PRISM_ALIGNOF(pm_array_pattern_node_t)); + + *node = (pm_array_pattern_node_t) { + .base = { .type = PM_ARRAY_PATTERN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .constant = constant, + .requireds = requireds, + .rest = rest, + .posts = posts, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new AssocNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param key The key of the association\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param value The value of the association, if present\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\>\` operator, if present\. + * @return The newly allocated and initialized node. + */ +static inline pm_assoc_node_t * +pm_assoc_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *key, struct pm_node *value, pm_location_t operator_loc) { + pm_assoc_node_t *node = (pm_assoc_node_t *) pm_arena_alloc(arena, sizeof(pm_assoc_node_t), PRISM_ALIGNOF(pm_assoc_node_t)); + + *node = (pm_assoc_node_t) { + .base = { .type = PM_ASSOC_NODE, .flags = flags, .node_id = node_id, .location = location }, + .key = key, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new AssocSplatNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value The value to be splatted, if present\. Will be missing when keyword rest argument forwarding is used\. + * @param operator_loc The Location of the \`\*\*\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_assoc_splat_node_t * +pm_assoc_splat_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *value, pm_location_t operator_loc) { + pm_assoc_splat_node_t *node = (pm_assoc_splat_node_t *) pm_arena_alloc(arena, sizeof(pm_assoc_splat_node_t), PRISM_ALIGNOF(pm_assoc_splat_node_t)); + + *node = (pm_assoc_splat_node_t) { + .base = { .type = PM_ASSOC_SPLAT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BackReferenceReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the back\-reference variable, including the leading \`$\`\. + * @return The newly allocated and initialized node. + */ +static inline pm_back_reference_read_node_t * +pm_back_reference_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_back_reference_read_node_t *node = (pm_back_reference_read_node_t *) pm_arena_alloc(arena, sizeof(pm_back_reference_read_node_t), PRISM_ALIGNOF(pm_back_reference_read_node_t)); + + *node = (pm_back_reference_read_node_t) { + .base = { .type = PM_BACK_REFERENCE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new BeginNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param begin_keyword_loc Represents the Location of the \`begin\` keyword\. + * @param statements Represents the statements within the begin block\. + * @param rescue_clause Represents the rescue clause within the begin block\. + * @param else_clause Represents the else clause within the begin block\. + * @param ensure_clause Represents the ensure clause within the begin block\. + * @param end_keyword_loc Represents the Location of the \`end\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_begin_node_t * +pm_begin_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t begin_keyword_loc, struct pm_statements_node *statements, struct pm_rescue_node *rescue_clause, struct pm_else_node *else_clause, struct pm_ensure_node *ensure_clause, pm_location_t end_keyword_loc) { + pm_begin_node_t *node = (pm_begin_node_t *) pm_arena_alloc(arena, sizeof(pm_begin_node_t), PRISM_ALIGNOF(pm_begin_node_t)); + + *node = (pm_begin_node_t) { + .base = { .type = PM_BEGIN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .begin_keyword_loc = begin_keyword_loc, + .statements = statements, + .rescue_clause = rescue_clause, + .else_clause = else_clause, + .ensure_clause = ensure_clause, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BlockArgumentNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param expression The expression that is being passed as a block argument\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc Represents the Location of the \`&\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_block_argument_node_t * +pm_block_argument_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *expression, pm_location_t operator_loc) { + pm_block_argument_node_t *node = (pm_block_argument_node_t *) pm_arena_alloc(arena, sizeof(pm_block_argument_node_t), PRISM_ALIGNOF(pm_block_argument_node_t)); + + *node = (pm_block_argument_node_t) { + .base = { .type = PM_BLOCK_ARGUMENT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .expression = expression, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BlockLocalVariableNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the block local variable\. + * @return The newly allocated and initialized node. + */ +static inline pm_block_local_variable_node_t * +pm_block_local_variable_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_block_local_variable_node_t *node = (pm_block_local_variable_node_t *) pm_arena_alloc(arena, sizeof(pm_block_local_variable_node_t), PRISM_ALIGNOF(pm_block_local_variable_node_t)); + + *node = (pm_block_local_variable_node_t) { + .base = { .type = PM_BLOCK_LOCAL_VARIABLE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new BlockNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The local variables declared in the block\. + * @param parameters The parameters of the block\. + * @param body The body of the block\. + * @param opening_loc Represents the Location of the opening \`{\` or \`do\`\. + * @param closing_loc Represents the Location of the closing \`}\` or \`end\`\. + * @return The newly allocated and initialized node. + */ +static inline pm_block_node_t * +pm_block_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, struct pm_node *parameters, struct pm_node *body, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_block_node_t *node = (pm_block_node_t *) pm_arena_alloc(arena, sizeof(pm_block_node_t), PRISM_ALIGNOF(pm_block_node_t)); + + *node = (pm_block_node_t) { + .base = { .type = PM_BLOCK_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .parameters = parameters, + .body = body, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BlockParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the block parameter\. + * @param name_loc Represents the Location of the block parameter name\. + * @param operator_loc Represents the Location of the \`&\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_block_parameter_node_t * +pm_block_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc) { + pm_block_parameter_node_t *node = (pm_block_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_block_parameter_node_t), PRISM_ALIGNOF(pm_block_parameter_node_t)); + + *node = (pm_block_parameter_node_t) { + .base = { .type = PM_BLOCK_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BlockParametersNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param parameters Represents the parameters of the block\. + * @param locals Represents the local variables of the block\. + * @param opening_loc Represents the opening Location of the block parameters\. + * @param closing_loc Represents the closing Location of the block parameters\. + * @return The newly allocated and initialized node. + */ +static inline pm_block_parameters_node_t * +pm_block_parameters_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_parameters_node *parameters, pm_node_list_t locals, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_block_parameters_node_t *node = (pm_block_parameters_node_t *) pm_arena_alloc(arena, sizeof(pm_block_parameters_node_t), PRISM_ALIGNOF(pm_block_parameters_node_t)); + + *node = (pm_block_parameters_node_t) { + .base = { .type = PM_BLOCK_PARAMETERS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .parameters = parameters, + .locals = locals, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new BreakNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param arguments The arguments to the break statement, if present\. These can be any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param keyword_loc The Location of the \`break\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_break_node_t * +pm_break_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_arguments_node *arguments, pm_location_t keyword_loc) { + pm_break_node_t *node = (pm_break_node_t *) pm_arena_alloc(arena, sizeof(pm_break_node_t), PRISM_ALIGNOF(pm_break_node_t)); + + *node = (pm_break_node_t) { + .base = { .type = PM_BREAK_NODE, .flags = flags, .node_id = node_id, .location = location }, + .arguments = arguments, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new CallAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The object that the method is being called on\. This can be either \`nil\` or any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param call_operator_loc Represents the Location of the call operator\. + * @param message_loc Represents the Location of the message\. + * @param read_name Represents the name of the method being called\. + * @param write_name Represents the name of the method being written to\. + * @param operator_loc Represents the Location of the operator\. + * @param value Represents the value being assigned\. + * @return The newly allocated and initialized node. + */ +static inline pm_call_and_write_node_t * +pm_call_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t message_loc, pm_constant_id_t read_name, pm_constant_id_t write_name, pm_location_t operator_loc, struct pm_node *value) { + pm_call_and_write_node_t *node = (pm_call_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_call_and_write_node_t), PRISM_ALIGNOF(pm_call_and_write_node_t)); + + *node = (pm_call_and_write_node_t) { + .base = { .type = PM_CALL_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .message_loc = message_loc, + .read_name = read_name, + .write_name = write_name, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new CallNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The object that the method is being called on\. This can be either \`nil\` or any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param call_operator_loc Represents the Location of the call operator\. + * @param name Represents the name of the method being called\. + * @param message_loc Represents the Location of the message\. + * @param opening_loc Represents the Location of the left parenthesis\. + * @param arguments Represents the arguments to the method call\. These can be any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param closing_loc Represents the Location of the right parenthesis\. + * @param equal_loc Represents the Location of the equal sign, in the case that this is an attribute write\. + * @param block Represents the block that is being passed to the method\. + * @return The newly allocated and initialized node. + */ +static inline pm_call_node_t * +pm_call_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_constant_id_t name, pm_location_t message_loc, pm_location_t opening_loc, struct pm_arguments_node *arguments, pm_location_t closing_loc, pm_location_t equal_loc, struct pm_node *block) { + pm_call_node_t *node = (pm_call_node_t *) pm_arena_alloc(arena, sizeof(pm_call_node_t), PRISM_ALIGNOF(pm_call_node_t)); + + *node = (pm_call_node_t) { + .base = { .type = PM_CALL_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .name = name, + .message_loc = message_loc, + .opening_loc = opening_loc, + .arguments = arguments, + .closing_loc = closing_loc, + .equal_loc = equal_loc, + .block = block + }; + + return node; +} + +/** + * Allocate and initialize a new CallOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The object that the method is being called on\. This can be either \`nil\` or any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param call_operator_loc Represents the Location of the call operator\. + * @param message_loc Represents the Location of the message\. + * @param read_name Represents the name of the method being called\. + * @param write_name Represents the name of the method being written to\. + * @param binary_operator Represents the binary operator being used\. + * @param binary_operator_loc Represents the Location of the binary operator\. + * @param value Represents the value being assigned\. + * @return The newly allocated and initialized node. + */ +static inline pm_call_operator_write_node_t * +pm_call_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t message_loc, pm_constant_id_t read_name, pm_constant_id_t write_name, pm_constant_id_t binary_operator, pm_location_t binary_operator_loc, struct pm_node *value) { + pm_call_operator_write_node_t *node = (pm_call_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_call_operator_write_node_t), PRISM_ALIGNOF(pm_call_operator_write_node_t)); + + *node = (pm_call_operator_write_node_t) { + .base = { .type = PM_CALL_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .message_loc = message_loc, + .read_name = read_name, + .write_name = write_name, + .binary_operator = binary_operator, + .binary_operator_loc = binary_operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new CallOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The object that the method is being called on\. This can be either \`nil\` or any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param call_operator_loc Represents the Location of the call operator\. + * @param message_loc Represents the Location of the message\. + * @param read_name Represents the name of the method being called\. + * @param write_name Represents the name of the method being written to\. + * @param operator_loc Represents the Location of the operator\. + * @param value Represents the value being assigned\. + * @return The newly allocated and initialized node. + */ +static inline pm_call_or_write_node_t * +pm_call_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t message_loc, pm_constant_id_t read_name, pm_constant_id_t write_name, pm_location_t operator_loc, struct pm_node *value) { + pm_call_or_write_node_t *node = (pm_call_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_call_or_write_node_t), PRISM_ALIGNOF(pm_call_or_write_node_t)); + + *node = (pm_call_or_write_node_t) { + .base = { .type = PM_CALL_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .message_loc = message_loc, + .read_name = read_name, + .write_name = write_name, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new CallTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The object that the method is being called on\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param call_operator_loc Represents the Location of the call operator\. + * @param name Represents the name of the method being called\. + * @param message_loc Represents the Location of the message\. + * @return The newly allocated and initialized node. + */ +static inline pm_call_target_node_t * +pm_call_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_constant_id_t name, pm_location_t message_loc) { + pm_call_target_node_t *node = (pm_call_target_node_t *) pm_arena_alloc(arena, sizeof(pm_call_target_node_t), PRISM_ALIGNOF(pm_call_target_node_t)); + + *node = (pm_call_target_node_t) { + .base = { .type = PM_CALL_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .name = name, + .message_loc = message_loc + }; + + return node; +} + +/** + * Allocate and initialize a new CapturePatternNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value Represents the value to capture\. + * @param target Represents the target of the capture\. + * @param operator_loc Represents the Location of the \`=\>\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_capture_pattern_node_t * +pm_capture_pattern_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *value, struct pm_local_variable_target_node *target, pm_location_t operator_loc) { + pm_capture_pattern_node_t *node = (pm_capture_pattern_node_t *) pm_arena_alloc(arena, sizeof(pm_capture_pattern_node_t), PRISM_ALIGNOF(pm_capture_pattern_node_t)); + + *node = (pm_capture_pattern_node_t) { + .base = { .type = PM_CAPTURE_PATTERN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value, + .target = target, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new CaseMatchNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param predicate Represents the predicate of the case match\. This can be either \`nil\` or any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param conditions Represents the conditions of the case match\. + * @param else_clause Represents the else clause of the case match\. + * @param case_keyword_loc Represents the Location of the \`case\` keyword\. + * @param end_keyword_loc Represents the Location of the \`end\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_case_match_node_t * +pm_case_match_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *predicate, pm_node_list_t conditions, struct pm_else_node *else_clause, pm_location_t case_keyword_loc, pm_location_t end_keyword_loc) { + pm_case_match_node_t *node = (pm_case_match_node_t *) pm_arena_alloc(arena, sizeof(pm_case_match_node_t), PRISM_ALIGNOF(pm_case_match_node_t)); + + *node = (pm_case_match_node_t) { + .base = { .type = PM_CASE_MATCH_NODE, .flags = flags, .node_id = node_id, .location = location }, + .predicate = predicate, + .conditions = conditions, + .else_clause = else_clause, + .case_keyword_loc = case_keyword_loc, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new CaseNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param predicate Represents the predicate of the case statement\. This can be either \`nil\` or any [non\-void expressions](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param conditions Represents the conditions of the case statement\. + * @param else_clause Represents the else clause of the case statement\. + * @param case_keyword_loc Represents the Location of the \`case\` keyword\. + * @param end_keyword_loc Represents the Location of the \`end\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_case_node_t * +pm_case_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *predicate, pm_node_list_t conditions, struct pm_else_node *else_clause, pm_location_t case_keyword_loc, pm_location_t end_keyword_loc) { + pm_case_node_t *node = (pm_case_node_t *) pm_arena_alloc(arena, sizeof(pm_case_node_t), PRISM_ALIGNOF(pm_case_node_t)); + + *node = (pm_case_node_t) { + .base = { .type = PM_CASE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .predicate = predicate, + .conditions = conditions, + .else_clause = else_clause, + .case_keyword_loc = case_keyword_loc, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ClassNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The locals field. + * @param class_keyword_loc Represents the Location of the \`class\` keyword\. + * @param constant_path The constant_path field. + * @param inheritance_operator_loc Represents the Location of the \`\<\` operator\. + * @param superclass Represents the superclass of the class\. + * @param body Represents the body of the class\. + * @param end_keyword_loc Represents the Location of the \`end\` keyword\. + * @param name The name of the class\. + * @return The newly allocated and initialized node. + */ +static inline pm_class_node_t * +pm_class_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, pm_location_t class_keyword_loc, struct pm_node *constant_path, pm_location_t inheritance_operator_loc, struct pm_node *superclass, struct pm_node *body, pm_location_t end_keyword_loc, pm_constant_id_t name) { + pm_class_node_t *node = (pm_class_node_t *) pm_arena_alloc(arena, sizeof(pm_class_node_t), PRISM_ALIGNOF(pm_class_node_t)); + + *node = (pm_class_node_t) { + .base = { .type = PM_CLASS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .class_keyword_loc = class_keyword_loc, + .constant_path = constant_path, + .inheritance_operator_loc = inheritance_operator_loc, + .superclass = superclass, + .body = body, + .end_keyword_loc = end_keyword_loc, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the class variable, which is a \`@@\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @param name_loc Represents the Location of the variable name\. + * @param operator_loc Represents the Location of the \`&&=\` operator\. + * @param value Represents the value being assigned\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_and_write_node_t * +pm_class_variable_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_class_variable_and_write_node_t *node = (pm_class_variable_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_and_write_node_t), PRISM_ALIGNOF(pm_class_variable_and_write_node_t)); + + *node = (pm_class_variable_and_write_node_t) { + .base = { .type = PM_CLASS_VARIABLE_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param binary_operator The binary_operator field. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_operator_write_node_t * +pm_class_variable_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t binary_operator) { + pm_class_variable_operator_write_node_t *node = (pm_class_variable_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_operator_write_node_t), PRISM_ALIGNOF(pm_class_variable_operator_write_node_t)); + + *node = (pm_class_variable_operator_write_node_t) { + .base = { .type = PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .binary_operator_loc = binary_operator_loc, + .value = value, + .binary_operator = binary_operator + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_or_write_node_t * +pm_class_variable_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_class_variable_or_write_node_t *node = (pm_class_variable_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_or_write_node_t), PRISM_ALIGNOF(pm_class_variable_or_write_node_t)); + + *node = (pm_class_variable_or_write_node_t) { + .base = { .type = PM_CLASS_VARIABLE_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the class variable, which is a \`@@\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_read_node_t * +pm_class_variable_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_class_variable_read_node_t *node = (pm_class_variable_read_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_read_node_t), PRISM_ALIGNOF(pm_class_variable_read_node_t)); + + *node = (pm_class_variable_read_node_t) { + .base = { .type = PM_CLASS_VARIABLE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_target_node_t * +pm_class_variable_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_class_variable_target_node_t *node = (pm_class_variable_target_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_target_node_t), PRISM_ALIGNOF(pm_class_variable_target_node_t)); + + *node = (pm_class_variable_target_node_t) { + .base = { .type = PM_CLASS_VARIABLE_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new ClassVariableWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the class variable, which is a \`@@\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @param name_loc The Location of the variable name\. + * @param value The value to write to the class variable\. This can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_class_variable_write_node_t * +pm_class_variable_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *value, pm_location_t operator_loc) { + pm_class_variable_write_node_t *node = (pm_class_variable_write_node_t *) pm_arena_alloc(arena, sizeof(pm_class_variable_write_node_t), PRISM_ALIGNOF(pm_class_variable_write_node_t)); + + *node = (pm_class_variable_write_node_t) { + .base = { .type = PM_CLASS_VARIABLE_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_and_write_node_t * +pm_constant_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_constant_and_write_node_t *node = (pm_constant_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_and_write_node_t), PRISM_ALIGNOF(pm_constant_and_write_node_t)); + + *node = (pm_constant_and_write_node_t) { + .base = { .type = PM_CONSTANT_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param binary_operator The binary_operator field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_operator_write_node_t * +pm_constant_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t binary_operator) { + pm_constant_operator_write_node_t *node = (pm_constant_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_operator_write_node_t), PRISM_ALIGNOF(pm_constant_operator_write_node_t)); + + *node = (pm_constant_operator_write_node_t) { + .base = { .type = PM_CONSTANT_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .binary_operator_loc = binary_operator_loc, + .value = value, + .binary_operator = binary_operator + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_or_write_node_t * +pm_constant_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_constant_or_write_node_t *node = (pm_constant_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_or_write_node_t), PRISM_ALIGNOF(pm_constant_or_write_node_t)); + + *node = (pm_constant_or_write_node_t) { + .base = { .type = PM_CONSTANT_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param target The target field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_and_write_node_t * +pm_constant_path_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_constant_path_node *target, pm_location_t operator_loc, struct pm_node *value) { + pm_constant_path_and_write_node_t *node = (pm_constant_path_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_and_write_node_t), PRISM_ALIGNOF(pm_constant_path_and_write_node_t)); + + *node = (pm_constant_path_and_write_node_t) { + .base = { .type = PM_CONSTANT_PATH_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .target = target, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param parent The left\-hand node of the path, if present\. It can be \`nil\` or any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. It will be \`nil\` when the constant lookup is at the root of the module tree\. + * @param name The name of the constant being accessed\. This could be \`nil\` in the event of a syntax error\. + * @param delimiter_loc The Location of the \`::\` delimiter\. + * @param name_loc The Location of the name of the constant\. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_node_t * +pm_constant_path_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *parent, pm_constant_id_t name, pm_location_t delimiter_loc, pm_location_t name_loc) { + pm_constant_path_node_t *node = (pm_constant_path_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_node_t), PRISM_ALIGNOF(pm_constant_path_node_t)); + + *node = (pm_constant_path_node_t) { + .base = { .type = PM_CONSTANT_PATH_NODE, .flags = flags, .node_id = node_id, .location = location }, + .parent = parent, + .name = name, + .delimiter_loc = delimiter_loc, + .name_loc = name_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param target The target field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param binary_operator The binary_operator field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_operator_write_node_t * +pm_constant_path_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_constant_path_node *target, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t binary_operator) { + pm_constant_path_operator_write_node_t *node = (pm_constant_path_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_operator_write_node_t), PRISM_ALIGNOF(pm_constant_path_operator_write_node_t)); + + *node = (pm_constant_path_operator_write_node_t) { + .base = { .type = PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .target = target, + .binary_operator_loc = binary_operator_loc, + .value = value, + .binary_operator = binary_operator + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param target The target field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_or_write_node_t * +pm_constant_path_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_constant_path_node *target, pm_location_t operator_loc, struct pm_node *value) { + pm_constant_path_or_write_node_t *node = (pm_constant_path_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_or_write_node_t), PRISM_ALIGNOF(pm_constant_path_or_write_node_t)); + + *node = (pm_constant_path_or_write_node_t) { + .base = { .type = PM_CONSTANT_PATH_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .target = target, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param parent The parent field. + * @param name The name field. + * @param delimiter_loc The delimiter_loc field. + * @param name_loc The name_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_target_node_t * +pm_constant_path_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *parent, pm_constant_id_t name, pm_location_t delimiter_loc, pm_location_t name_loc) { + pm_constant_path_target_node_t *node = (pm_constant_path_target_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_target_node_t), PRISM_ALIGNOF(pm_constant_path_target_node_t)); + + *node = (pm_constant_path_target_node_t) { + .base = { .type = PM_CONSTANT_PATH_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .parent = parent, + .name = name, + .delimiter_loc = delimiter_loc, + .name_loc = name_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantPathWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param target A node representing the constant path being written to\. + * @param operator_loc The Location of the \`=\` operator\. + * @param value The value to write to the constant path\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_path_write_node_t * +pm_constant_path_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_constant_path_node *target, pm_location_t operator_loc, struct pm_node *value) { + pm_constant_path_write_node_t *node = (pm_constant_path_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_path_write_node_t), PRISM_ALIGNOF(pm_constant_path_write_node_t)); + + *node = (pm_constant_path_write_node_t) { + .base = { .type = PM_CONSTANT_PATH_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .target = target, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the [constant](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#constants)\. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_read_node_t * +pm_constant_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_constant_read_node_t *node = (pm_constant_read_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_read_node_t), PRISM_ALIGNOF(pm_constant_read_node_t)); + + *node = (pm_constant_read_node_t) { + .base = { .type = PM_CONSTANT_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_target_node_t * +pm_constant_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_constant_target_node_t *node = (pm_constant_target_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_target_node_t), PRISM_ALIGNOF(pm_constant_target_node_t)); + + *node = (pm_constant_target_node_t) { + .base = { .type = PM_CONSTANT_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new ConstantWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the [constant](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#constants)\. + * @param name_loc The Location of the constant name\. + * @param value The value to write to the constant\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_constant_write_node_t * +pm_constant_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *value, pm_location_t operator_loc) { + pm_constant_write_node_t *node = (pm_constant_write_node_t *) pm_arena_alloc(arena, sizeof(pm_constant_write_node_t), PRISM_ALIGNOF(pm_constant_write_node_t)); + + *node = (pm_constant_write_node_t) { + .base = { .type = PM_CONSTANT_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new DefNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param receiver The receiver field. + * @param parameters The parameters field. + * @param body The body field. + * @param locals The locals field. + * @param def_keyword_loc The def_keyword_loc field. + * @param operator_loc The operator_loc field. + * @param lparen_loc The lparen_loc field. + * @param rparen_loc The rparen_loc field. + * @param equal_loc The equal_loc field. + * @param end_keyword_loc The end_keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_def_node_t * +pm_def_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *receiver, struct pm_parameters_node *parameters, struct pm_node *body, pm_constant_id_list_t locals, pm_location_t def_keyword_loc, pm_location_t operator_loc, pm_location_t lparen_loc, pm_location_t rparen_loc, pm_location_t equal_loc, pm_location_t end_keyword_loc) { + pm_def_node_t *node = (pm_def_node_t *) pm_arena_alloc(arena, sizeof(pm_def_node_t), PRISM_ALIGNOF(pm_def_node_t)); + + *node = (pm_def_node_t) { + .base = { .type = PM_DEF_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .receiver = receiver, + .parameters = parameters, + .body = body, + .locals = locals, + .def_keyword_loc = def_keyword_loc, + .operator_loc = operator_loc, + .lparen_loc = lparen_loc, + .rparen_loc = rparen_loc, + .equal_loc = equal_loc, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new DefinedNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param lparen_loc The lparen_loc field. + * @param value The value field. + * @param rparen_loc The rparen_loc field. + * @param keyword_loc The keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_defined_node_t * +pm_defined_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t lparen_loc, struct pm_node *value, pm_location_t rparen_loc, pm_location_t keyword_loc) { + pm_defined_node_t *node = (pm_defined_node_t *) pm_arena_alloc(arena, sizeof(pm_defined_node_t), PRISM_ALIGNOF(pm_defined_node_t)); + + *node = (pm_defined_node_t) { + .base = { .type = PM_DEFINED_NODE, .flags = flags, .node_id = node_id, .location = location }, + .lparen_loc = lparen_loc, + .value = value, + .rparen_loc = rparen_loc, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ElseNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param else_keyword_loc The else_keyword_loc field. + * @param statements The statements field. + * @param end_keyword_loc The end_keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_else_node_t * +pm_else_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t else_keyword_loc, struct pm_statements_node *statements, pm_location_t end_keyword_loc) { + pm_else_node_t *node = (pm_else_node_t *) pm_arena_alloc(arena, sizeof(pm_else_node_t), PRISM_ALIGNOF(pm_else_node_t)); + + *node = (pm_else_node_t) { + .base = { .type = PM_ELSE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .else_keyword_loc = else_keyword_loc, + .statements = statements, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new EmbeddedStatementsNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param statements The statements field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_embedded_statements_node_t * +pm_embedded_statements_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, struct pm_statements_node *statements, pm_location_t closing_loc) { + pm_embedded_statements_node_t *node = (pm_embedded_statements_node_t *) pm_arena_alloc(arena, sizeof(pm_embedded_statements_node_t), PRISM_ALIGNOF(pm_embedded_statements_node_t)); + + *node = (pm_embedded_statements_node_t) { + .base = { .type = PM_EMBEDDED_STATEMENTS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .statements = statements, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new EmbeddedVariableNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param operator_loc The operator_loc field. + * @param variable The variable field. + * @return The newly allocated and initialized node. + */ +static inline pm_embedded_variable_node_t * +pm_embedded_variable_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t operator_loc, struct pm_node *variable) { + pm_embedded_variable_node_t *node = (pm_embedded_variable_node_t *) pm_arena_alloc(arena, sizeof(pm_embedded_variable_node_t), PRISM_ALIGNOF(pm_embedded_variable_node_t)); + + *node = (pm_embedded_variable_node_t) { + .base = { .type = PM_EMBEDDED_VARIABLE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .operator_loc = operator_loc, + .variable = variable + }; + + return node; +} + +/** + * Allocate and initialize a new EnsureNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param ensure_keyword_loc The ensure_keyword_loc field. + * @param statements The statements field. + * @param end_keyword_loc The end_keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_ensure_node_t * +pm_ensure_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t ensure_keyword_loc, struct pm_statements_node *statements, pm_location_t end_keyword_loc) { + pm_ensure_node_t *node = (pm_ensure_node_t *) pm_arena_alloc(arena, sizeof(pm_ensure_node_t), PRISM_ALIGNOF(pm_ensure_node_t)); + + *node = (pm_ensure_node_t) { + .base = { .type = PM_ENSURE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .ensure_keyword_loc = ensure_keyword_loc, + .statements = statements, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new FalseNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_false_node_t * +pm_false_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_false_node_t *node = (pm_false_node_t *) pm_arena_alloc(arena, sizeof(pm_false_node_t), PRISM_ALIGNOF(pm_false_node_t)); + + *node = (pm_false_node_t) { + .base = { .type = PM_FALSE_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new FindPatternNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param constant Represents the optional constant preceding the pattern + * @param left Represents the first wildcard node in the pattern\. + * @param requireds Represents the nodes in between the wildcards\. + * @param right Represents the second wildcard node in the pattern\. + * @param opening_loc The Location of the opening brace\. + * @param closing_loc The Location of the closing brace\. + * @return The newly allocated and initialized node. + */ +static inline pm_find_pattern_node_t * +pm_find_pattern_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *constant, struct pm_splat_node *left, pm_node_list_t requireds, struct pm_node *right, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_find_pattern_node_t *node = (pm_find_pattern_node_t *) pm_arena_alloc(arena, sizeof(pm_find_pattern_node_t), PRISM_ALIGNOF(pm_find_pattern_node_t)); + + *node = (pm_find_pattern_node_t) { + .base = { .type = PM_FIND_PATTERN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .constant = constant, + .left = left, + .requireds = requireds, + .right = right, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new FlipFlopNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param left The left field. + * @param right The right field. + * @param operator_loc The operator_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_flip_flop_node_t * +pm_flip_flop_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *left, struct pm_node *right, pm_location_t operator_loc) { + pm_flip_flop_node_t *node = (pm_flip_flop_node_t *) pm_arena_alloc(arena, sizeof(pm_flip_flop_node_t), PRISM_ALIGNOF(pm_flip_flop_node_t)); + + *node = (pm_flip_flop_node_t) { + .base = { .type = PM_FLIP_FLOP_NODE, .flags = flags, .node_id = node_id, .location = location }, + .left = left, + .right = right, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new FloatNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value The value of the floating point number as a Float\. + * @return The newly allocated and initialized node. + */ +static inline pm_float_node_t * +pm_float_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, double value) { + pm_float_node_t *node = (pm_float_node_t *) pm_arena_alloc(arena, sizeof(pm_float_node_t), PRISM_ALIGNOF(pm_float_node_t)); + + *node = (pm_float_node_t) { + .base = { .type = PM_FLOAT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ForNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param index The index expression for \`for\` loops\. + * @param collection The collection to iterate over\. + * @param statements Represents the body of statements to execute for each iteration of the loop\. + * @param for_keyword_loc The Location of the \`for\` keyword\. + * @param in_keyword_loc The Location of the \`in\` keyword\. + * @param do_keyword_loc The Location of the \`do\` keyword, if present\. + * @param end_keyword_loc The Location of the \`end\` keyword\. + * @return The newly allocated and initialized node. + */ +static inline pm_for_node_t * +pm_for_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *index, struct pm_node *collection, struct pm_statements_node *statements, pm_location_t for_keyword_loc, pm_location_t in_keyword_loc, pm_location_t do_keyword_loc, pm_location_t end_keyword_loc) { + pm_for_node_t *node = (pm_for_node_t *) pm_arena_alloc(arena, sizeof(pm_for_node_t), PRISM_ALIGNOF(pm_for_node_t)); + + *node = (pm_for_node_t) { + .base = { .type = PM_FOR_NODE, .flags = flags, .node_id = node_id, .location = location }, + .index = index, + .collection = collection, + .statements = statements, + .for_keyword_loc = for_keyword_loc, + .in_keyword_loc = in_keyword_loc, + .do_keyword_loc = do_keyword_loc, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ForwardingArgumentsNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_forwarding_arguments_node_t * +pm_forwarding_arguments_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_forwarding_arguments_node_t *node = (pm_forwarding_arguments_node_t *) pm_arena_alloc(arena, sizeof(pm_forwarding_arguments_node_t), PRISM_ALIGNOF(pm_forwarding_arguments_node_t)); + + *node = (pm_forwarding_arguments_node_t) { + .base = { .type = PM_FORWARDING_ARGUMENTS_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ForwardingParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_forwarding_parameter_node_t * +pm_forwarding_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_forwarding_parameter_node_t *node = (pm_forwarding_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_forwarding_parameter_node_t), PRISM_ALIGNOF(pm_forwarding_parameter_node_t)); + + *node = (pm_forwarding_parameter_node_t) { + .base = { .type = PM_FORWARDING_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ForwardingSuperNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param block All other arguments are forwarded as normal, except the original block is replaced with the new block\. + * @return The newly allocated and initialized node. + */ +static inline pm_forwarding_super_node_t * +pm_forwarding_super_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_block_node *block) { + pm_forwarding_super_node_t *node = (pm_forwarding_super_node_t *) pm_arena_alloc(arena, sizeof(pm_forwarding_super_node_t), PRISM_ALIGNOF(pm_forwarding_super_node_t)); + + *node = (pm_forwarding_super_node_t) { + .base = { .type = PM_FORWARDING_SUPER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .block = block + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_and_write_node_t * +pm_global_variable_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_global_variable_and_write_node_t *node = (pm_global_variable_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_and_write_node_t), PRISM_ALIGNOF(pm_global_variable_and_write_node_t)); + + *node = (pm_global_variable_and_write_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param binary_operator The binary_operator field. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_operator_write_node_t * +pm_global_variable_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t binary_operator) { + pm_global_variable_operator_write_node_t *node = (pm_global_variable_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_operator_write_node_t), PRISM_ALIGNOF(pm_global_variable_operator_write_node_t)); + + *node = (pm_global_variable_operator_write_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .binary_operator_loc = binary_operator_loc, + .value = value, + .binary_operator = binary_operator + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_or_write_node_t * +pm_global_variable_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_global_variable_or_write_node_t *node = (pm_global_variable_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_or_write_node_t), PRISM_ALIGNOF(pm_global_variable_or_write_node_t)); + + *node = (pm_global_variable_or_write_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the global variable, which is a \`$\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifier)\. Alternatively, it can be one of the special global variables designated by a symbol\. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_read_node_t * +pm_global_variable_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_global_variable_read_node_t *node = (pm_global_variable_read_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_read_node_t), PRISM_ALIGNOF(pm_global_variable_read_node_t)); + + *node = (pm_global_variable_read_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_target_node_t * +pm_global_variable_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_global_variable_target_node_t *node = (pm_global_variable_target_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_target_node_t), PRISM_ALIGNOF(pm_global_variable_target_node_t)); + + *node = (pm_global_variable_target_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new GlobalVariableWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the global variable, which is a \`$\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifier)\. Alternatively, it can be one of the special global variables designated by a symbol\. + * @param name_loc The Location of the global variable's name\. + * @param value The value to write to the global variable\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_global_variable_write_node_t * +pm_global_variable_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *value, pm_location_t operator_loc) { + pm_global_variable_write_node_t *node = (pm_global_variable_write_node_t *) pm_arena_alloc(arena, sizeof(pm_global_variable_write_node_t), PRISM_ALIGNOF(pm_global_variable_write_node_t)); + + *node = (pm_global_variable_write_node_t) { + .base = { .type = PM_GLOBAL_VARIABLE_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new HashNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The Location of the opening brace\. + * @param elements The elements of the hash\. These can be either \`AssocNode\`s or \`AssocSplatNode\`s\. + * @param closing_loc The Location of the closing brace\. + * @return The newly allocated and initialized node. + */ +static inline pm_hash_node_t * +pm_hash_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t elements, pm_location_t closing_loc) { + pm_hash_node_t *node = (pm_hash_node_t *) pm_arena_alloc(arena, sizeof(pm_hash_node_t), PRISM_ALIGNOF(pm_hash_node_t)); + + *node = (pm_hash_node_t) { + .base = { .type = PM_HASH_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .elements = elements, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new HashPatternNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param constant Represents the optional constant preceding the Hash\. + * @param elements Represents the explicit named hash keys and values\. + * @param rest Represents the rest of the Hash keys and values\. This can be named, unnamed, or explicitly forbidden via \`\*\*nil\`, this last one results in a \`NoKeywordsParameterNode\`\. + * @param opening_loc The Location of the opening brace\. + * @param closing_loc The Location of the closing brace\. + * @return The newly allocated and initialized node. + */ +static inline pm_hash_pattern_node_t * +pm_hash_pattern_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *constant, pm_node_list_t elements, struct pm_node *rest, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_hash_pattern_node_t *node = (pm_hash_pattern_node_t *) pm_arena_alloc(arena, sizeof(pm_hash_pattern_node_t), PRISM_ALIGNOF(pm_hash_pattern_node_t)); + + *node = (pm_hash_pattern_node_t) { + .base = { .type = PM_HASH_PATTERN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .constant = constant, + .elements = elements, + .rest = rest, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new IfNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param if_keyword_loc The Location of the \`if\` keyword if present\. + * @param predicate The node for the condition the \`IfNode\` is testing\. + * @param then_keyword_loc The Location of the \`then\` keyword (if present) or the \`?\` in a ternary expression, \`nil\` otherwise\. + * @param statements Represents the body of statements that will be executed when the predicate is evaluated as truthy\. Will be \`nil\` when no body is provided\. + * @param subsequent Represents an \`ElseNode\` or an \`IfNode\` when there is an \`else\` or an \`elsif\` in the \`if\` statement\. + * @param end_keyword_loc The Location of the \`end\` keyword if present, \`nil\` otherwise\. + * @return The newly allocated and initialized node. + */ +static inline pm_if_node_t * +pm_if_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t if_keyword_loc, struct pm_node *predicate, pm_location_t then_keyword_loc, struct pm_statements_node *statements, struct pm_node *subsequent, pm_location_t end_keyword_loc) { + pm_if_node_t *node = (pm_if_node_t *) pm_arena_alloc(arena, sizeof(pm_if_node_t), PRISM_ALIGNOF(pm_if_node_t)); + + *node = (pm_if_node_t) { + .base = { .type = PM_IF_NODE, .flags = flags, .node_id = node_id, .location = location }, + .if_keyword_loc = if_keyword_loc, + .predicate = predicate, + .then_keyword_loc = then_keyword_loc, + .statements = statements, + .subsequent = subsequent, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ImaginaryNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param numeric The numeric field. + * @return The newly allocated and initialized node. + */ +static inline pm_imaginary_node_t * +pm_imaginary_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *numeric) { + pm_imaginary_node_t *node = (pm_imaginary_node_t *) pm_arena_alloc(arena, sizeof(pm_imaginary_node_t), PRISM_ALIGNOF(pm_imaginary_node_t)); + + *node = (pm_imaginary_node_t) { + .base = { .type = PM_IMAGINARY_NODE, .flags = flags, .node_id = node_id, .location = location }, + .numeric = numeric + }; + + return node; +} + +/** + * Allocate and initialize a new ImplicitNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_implicit_node_t * +pm_implicit_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *value) { + pm_implicit_node_t *node = (pm_implicit_node_t *) pm_arena_alloc(arena, sizeof(pm_implicit_node_t), PRISM_ALIGNOF(pm_implicit_node_t)); + + *node = (pm_implicit_node_t) { + .base = { .type = PM_IMPLICIT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new ImplicitRestNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_implicit_rest_node_t * +pm_implicit_rest_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_implicit_rest_node_t *node = (pm_implicit_rest_node_t *) pm_arena_alloc(arena, sizeof(pm_implicit_rest_node_t), PRISM_ALIGNOF(pm_implicit_rest_node_t)); + + *node = (pm_implicit_rest_node_t) { + .base = { .type = PM_IMPLICIT_REST_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new InNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param pattern The pattern field. + * @param statements The statements field. + * @param in_loc The in_loc field. + * @param then_loc The then_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_in_node_t * +pm_in_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *pattern, struct pm_statements_node *statements, pm_location_t in_loc, pm_location_t then_loc) { + pm_in_node_t *node = (pm_in_node_t *) pm_arena_alloc(arena, sizeof(pm_in_node_t), PRISM_ALIGNOF(pm_in_node_t)); + + *node = (pm_in_node_t) { + .base = { .type = PM_IN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .pattern = pattern, + .statements = statements, + .in_loc = in_loc, + .then_loc = then_loc + }; + + return node; +} + +/** + * Allocate and initialize a new IndexAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The receiver field. + * @param call_operator_loc The call_operator_loc field. + * @param opening_loc The opening_loc field. + * @param arguments The arguments field. + * @param closing_loc The closing_loc field. + * @param block The block field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_index_and_write_node_t * +pm_index_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t opening_loc, struct pm_arguments_node *arguments, pm_location_t closing_loc, struct pm_block_argument_node *block, pm_location_t operator_loc, struct pm_node *value) { + pm_index_and_write_node_t *node = (pm_index_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_index_and_write_node_t), PRISM_ALIGNOF(pm_index_and_write_node_t)); + + *node = (pm_index_and_write_node_t) { + .base = { .type = PM_INDEX_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .opening_loc = opening_loc, + .arguments = arguments, + .closing_loc = closing_loc, + .block = block, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new IndexOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The receiver field. + * @param call_operator_loc The call_operator_loc field. + * @param opening_loc The opening_loc field. + * @param arguments The arguments field. + * @param closing_loc The closing_loc field. + * @param block The block field. + * @param binary_operator The binary_operator field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_index_operator_write_node_t * +pm_index_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t opening_loc, struct pm_arguments_node *arguments, pm_location_t closing_loc, struct pm_block_argument_node *block, pm_constant_id_t binary_operator, pm_location_t binary_operator_loc, struct pm_node *value) { + pm_index_operator_write_node_t *node = (pm_index_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_index_operator_write_node_t), PRISM_ALIGNOF(pm_index_operator_write_node_t)); + + *node = (pm_index_operator_write_node_t) { + .base = { .type = PM_INDEX_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .opening_loc = opening_loc, + .arguments = arguments, + .closing_loc = closing_loc, + .block = block, + .binary_operator = binary_operator, + .binary_operator_loc = binary_operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new IndexOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The receiver field. + * @param call_operator_loc The call_operator_loc field. + * @param opening_loc The opening_loc field. + * @param arguments The arguments field. + * @param closing_loc The closing_loc field. + * @param block The block field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_index_or_write_node_t * +pm_index_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t call_operator_loc, pm_location_t opening_loc, struct pm_arguments_node *arguments, pm_location_t closing_loc, struct pm_block_argument_node *block, pm_location_t operator_loc, struct pm_node *value) { + pm_index_or_write_node_t *node = (pm_index_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_index_or_write_node_t), PRISM_ALIGNOF(pm_index_or_write_node_t)); + + *node = (pm_index_or_write_node_t) { + .base = { .type = PM_INDEX_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .call_operator_loc = call_operator_loc, + .opening_loc = opening_loc, + .arguments = arguments, + .closing_loc = closing_loc, + .block = block, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new IndexTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param receiver The receiver field. + * @param opening_loc The opening_loc field. + * @param arguments The arguments field. + * @param closing_loc The closing_loc field. + * @param block The block field. + * @return The newly allocated and initialized node. + */ +static inline pm_index_target_node_t * +pm_index_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *receiver, pm_location_t opening_loc, struct pm_arguments_node *arguments, pm_location_t closing_loc, struct pm_block_argument_node *block) { + pm_index_target_node_t *node = (pm_index_target_node_t *) pm_arena_alloc(arena, sizeof(pm_index_target_node_t), PRISM_ALIGNOF(pm_index_target_node_t)); + + *node = (pm_index_target_node_t) { + .base = { .type = PM_INDEX_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .receiver = receiver, + .opening_loc = opening_loc, + .arguments = arguments, + .closing_loc = closing_loc, + .block = block + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_and_write_node_t * +pm_instance_variable_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_instance_variable_and_write_node_t *node = (pm_instance_variable_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_and_write_node_t), PRISM_ALIGNOF(pm_instance_variable_and_write_node_t)); + + *node = (pm_instance_variable_and_write_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param binary_operator The binary_operator field. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_operator_write_node_t * +pm_instance_variable_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t binary_operator) { + pm_instance_variable_operator_write_node_t *node = (pm_instance_variable_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_operator_write_node_t), PRISM_ALIGNOF(pm_instance_variable_operator_write_node_t)); + + *node = (pm_instance_variable_operator_write_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .binary_operator_loc = binary_operator_loc, + .value = value, + .binary_operator = binary_operator + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_or_write_node_t * +pm_instance_variable_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_instance_variable_or_write_node_t *node = (pm_instance_variable_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_or_write_node_t), PRISM_ALIGNOF(pm_instance_variable_or_write_node_t)); + + *node = (pm_instance_variable_or_write_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the instance variable, which is a \`@\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_read_node_t * +pm_instance_variable_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_instance_variable_read_node_t *node = (pm_instance_variable_read_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_read_node_t), PRISM_ALIGNOF(pm_instance_variable_read_node_t)); + + *node = (pm_instance_variable_read_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_target_node_t * +pm_instance_variable_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_instance_variable_target_node_t *node = (pm_instance_variable_target_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_target_node_t), PRISM_ALIGNOF(pm_instance_variable_target_node_t)); + + *node = (pm_instance_variable_target_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new InstanceVariableWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the instance variable, which is a \`@\` followed by an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @param name_loc The Location of the variable name\. + * @param value The value to write to the instance variable\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_instance_variable_write_node_t * +pm_instance_variable_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *value, pm_location_t operator_loc) { + pm_instance_variable_write_node_t *node = (pm_instance_variable_write_node_t *) pm_arena_alloc(arena, sizeof(pm_instance_variable_write_node_t), PRISM_ALIGNOF(pm_instance_variable_write_node_t)); + + *node = (pm_instance_variable_write_node_t) { + .base = { .type = PM_INSTANCE_VARIABLE_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new IntegerNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value The value of the integer literal as a number\. + * @return The newly allocated and initialized node. + */ +static inline pm_integer_node_t * +pm_integer_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_integer_t value) { + pm_integer_node_t *node = (pm_integer_node_t *) pm_arena_alloc(arena, sizeof(pm_integer_node_t), PRISM_ALIGNOF(pm_integer_node_t)); + + *node = (pm_integer_node_t) { + .base = { .type = PM_INTEGER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new InterpolatedMatchLastLineNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param parts The parts field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_interpolated_match_last_line_node_t * +pm_interpolated_match_last_line_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t parts, pm_location_t closing_loc) { + pm_interpolated_match_last_line_node_t *node = (pm_interpolated_match_last_line_node_t *) pm_arena_alloc(arena, sizeof(pm_interpolated_match_last_line_node_t), PRISM_ALIGNOF(pm_interpolated_match_last_line_node_t)); + + *node = (pm_interpolated_match_last_line_node_t) { + .base = { .type = PM_INTERPOLATED_MATCH_LAST_LINE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .parts = parts, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new InterpolatedRegularExpressionNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param parts The parts field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_interpolated_regular_expression_node_t * +pm_interpolated_regular_expression_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t parts, pm_location_t closing_loc) { + pm_interpolated_regular_expression_node_t *node = (pm_interpolated_regular_expression_node_t *) pm_arena_alloc(arena, sizeof(pm_interpolated_regular_expression_node_t), PRISM_ALIGNOF(pm_interpolated_regular_expression_node_t)); + + *node = (pm_interpolated_regular_expression_node_t) { + .base = { .type = PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .parts = parts, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new InterpolatedStringNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param parts The parts field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_interpolated_string_node_t * +pm_interpolated_string_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t parts, pm_location_t closing_loc) { + pm_interpolated_string_node_t *node = (pm_interpolated_string_node_t *) pm_arena_alloc(arena, sizeof(pm_interpolated_string_node_t), PRISM_ALIGNOF(pm_interpolated_string_node_t)); + + *node = (pm_interpolated_string_node_t) { + .base = { .type = PM_INTERPOLATED_STRING_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .parts = parts, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new InterpolatedSymbolNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param parts The parts field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_interpolated_symbol_node_t * +pm_interpolated_symbol_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t parts, pm_location_t closing_loc) { + pm_interpolated_symbol_node_t *node = (pm_interpolated_symbol_node_t *) pm_arena_alloc(arena, sizeof(pm_interpolated_symbol_node_t), PRISM_ALIGNOF(pm_interpolated_symbol_node_t)); + + *node = (pm_interpolated_symbol_node_t) { + .base = { .type = PM_INTERPOLATED_SYMBOL_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .parts = parts, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new InterpolatedXStringNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param parts The parts field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_interpolated_x_string_node_t * +pm_interpolated_x_string_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_node_list_t parts, pm_location_t closing_loc) { + pm_interpolated_x_string_node_t *node = (pm_interpolated_x_string_node_t *) pm_arena_alloc(arena, sizeof(pm_interpolated_x_string_node_t), PRISM_ALIGNOF(pm_interpolated_x_string_node_t)); + + *node = (pm_interpolated_x_string_node_t) { + .base = { .type = PM_INTERPOLATED_X_STRING_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .parts = parts, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ItLocalVariableReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_it_local_variable_read_node_t * +pm_it_local_variable_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_it_local_variable_read_node_t *node = (pm_it_local_variable_read_node_t *) pm_arena_alloc(arena, sizeof(pm_it_local_variable_read_node_t), PRISM_ALIGNOF(pm_it_local_variable_read_node_t)); + + *node = (pm_it_local_variable_read_node_t) { + .base = { .type = PM_IT_LOCAL_VARIABLE_READ_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ItParametersNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_it_parameters_node_t * +pm_it_parameters_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_it_parameters_node_t *node = (pm_it_parameters_node_t *) pm_arena_alloc(arena, sizeof(pm_it_parameters_node_t), PRISM_ALIGNOF(pm_it_parameters_node_t)); + + *node = (pm_it_parameters_node_t) { + .base = { .type = PM_IT_PARAMETERS_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new KeywordHashNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param elements The elements field. + * @return The newly allocated and initialized node. + */ +static inline pm_keyword_hash_node_t * +pm_keyword_hash_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t elements) { + pm_keyword_hash_node_t *node = (pm_keyword_hash_node_t *) pm_arena_alloc(arena, sizeof(pm_keyword_hash_node_t), PRISM_ALIGNOF(pm_keyword_hash_node_t)); + + *node = (pm_keyword_hash_node_t) { + .base = { .type = PM_KEYWORD_HASH_NODE, .flags = flags, .node_id = node_id, .location = location }, + .elements = elements + }; + + return node; +} + +/** + * Allocate and initialize a new KeywordRestParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_keyword_rest_parameter_node_t * +pm_keyword_rest_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc) { + pm_keyword_rest_parameter_node_t *node = (pm_keyword_rest_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_keyword_rest_parameter_node_t), PRISM_ALIGNOF(pm_keyword_rest_parameter_node_t)); + + *node = (pm_keyword_rest_parameter_node_t) { + .base = { .type = PM_KEYWORD_REST_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new LambdaNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The locals field. + * @param operator_loc The operator_loc field. + * @param opening_loc The opening_loc field. + * @param closing_loc The closing_loc field. + * @param parameters The parameters field. + * @param body The body field. + * @return The newly allocated and initialized node. + */ +static inline pm_lambda_node_t * +pm_lambda_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, pm_location_t operator_loc, pm_location_t opening_loc, pm_location_t closing_loc, struct pm_node *parameters, struct pm_node *body) { + pm_lambda_node_t *node = (pm_lambda_node_t *) pm_arena_alloc(arena, sizeof(pm_lambda_node_t), PRISM_ALIGNOF(pm_lambda_node_t)); + + *node = (pm_lambda_node_t) { + .base = { .type = PM_LAMBDA_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .operator_loc = operator_loc, + .opening_loc = opening_loc, + .closing_loc = closing_loc, + .parameters = parameters, + .body = body + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableAndWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @param name The name field. + * @param depth The depth field. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_and_write_node_t * +pm_local_variable_and_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value, pm_constant_id_t name, uint32_t depth) { + pm_local_variable_and_write_node_t *node = (pm_local_variable_and_write_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_and_write_node_t), PRISM_ALIGNOF(pm_local_variable_and_write_node_t)); + + *node = (pm_local_variable_and_write_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_AND_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value, + .name = name, + .depth = depth + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableOperatorWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name_loc The name_loc field. + * @param binary_operator_loc The binary_operator_loc field. + * @param value The value field. + * @param name The name field. + * @param binary_operator The binary_operator field. + * @param depth The depth field. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_operator_write_node_t * +pm_local_variable_operator_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t name_loc, pm_location_t binary_operator_loc, struct pm_node *value, pm_constant_id_t name, pm_constant_id_t binary_operator, uint32_t depth) { + pm_local_variable_operator_write_node_t *node = (pm_local_variable_operator_write_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_operator_write_node_t), PRISM_ALIGNOF(pm_local_variable_operator_write_node_t)); + + *node = (pm_local_variable_operator_write_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name_loc = name_loc, + .binary_operator_loc = binary_operator_loc, + .value = value, + .name = name, + .binary_operator = binary_operator, + .depth = depth + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableOrWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @param name The name field. + * @param depth The depth field. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_or_write_node_t * +pm_local_variable_or_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value, pm_constant_id_t name, uint32_t depth) { + pm_local_variable_or_write_node_t *node = (pm_local_variable_or_write_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_or_write_node_t), PRISM_ALIGNOF(pm_local_variable_or_write_node_t)); + + *node = (pm_local_variable_or_write_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_OR_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value, + .name = name, + .depth = depth + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the local variable, which is an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @param depth The number of visible scopes that should be searched to find the origin of this local variable\. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_read_node_t * +pm_local_variable_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, uint32_t depth) { + pm_local_variable_read_node_t *node = (pm_local_variable_read_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_read_node_t), PRISM_ALIGNOF(pm_local_variable_read_node_t)); + + *node = (pm_local_variable_read_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .depth = depth + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param depth The depth field. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_target_node_t * +pm_local_variable_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, uint32_t depth) { + pm_local_variable_target_node_t *node = (pm_local_variable_target_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_target_node_t), PRISM_ALIGNOF(pm_local_variable_target_node_t)); + + *node = (pm_local_variable_target_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .depth = depth + }; + + return node; +} + +/** + * Allocate and initialize a new LocalVariableWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name of the local variable, which is an [identifier](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#identifiers)\. + * @param depth The number of semantic scopes we have to traverse to find the declaration of this variable\. + * @param name_loc The Location of the variable name\. + * @param value The value to write to the local variable\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`=\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_local_variable_write_node_t * +pm_local_variable_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, uint32_t depth, pm_location_t name_loc, struct pm_node *value, pm_location_t operator_loc) { + pm_local_variable_write_node_t *node = (pm_local_variable_write_node_t *) pm_arena_alloc(arena, sizeof(pm_local_variable_write_node_t), PRISM_ALIGNOF(pm_local_variable_write_node_t)); + + *node = (pm_local_variable_write_node_t) { + .base = { .type = PM_LOCAL_VARIABLE_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .depth = depth, + .name_loc = name_loc, + .value = value, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new MatchLastLineNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param content_loc The content_loc field. + * @param closing_loc The closing_loc field. + * @param unescaped The unescaped field. + * @return The newly allocated and initialized node. + */ +static inline pm_match_last_line_node_t * +pm_match_last_line_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_location_t content_loc, pm_location_t closing_loc, pm_string_t unescaped) { + pm_match_last_line_node_t *node = (pm_match_last_line_node_t *) pm_arena_alloc(arena, sizeof(pm_match_last_line_node_t), PRISM_ALIGNOF(pm_match_last_line_node_t)); + + *node = (pm_match_last_line_node_t) { + .base = { .type = PM_MATCH_LAST_LINE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .content_loc = content_loc, + .closing_loc = closing_loc, + .unescaped = unescaped + }; + + return node; +} + +/** + * Allocate and initialize a new MatchPredicateNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value The value field. + * @param pattern The pattern field. + * @param operator_loc The operator_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_match_predicate_node_t * +pm_match_predicate_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *value, struct pm_node *pattern, pm_location_t operator_loc) { + pm_match_predicate_node_t *node = (pm_match_predicate_node_t *) pm_arena_alloc(arena, sizeof(pm_match_predicate_node_t), PRISM_ALIGNOF(pm_match_predicate_node_t)); + + *node = (pm_match_predicate_node_t) { + .base = { .type = PM_MATCH_PREDICATE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value, + .pattern = pattern, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new MatchRequiredNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param value Represents the left\-hand side of the operator\. + * @param pattern Represents the right\-hand side of the operator\. The type of the node depends on the expression\. + * @param operator_loc The Location of the operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_match_required_node_t * +pm_match_required_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *value, struct pm_node *pattern, pm_location_t operator_loc) { + pm_match_required_node_t *node = (pm_match_required_node_t *) pm_arena_alloc(arena, sizeof(pm_match_required_node_t), PRISM_ALIGNOF(pm_match_required_node_t)); + + *node = (pm_match_required_node_t) { + .base = { .type = PM_MATCH_REQUIRED_NODE, .flags = flags, .node_id = node_id, .location = location }, + .value = value, + .pattern = pattern, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new MatchWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param call The call field. + * @param targets The targets field. + * @return The newly allocated and initialized node. + */ +static inline pm_match_write_node_t * +pm_match_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_call_node *call, pm_node_list_t targets) { + pm_match_write_node_t *node = (pm_match_write_node_t *) pm_arena_alloc(arena, sizeof(pm_match_write_node_t), PRISM_ALIGNOF(pm_match_write_node_t)); + + *node = (pm_match_write_node_t) { + .base = { .type = PM_MATCH_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .call = call, + .targets = targets + }; + + return node; +} + +/** + * Allocate and initialize a new MissingNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_missing_node_t * +pm_missing_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_missing_node_t *node = (pm_missing_node_t *) pm_arena_alloc(arena, sizeof(pm_missing_node_t), PRISM_ALIGNOF(pm_missing_node_t)); + + *node = (pm_missing_node_t) { + .base = { .type = PM_MISSING_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ModuleNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The locals field. + * @param module_keyword_loc The module_keyword_loc field. + * @param constant_path The constant_path field. + * @param body The body field. + * @param end_keyword_loc The end_keyword_loc field. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_module_node_t * +pm_module_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, pm_location_t module_keyword_loc, struct pm_node *constant_path, struct pm_node *body, pm_location_t end_keyword_loc, pm_constant_id_t name) { + pm_module_node_t *node = (pm_module_node_t *) pm_arena_alloc(arena, sizeof(pm_module_node_t), PRISM_ALIGNOF(pm_module_node_t)); + + *node = (pm_module_node_t) { + .base = { .type = PM_MODULE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .module_keyword_loc = module_keyword_loc, + .constant_path = constant_path, + .body = body, + .end_keyword_loc = end_keyword_loc, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new MultiTargetNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param lefts Represents the targets expressions before a splat node\. + * @param rest Represents a splat node in the target expression\. + * @param rights Represents the targets expressions after a splat node\. + * @param lparen_loc The Location of the opening parenthesis\. + * @param rparen_loc The Location of the closing parenthesis\. + * @return The newly allocated and initialized node. + */ +static inline pm_multi_target_node_t * +pm_multi_target_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t lefts, struct pm_node *rest, pm_node_list_t rights, pm_location_t lparen_loc, pm_location_t rparen_loc) { + pm_multi_target_node_t *node = (pm_multi_target_node_t *) pm_arena_alloc(arena, sizeof(pm_multi_target_node_t), PRISM_ALIGNOF(pm_multi_target_node_t)); + + *node = (pm_multi_target_node_t) { + .base = { .type = PM_MULTI_TARGET_NODE, .flags = flags, .node_id = node_id, .location = location }, + .lefts = lefts, + .rest = rest, + .rights = rights, + .lparen_loc = lparen_loc, + .rparen_loc = rparen_loc + }; + + return node; +} + +/** + * Allocate and initialize a new MultiWriteNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param lefts Represents the targets expressions before a splat node\. + * @param rest Represents a splat node in the target expression\. + * @param rights Represents the targets expressions after a splat node\. + * @param lparen_loc The Location of the opening parenthesis\. + * @param rparen_loc The Location of the closing parenthesis\. + * @param operator_loc The Location of the operator\. + * @param value The value to write to the targets\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @return The newly allocated and initialized node. + */ +static inline pm_multi_write_node_t * +pm_multi_write_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t lefts, struct pm_node *rest, pm_node_list_t rights, pm_location_t lparen_loc, pm_location_t rparen_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_multi_write_node_t *node = (pm_multi_write_node_t *) pm_arena_alloc(arena, sizeof(pm_multi_write_node_t), PRISM_ALIGNOF(pm_multi_write_node_t)); + + *node = (pm_multi_write_node_t) { + .base = { .type = PM_MULTI_WRITE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .lefts = lefts, + .rest = rest, + .rights = rights, + .lparen_loc = lparen_loc, + .rparen_loc = rparen_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new NextNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param arguments The arguments field. + * @param keyword_loc The keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_next_node_t * +pm_next_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_arguments_node *arguments, pm_location_t keyword_loc) { + pm_next_node_t *node = (pm_next_node_t *) pm_arena_alloc(arena, sizeof(pm_next_node_t), PRISM_ALIGNOF(pm_next_node_t)); + + *node = (pm_next_node_t) { + .base = { .type = PM_NEXT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .arguments = arguments, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new NilNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_nil_node_t * +pm_nil_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_nil_node_t *node = (pm_nil_node_t *) pm_arena_alloc(arena, sizeof(pm_nil_node_t), PRISM_ALIGNOF(pm_nil_node_t)); + + *node = (pm_nil_node_t) { + .base = { .type = PM_NIL_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new NoBlockParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param operator_loc The operator_loc field. + * @param keyword_loc The keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_no_block_parameter_node_t * +pm_no_block_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t operator_loc, pm_location_t keyword_loc) { + pm_no_block_parameter_node_t *node = (pm_no_block_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_no_block_parameter_node_t), PRISM_ALIGNOF(pm_no_block_parameter_node_t)); + + *node = (pm_no_block_parameter_node_t) { + .base = { .type = PM_NO_BLOCK_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .operator_loc = operator_loc, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new NoKeywordsParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param operator_loc The operator_loc field. + * @param keyword_loc The keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_no_keywords_parameter_node_t * +pm_no_keywords_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t operator_loc, pm_location_t keyword_loc) { + pm_no_keywords_parameter_node_t *node = (pm_no_keywords_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_no_keywords_parameter_node_t), PRISM_ALIGNOF(pm_no_keywords_parameter_node_t)); + + *node = (pm_no_keywords_parameter_node_t) { + .base = { .type = PM_NO_KEYWORDS_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .operator_loc = operator_loc, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new NumberedParametersNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param maximum The maximum field. + * @return The newly allocated and initialized node. + */ +static inline pm_numbered_parameters_node_t * +pm_numbered_parameters_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, uint8_t maximum) { + pm_numbered_parameters_node_t *node = (pm_numbered_parameters_node_t *) pm_arena_alloc(arena, sizeof(pm_numbered_parameters_node_t), PRISM_ALIGNOF(pm_numbered_parameters_node_t)); + + *node = (pm_numbered_parameters_node_t) { + .base = { .type = PM_NUMBERED_PARAMETERS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .maximum = maximum + }; + + return node; +} + +/** + * Allocate and initialize a new NumberedReferenceReadNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param number The (1\-indexed, from the left) number of the capture group\. Numbered references that are too large result in this value being \`0\`\. + * @return The newly allocated and initialized node. + */ +static inline pm_numbered_reference_read_node_t * +pm_numbered_reference_read_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, uint32_t number) { + pm_numbered_reference_read_node_t *node = (pm_numbered_reference_read_node_t *) pm_arena_alloc(arena, sizeof(pm_numbered_reference_read_node_t), PRISM_ALIGNOF(pm_numbered_reference_read_node_t)); + + *node = (pm_numbered_reference_read_node_t) { + .base = { .type = PM_NUMBERED_REFERENCE_READ_NODE, .flags = flags, .node_id = node_id, .location = location }, + .number = number + }; + + return node; +} + +/** + * Allocate and initialize a new OptionalKeywordParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_optional_keyword_parameter_node_t * +pm_optional_keyword_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, struct pm_node *value) { + pm_optional_keyword_parameter_node_t *node = (pm_optional_keyword_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_optional_keyword_parameter_node_t), PRISM_ALIGNOF(pm_optional_keyword_parameter_node_t)); + + *node = (pm_optional_keyword_parameter_node_t) { + .base = { .type = PM_OPTIONAL_KEYWORD_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new OptionalParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @param value The value field. + * @return The newly allocated and initialized node. + */ +static inline pm_optional_parameter_node_t * +pm_optional_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc, struct pm_node *value) { + pm_optional_parameter_node_t *node = (pm_optional_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_optional_parameter_node_t), PRISM_ALIGNOF(pm_optional_parameter_node_t)); + + *node = (pm_optional_parameter_node_t) { + .base = { .type = PM_OPTIONAL_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc, + .value = value + }; + + return node; +} + +/** + * Allocate and initialize a new OrNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param left Represents the left side of the expression\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param right Represents the right side of the expression\. + * @param operator_loc The Location of the \`or\` keyword or the \`||\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_or_node_t * +pm_or_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *left, struct pm_node *right, pm_location_t operator_loc) { + pm_or_node_t *node = (pm_or_node_t *) pm_arena_alloc(arena, sizeof(pm_or_node_t), PRISM_ALIGNOF(pm_or_node_t)); + + *node = (pm_or_node_t) { + .base = { .type = PM_OR_NODE, .flags = flags, .node_id = node_id, .location = location }, + .left = left, + .right = right, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ParametersNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param requireds The requireds field. + * @param optionals The optionals field. + * @param rest The rest field. + * @param posts The posts field. + * @param keywords The keywords field. + * @param keyword_rest The keyword_rest field. + * @param block The block field. + * @return The newly allocated and initialized node. + */ +static inline pm_parameters_node_t * +pm_parameters_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t requireds, pm_node_list_t optionals, struct pm_node *rest, pm_node_list_t posts, pm_node_list_t keywords, struct pm_node *keyword_rest, struct pm_node *block) { + pm_parameters_node_t *node = (pm_parameters_node_t *) pm_arena_alloc(arena, sizeof(pm_parameters_node_t), PRISM_ALIGNOF(pm_parameters_node_t)); + + *node = (pm_parameters_node_t) { + .base = { .type = PM_PARAMETERS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .requireds = requireds, + .optionals = optionals, + .rest = rest, + .posts = posts, + .keywords = keywords, + .keyword_rest = keyword_rest, + .block = block + }; + + return node; +} + +/** + * Allocate and initialize a new ParenthesesNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param body The body field. + * @param opening_loc The opening_loc field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_parentheses_node_t * +pm_parentheses_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *body, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_parentheses_node_t *node = (pm_parentheses_node_t *) pm_arena_alloc(arena, sizeof(pm_parentheses_node_t), PRISM_ALIGNOF(pm_parentheses_node_t)); + + *node = (pm_parentheses_node_t) { + .base = { .type = PM_PARENTHESES_NODE, .flags = flags, .node_id = node_id, .location = location }, + .body = body, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new PinnedExpressionNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param expression The expression used in the pinned expression + * @param operator_loc The Location of the \`^\` operator + * @param lparen_loc The Location of the opening parenthesis\. + * @param rparen_loc The Location of the closing parenthesis\. + * @return The newly allocated and initialized node. + */ +static inline pm_pinned_expression_node_t * +pm_pinned_expression_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *expression, pm_location_t operator_loc, pm_location_t lparen_loc, pm_location_t rparen_loc) { + pm_pinned_expression_node_t *node = (pm_pinned_expression_node_t *) pm_arena_alloc(arena, sizeof(pm_pinned_expression_node_t), PRISM_ALIGNOF(pm_pinned_expression_node_t)); + + *node = (pm_pinned_expression_node_t) { + .base = { .type = PM_PINNED_EXPRESSION_NODE, .flags = flags, .node_id = node_id, .location = location }, + .expression = expression, + .operator_loc = operator_loc, + .lparen_loc = lparen_loc, + .rparen_loc = rparen_loc + }; + + return node; +} + +/** + * Allocate and initialize a new PinnedVariableNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param variable The variable used in the pinned expression + * @param operator_loc The Location of the \`^\` operator + * @return The newly allocated and initialized node. + */ +static inline pm_pinned_variable_node_t * +pm_pinned_variable_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *variable, pm_location_t operator_loc) { + pm_pinned_variable_node_t *node = (pm_pinned_variable_node_t *) pm_arena_alloc(arena, sizeof(pm_pinned_variable_node_t), PRISM_ALIGNOF(pm_pinned_variable_node_t)); + + *node = (pm_pinned_variable_node_t) { + .base = { .type = PM_PINNED_VARIABLE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .variable = variable, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new PostExecutionNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param statements The statements field. + * @param keyword_loc The keyword_loc field. + * @param opening_loc The opening_loc field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_post_execution_node_t * +pm_post_execution_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_statements_node *statements, pm_location_t keyword_loc, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_post_execution_node_t *node = (pm_post_execution_node_t *) pm_arena_alloc(arena, sizeof(pm_post_execution_node_t), PRISM_ALIGNOF(pm_post_execution_node_t)); + + *node = (pm_post_execution_node_t) { + .base = { .type = PM_POST_EXECUTION_NODE, .flags = flags, .node_id = node_id, .location = location }, + .statements = statements, + .keyword_loc = keyword_loc, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new PreExecutionNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param statements The statements field. + * @param keyword_loc The keyword_loc field. + * @param opening_loc The opening_loc field. + * @param closing_loc The closing_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_pre_execution_node_t * +pm_pre_execution_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_statements_node *statements, pm_location_t keyword_loc, pm_location_t opening_loc, pm_location_t closing_loc) { + pm_pre_execution_node_t *node = (pm_pre_execution_node_t *) pm_arena_alloc(arena, sizeof(pm_pre_execution_node_t), PRISM_ALIGNOF(pm_pre_execution_node_t)); + + *node = (pm_pre_execution_node_t) { + .base = { .type = PM_PRE_EXECUTION_NODE, .flags = flags, .node_id = node_id, .location = location }, + .statements = statements, + .keyword_loc = keyword_loc, + .opening_loc = opening_loc, + .closing_loc = closing_loc + }; + + return node; +} + +/** + * Allocate and initialize a new ProgramNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The locals field. + * @param statements The statements field. + * @return The newly allocated and initialized node. + */ +static inline pm_program_node_t * +pm_program_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, struct pm_statements_node *statements) { + pm_program_node_t *node = (pm_program_node_t *) pm_arena_alloc(arena, sizeof(pm_program_node_t), PRISM_ALIGNOF(pm_program_node_t)); + + *node = (pm_program_node_t) { + .base = { .type = PM_PROGRAM_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .statements = statements + }; + + return node; +} + +/** + * Allocate and initialize a new RangeNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param left The left\-hand side of the range, if present\. It can be either \`nil\` or any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param right The right\-hand side of the range, if present\. It can be either \`nil\` or any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param operator_loc The Location of the \`\.\.\` or \`\.\.\.\` operator\. + * @return The newly allocated and initialized node. + */ +static inline pm_range_node_t * +pm_range_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *left, struct pm_node *right, pm_location_t operator_loc) { + pm_range_node_t *node = (pm_range_node_t *) pm_arena_alloc(arena, sizeof(pm_range_node_t), PRISM_ALIGNOF(pm_range_node_t)); + + *node = (pm_range_node_t) { + .base = { .type = PM_RANGE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .left = left, + .right = right, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new RationalNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param numerator The numerator of the rational number\. + * @param denominator The denominator of the rational number\. + * @return The newly allocated and initialized node. + */ +static inline pm_rational_node_t * +pm_rational_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_integer_t numerator, pm_integer_t denominator) { + pm_rational_node_t *node = (pm_rational_node_t *) pm_arena_alloc(arena, sizeof(pm_rational_node_t), PRISM_ALIGNOF(pm_rational_node_t)); + + *node = (pm_rational_node_t) { + .base = { .type = PM_RATIONAL_NODE, .flags = flags, .node_id = node_id, .location = location }, + .numerator = numerator, + .denominator = denominator + }; + + return node; +} + +/** + * Allocate and initialize a new RedoNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_redo_node_t * +pm_redo_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_redo_node_t *node = (pm_redo_node_t *) pm_arena_alloc(arena, sizeof(pm_redo_node_t), PRISM_ALIGNOF(pm_redo_node_t)); + + *node = (pm_redo_node_t) { + .base = { .type = PM_REDO_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new RegularExpressionNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param content_loc The content_loc field. + * @param closing_loc The closing_loc field. + * @param unescaped The unescaped field. + * @return The newly allocated and initialized node. + */ +static inline pm_regular_expression_node_t * +pm_regular_expression_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_location_t content_loc, pm_location_t closing_loc, pm_string_t unescaped) { + pm_regular_expression_node_t *node = (pm_regular_expression_node_t *) pm_arena_alloc(arena, sizeof(pm_regular_expression_node_t), PRISM_ALIGNOF(pm_regular_expression_node_t)); + + *node = (pm_regular_expression_node_t) { + .base = { .type = PM_REGULAR_EXPRESSION_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .content_loc = content_loc, + .closing_loc = closing_loc, + .unescaped = unescaped + }; + + return node; +} + +/** + * Allocate and initialize a new RequiredKeywordParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_required_keyword_parameter_node_t * +pm_required_keyword_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc) { + pm_required_keyword_parameter_node_t *node = (pm_required_keyword_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_required_keyword_parameter_node_t), PRISM_ALIGNOF(pm_required_keyword_parameter_node_t)); + + *node = (pm_required_keyword_parameter_node_t) { + .base = { .type = PM_REQUIRED_KEYWORD_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc + }; + + return node; +} + +/** + * Allocate and initialize a new RequiredParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @return The newly allocated and initialized node. + */ +static inline pm_required_parameter_node_t * +pm_required_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name) { + pm_required_parameter_node_t *node = (pm_required_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_required_parameter_node_t), PRISM_ALIGNOF(pm_required_parameter_node_t)); + + *node = (pm_required_parameter_node_t) { + .base = { .type = PM_REQUIRED_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name + }; + + return node; +} + +/** + * Allocate and initialize a new RescueModifierNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param expression The expression field. + * @param keyword_loc The keyword_loc field. + * @param rescue_expression The rescue_expression field. + * @return The newly allocated and initialized node. + */ +static inline pm_rescue_modifier_node_t * +pm_rescue_modifier_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *expression, pm_location_t keyword_loc, struct pm_node *rescue_expression) { + pm_rescue_modifier_node_t *node = (pm_rescue_modifier_node_t *) pm_arena_alloc(arena, sizeof(pm_rescue_modifier_node_t), PRISM_ALIGNOF(pm_rescue_modifier_node_t)); + + *node = (pm_rescue_modifier_node_t) { + .base = { .type = PM_RESCUE_MODIFIER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .expression = expression, + .keyword_loc = keyword_loc, + .rescue_expression = rescue_expression + }; + + return node; +} + +/** + * Allocate and initialize a new RescueNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param exceptions The exceptions field. + * @param operator_loc The operator_loc field. + * @param reference The reference field. + * @param then_keyword_loc The then_keyword_loc field. + * @param statements The statements field. + * @param subsequent The subsequent field. + * @return The newly allocated and initialized node. + */ +static inline pm_rescue_node_t * +pm_rescue_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_node_list_t exceptions, pm_location_t operator_loc, struct pm_node *reference, pm_location_t then_keyword_loc, struct pm_statements_node *statements, struct pm_rescue_node *subsequent) { + pm_rescue_node_t *node = (pm_rescue_node_t *) pm_arena_alloc(arena, sizeof(pm_rescue_node_t), PRISM_ALIGNOF(pm_rescue_node_t)); + + *node = (pm_rescue_node_t) { + .base = { .type = PM_RESCUE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .exceptions = exceptions, + .operator_loc = operator_loc, + .reference = reference, + .then_keyword_loc = then_keyword_loc, + .statements = statements, + .subsequent = subsequent + }; + + return node; +} + +/** + * Allocate and initialize a new RestParameterNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param name The name field. + * @param name_loc The name_loc field. + * @param operator_loc The operator_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_rest_parameter_node_t * +pm_rest_parameter_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_t name, pm_location_t name_loc, pm_location_t operator_loc) { + pm_rest_parameter_node_t *node = (pm_rest_parameter_node_t *) pm_arena_alloc(arena, sizeof(pm_rest_parameter_node_t), PRISM_ALIGNOF(pm_rest_parameter_node_t)); + + *node = (pm_rest_parameter_node_t) { + .base = { .type = PM_REST_PARAMETER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .name = name, + .name_loc = name_loc, + .operator_loc = operator_loc + }; + + return node; +} + +/** + * Allocate and initialize a new RetryNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_retry_node_t * +pm_retry_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_retry_node_t *node = (pm_retry_node_t *) pm_arena_alloc(arena, sizeof(pm_retry_node_t), PRISM_ALIGNOF(pm_retry_node_t)); + + *node = (pm_retry_node_t) { + .base = { .type = PM_RETRY_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ReturnNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param arguments The arguments field. + * @return The newly allocated and initialized node. + */ +static inline pm_return_node_t * +pm_return_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, struct pm_arguments_node *arguments) { + pm_return_node_t *node = (pm_return_node_t *) pm_arena_alloc(arena, sizeof(pm_return_node_t), PRISM_ALIGNOF(pm_return_node_t)); + + *node = (pm_return_node_t) { + .base = { .type = PM_RETURN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .arguments = arguments + }; + + return node; +} + +/** + * Allocate and initialize a new SelfNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_self_node_t * +pm_self_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_self_node_t *node = (pm_self_node_t *) pm_arena_alloc(arena, sizeof(pm_self_node_t), PRISM_ALIGNOF(pm_self_node_t)); + + *node = (pm_self_node_t) { + .base = { .type = PM_SELF_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new ShareableConstantNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param write The constant write that should be modified with the shareability state\. + * @return The newly allocated and initialized node. + */ +static inline pm_shareable_constant_node_t * +pm_shareable_constant_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, struct pm_node *write) { + pm_shareable_constant_node_t *node = (pm_shareable_constant_node_t *) pm_arena_alloc(arena, sizeof(pm_shareable_constant_node_t), PRISM_ALIGNOF(pm_shareable_constant_node_t)); + + *node = (pm_shareable_constant_node_t) { + .base = { .type = PM_SHAREABLE_CONSTANT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .write = write + }; + + return node; +} + +/** + * Allocate and initialize a new SingletonClassNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param locals The locals field. + * @param class_keyword_loc The class_keyword_loc field. + * @param operator_loc The operator_loc field. + * @param expression The expression field. + * @param body The body field. + * @param end_keyword_loc The end_keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_singleton_class_node_t * +pm_singleton_class_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_constant_id_list_t locals, pm_location_t class_keyword_loc, pm_location_t operator_loc, struct pm_node *expression, struct pm_node *body, pm_location_t end_keyword_loc) { + pm_singleton_class_node_t *node = (pm_singleton_class_node_t *) pm_arena_alloc(arena, sizeof(pm_singleton_class_node_t), PRISM_ALIGNOF(pm_singleton_class_node_t)); + + *node = (pm_singleton_class_node_t) { + .base = { .type = PM_SINGLETON_CLASS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .locals = locals, + .class_keyword_loc = class_keyword_loc, + .operator_loc = operator_loc, + .expression = expression, + .body = body, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new SourceEncodingNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_source_encoding_node_t * +pm_source_encoding_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_source_encoding_node_t *node = (pm_source_encoding_node_t *) pm_arena_alloc(arena, sizeof(pm_source_encoding_node_t), PRISM_ALIGNOF(pm_source_encoding_node_t)); + + *node = (pm_source_encoding_node_t) { + .base = { .type = PM_SOURCE_ENCODING_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new SourceFileNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param filepath Represents the file path being parsed\. This corresponds directly to the \`filepath\` option given to the various \`Prism\.parse\*\` APIs\. + * @return The newly allocated and initialized node. + */ +static inline pm_source_file_node_t * +pm_source_file_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_string_t filepath) { + pm_source_file_node_t *node = (pm_source_file_node_t *) pm_arena_alloc(arena, sizeof(pm_source_file_node_t), PRISM_ALIGNOF(pm_source_file_node_t)); + + *node = (pm_source_file_node_t) { + .base = { .type = PM_SOURCE_FILE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .filepath = filepath + }; + + return node; +} + +/** + * Allocate and initialize a new SourceLineNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_source_line_node_t * +pm_source_line_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_source_line_node_t *node = (pm_source_line_node_t *) pm_arena_alloc(arena, sizeof(pm_source_line_node_t), PRISM_ALIGNOF(pm_source_line_node_t)); + + *node = (pm_source_line_node_t) { + .base = { .type = PM_SOURCE_LINE_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new SplatNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param operator_loc The operator_loc field. + * @param expression The expression field. + * @return The newly allocated and initialized node. + */ +static inline pm_splat_node_t * +pm_splat_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t operator_loc, struct pm_node *expression) { + pm_splat_node_t *node = (pm_splat_node_t *) pm_arena_alloc(arena, sizeof(pm_splat_node_t), PRISM_ALIGNOF(pm_splat_node_t)); + + *node = (pm_splat_node_t) { + .base = { .type = PM_SPLAT_NODE, .flags = flags, .node_id = node_id, .location = location }, + .operator_loc = operator_loc, + .expression = expression + }; + + return node; +} + +/** + * Allocate and initialize a new StatementsNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param body The body field. + * @return The newly allocated and initialized node. + */ +static inline pm_statements_node_t * +pm_statements_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t body) { + pm_statements_node_t *node = (pm_statements_node_t *) pm_arena_alloc(arena, sizeof(pm_statements_node_t), PRISM_ALIGNOF(pm_statements_node_t)); + + *node = (pm_statements_node_t) { + .base = { .type = PM_STATEMENTS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .body = body + }; + + return node; +} + +/** + * Allocate and initialize a new StringNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param content_loc The content_loc field. + * @param closing_loc The closing_loc field. + * @param unescaped The unescaped field. + * @return The newly allocated and initialized node. + */ +static inline pm_string_node_t * +pm_string_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_location_t content_loc, pm_location_t closing_loc, pm_string_t unescaped) { + pm_string_node_t *node = (pm_string_node_t *) pm_arena_alloc(arena, sizeof(pm_string_node_t), PRISM_ALIGNOF(pm_string_node_t)); + + *node = (pm_string_node_t) { + .base = { .type = PM_STRING_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .content_loc = content_loc, + .closing_loc = closing_loc, + .unescaped = unescaped + }; + + return node; +} + +/** + * Allocate and initialize a new SuperNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param lparen_loc The lparen_loc field. + * @param arguments Can be only \`nil\` when there are empty parentheses, like \`super()\`\. + * @param rparen_loc The rparen_loc field. + * @param block The block field. + * @return The newly allocated and initialized node. + */ +static inline pm_super_node_t * +pm_super_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_location_t lparen_loc, struct pm_arguments_node *arguments, pm_location_t rparen_loc, struct pm_node *block) { + pm_super_node_t *node = (pm_super_node_t *) pm_arena_alloc(arena, sizeof(pm_super_node_t), PRISM_ALIGNOF(pm_super_node_t)); + + *node = (pm_super_node_t) { + .base = { .type = PM_SUPER_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .lparen_loc = lparen_loc, + .arguments = arguments, + .rparen_loc = rparen_loc, + .block = block + }; + + return node; +} + +/** + * Allocate and initialize a new SymbolNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param value_loc The value_loc field. + * @param closing_loc The closing_loc field. + * @param unescaped The unescaped field. + * @return The newly allocated and initialized node. + */ +static inline pm_symbol_node_t * +pm_symbol_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_location_t value_loc, pm_location_t closing_loc, pm_string_t unescaped) { + pm_symbol_node_t *node = (pm_symbol_node_t *) pm_arena_alloc(arena, sizeof(pm_symbol_node_t), PRISM_ALIGNOF(pm_symbol_node_t)); + + *node = (pm_symbol_node_t) { + .base = { .type = PM_SYMBOL_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .value_loc = value_loc, + .closing_loc = closing_loc, + .unescaped = unescaped + }; + + return node; +} + +/** + * Allocate and initialize a new TrueNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @return The newly allocated and initialized node. + */ +static inline pm_true_node_t * +pm_true_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location) { + pm_true_node_t *node = (pm_true_node_t *) pm_arena_alloc(arena, sizeof(pm_true_node_t), PRISM_ALIGNOF(pm_true_node_t)); + + *node = (pm_true_node_t) { + .base = { .type = PM_TRUE_NODE, .flags = flags, .node_id = node_id, .location = location } + }; + + return node; +} + +/** + * Allocate and initialize a new UndefNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param names The names field. + * @param keyword_loc The keyword_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_undef_node_t * +pm_undef_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_node_list_t names, pm_location_t keyword_loc) { + pm_undef_node_t *node = (pm_undef_node_t *) pm_arena_alloc(arena, sizeof(pm_undef_node_t), PRISM_ALIGNOF(pm_undef_node_t)); + + *node = (pm_undef_node_t) { + .base = { .type = PM_UNDEF_NODE, .flags = flags, .node_id = node_id, .location = location }, + .names = names, + .keyword_loc = keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new UnlessNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The Location of the \`unless\` keyword\. + * @param predicate The condition to be evaluated for the unless expression\. It can be any [non\-void expression](https://github\.com/ruby/prism/blob/main/docs/parsing\_rules\.md\#non\-void\-expression)\. + * @param then_keyword_loc The Location of the \`then\` keyword, if present\. + * @param statements The body of statements that will executed if the unless condition is + * @param else_clause The else clause of the unless expression, if present\. + * @param end_keyword_loc The Location of the \`end\` keyword, if present\. + * @return The newly allocated and initialized node. + */ +static inline pm_unless_node_t * +pm_unless_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, struct pm_node *predicate, pm_location_t then_keyword_loc, struct pm_statements_node *statements, struct pm_else_node *else_clause, pm_location_t end_keyword_loc) { + pm_unless_node_t *node = (pm_unless_node_t *) pm_arena_alloc(arena, sizeof(pm_unless_node_t), PRISM_ALIGNOF(pm_unless_node_t)); + + *node = (pm_unless_node_t) { + .base = { .type = PM_UNLESS_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .predicate = predicate, + .then_keyword_loc = then_keyword_loc, + .statements = statements, + .else_clause = else_clause, + .end_keyword_loc = end_keyword_loc + }; + + return node; +} + +/** + * Allocate and initialize a new UntilNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param do_keyword_loc The do_keyword_loc field. + * @param closing_loc The closing_loc field. + * @param predicate The predicate field. + * @param statements The statements field. + * @return The newly allocated and initialized node. + */ +static inline pm_until_node_t * +pm_until_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_location_t do_keyword_loc, pm_location_t closing_loc, struct pm_node *predicate, struct pm_statements_node *statements) { + pm_until_node_t *node = (pm_until_node_t *) pm_arena_alloc(arena, sizeof(pm_until_node_t), PRISM_ALIGNOF(pm_until_node_t)); + + *node = (pm_until_node_t) { + .base = { .type = PM_UNTIL_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .do_keyword_loc = do_keyword_loc, + .closing_loc = closing_loc, + .predicate = predicate, + .statements = statements + }; + + return node; +} + +/** + * Allocate and initialize a new WhenNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param conditions The conditions field. + * @param then_keyword_loc The then_keyword_loc field. + * @param statements The statements field. + * @return The newly allocated and initialized node. + */ +static inline pm_when_node_t * +pm_when_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_node_list_t conditions, pm_location_t then_keyword_loc, struct pm_statements_node *statements) { + pm_when_node_t *node = (pm_when_node_t *) pm_arena_alloc(arena, sizeof(pm_when_node_t), PRISM_ALIGNOF(pm_when_node_t)); + + *node = (pm_when_node_t) { + .base = { .type = PM_WHEN_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .conditions = conditions, + .then_keyword_loc = then_keyword_loc, + .statements = statements + }; + + return node; +} + +/** + * Allocate and initialize a new WhileNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param do_keyword_loc The do_keyword_loc field. + * @param closing_loc The closing_loc field. + * @param predicate The predicate field. + * @param statements The statements field. + * @return The newly allocated and initialized node. + */ +static inline pm_while_node_t * +pm_while_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_location_t do_keyword_loc, pm_location_t closing_loc, struct pm_node *predicate, struct pm_statements_node *statements) { + pm_while_node_t *node = (pm_while_node_t *) pm_arena_alloc(arena, sizeof(pm_while_node_t), PRISM_ALIGNOF(pm_while_node_t)); + + *node = (pm_while_node_t) { + .base = { .type = PM_WHILE_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .do_keyword_loc = do_keyword_loc, + .closing_loc = closing_loc, + .predicate = predicate, + .statements = statements + }; + + return node; +} + +/** + * Allocate and initialize a new XStringNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param opening_loc The opening_loc field. + * @param content_loc The content_loc field. + * @param closing_loc The closing_loc field. + * @param unescaped The unescaped field. + * @return The newly allocated and initialized node. + */ +static inline pm_x_string_node_t * +pm_x_string_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t opening_loc, pm_location_t content_loc, pm_location_t closing_loc, pm_string_t unescaped) { + pm_x_string_node_t *node = (pm_x_string_node_t *) pm_arena_alloc(arena, sizeof(pm_x_string_node_t), PRISM_ALIGNOF(pm_x_string_node_t)); + + *node = (pm_x_string_node_t) { + .base = { .type = PM_X_STRING_NODE, .flags = flags, .node_id = node_id, .location = location }, + .opening_loc = opening_loc, + .content_loc = content_loc, + .closing_loc = closing_loc, + .unescaped = unescaped + }; + + return node; +} + +/** + * Allocate and initialize a new YieldNode node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. + * @param keyword_loc The keyword_loc field. + * @param lparen_loc The lparen_loc field. + * @param arguments The arguments field. + * @param rparen_loc The rparen_loc field. + * @return The newly allocated and initialized node. + */ +static inline pm_yield_node_t * +pm_yield_node_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location, pm_location_t keyword_loc, pm_location_t lparen_loc, struct pm_arguments_node *arguments, pm_location_t rparen_loc) { + pm_yield_node_t *node = (pm_yield_node_t *) pm_arena_alloc(arena, sizeof(pm_yield_node_t), PRISM_ALIGNOF(pm_yield_node_t)); + + *node = (pm_yield_node_t) { + .base = { .type = PM_YIELD_NODE, .flags = flags, .node_id = node_id, .location = location }, + .keyword_loc = keyword_loc, + .lparen_loc = lparen_loc, + .arguments = arguments, + .rparen_loc = rparen_loc + }; + + return node; +} + +#endif diff --git a/prism/prism.c b/prism/prism.c index b6bc90ebd77234..6f21c97bc3b48f 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1,4 +1,5 @@ #include "prism.h" +#include "prism/node_new.h" /** * The prism version and the serialization format. @@ -2018,26 +2019,17 @@ pm_integer_arena_move(pm_arena_t *arena, pm_integer_t *integer) { } } -#define PM_NODE_ALLOC(parser_, type_) (type_ *) pm_arena_zalloc((parser_)->arena, sizeof(type_), PRISM_ALIGNOF(type_)) -#define PM_NODE_INIT(parser_, type_, flags_, location_) (pm_node_t) { \ - .type = (type_), \ - .flags = (flags_), \ - .node_id = ++(parser_)->node_id, \ - .location = location_ \ -} - /** * Allocate a new MissingNode node. */ static pm_missing_node_t * pm_missing_node_create(pm_parser_t *parser, uint32_t start, uint32_t length) { - pm_missing_node_t *node = PM_NODE_ALLOC(parser, pm_missing_node_t); - - *node = (pm_missing_node_t) { - .base = PM_NODE_INIT(parser, PM_MISSING_NODE, 0, ((pm_location_t) { .start = start, .length = length })) - }; - - return node; + return pm_missing_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = length }) + ); } /** @@ -2046,16 +2038,16 @@ pm_missing_node_create(pm_parser_t *parser, uint32_t start, uint32_t length) { static pm_alias_global_variable_node_t * pm_alias_global_variable_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *new_name, pm_node_t *old_name) { assert(keyword->type == PM_TOKEN_KEYWORD_ALIAS); - pm_alias_global_variable_node_t *node = PM_NODE_ALLOC(parser, pm_alias_global_variable_node_t); - - *node = (pm_alias_global_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_ALIAS_GLOBAL_VARIABLE_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, old_name)), - .new_name = new_name, - .old_name = old_name, - .keyword_loc = TOK2LOC(parser, keyword) - }; - return node; + return pm_alias_global_variable_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, old_name), + new_name, + old_name, + TOK2LOC(parser, keyword) + ); } /** @@ -2064,16 +2056,16 @@ pm_alias_global_variable_node_create(pm_parser_t *parser, const pm_token_t *keyw static pm_alias_method_node_t * pm_alias_method_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *new_name, pm_node_t *old_name) { assert(keyword->type == PM_TOKEN_KEYWORD_ALIAS); - pm_alias_method_node_t *node = PM_NODE_ALLOC(parser, pm_alias_method_node_t); - - *node = (pm_alias_method_node_t) { - .base = PM_NODE_INIT(parser, PM_ALIAS_METHOD_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, old_name)), - .new_name = new_name, - .old_name = old_name, - .keyword_loc = TOK2LOC(parser, keyword) - }; - return node; + return pm_alias_method_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, old_name), + new_name, + old_name, + TOK2LOC(parser, keyword) + ); } /** @@ -2081,16 +2073,15 @@ pm_alias_method_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_n */ static pm_alternation_pattern_node_t * pm_alternation_pattern_node_create(pm_parser_t *parser, pm_node_t *left, pm_node_t *right, const pm_token_t *operator) { - pm_alternation_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_alternation_pattern_node_t); - - *node = (pm_alternation_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ALTERNATION_PATTERN_NODE, 0, PM_LOCATION_INIT_NODES(left, right)), - .left = left, - .right = right, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_alternation_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(left, right), + left, + right, + TOK2LOC(parser, operator) + ); } /** @@ -2100,16 +2091,15 @@ static pm_and_node_t * pm_and_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operator, pm_node_t *right) { pm_assert_value_expression(parser, left); - pm_and_node_t *node = PM_NODE_ALLOC(parser, pm_and_node_t); - - *node = (pm_and_node_t) { - .base = PM_NODE_INIT(parser, PM_AND_NODE, 0, PM_LOCATION_INIT_NODES(left, right)), - .left = left, - .operator_loc = TOK2LOC(parser, operator), - .right = right - }; - - return node; + return pm_and_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(left, right), + left, + right, + TOK2LOC(parser, operator) + ); } /** @@ -2117,14 +2107,13 @@ pm_and_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *opera */ static pm_arguments_node_t * pm_arguments_node_create(pm_parser_t *parser) { - pm_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_arguments_node_t); - - *node = (pm_arguments_node_t) { - .base = PM_NODE_INIT(parser, PM_ARGUMENTS_NODE, 0, PM_LOCATION_INIT_UNSET), - .arguments = { 0 } - }; - - return node; + return pm_arguments_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }) + ); } /** @@ -2164,25 +2153,27 @@ pm_arguments_node_arguments_append(pm_arena_t *arena, pm_arguments_node_t *node, */ static pm_array_node_t * pm_array_node_create(pm_parser_t *parser, const pm_token_t *opening) { - pm_array_node_t *node = PM_NODE_ALLOC(parser, pm_array_node_t); - if (opening == NULL) { - *node = (pm_array_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_UNSET), - .opening_loc = { 0 }, - .closing_loc = { 0 }, - .elements = { 0 } - }; + return pm_array_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); } else { - *node = (pm_array_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, opening)), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, opening), - .elements = { 0 } - }; + return pm_array_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, opening), + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, opening), + TOK2LOC(parser, opening) + ); } - - return node; } /** @@ -2224,17 +2215,18 @@ pm_array_node_close_set(const pm_parser_t *parser, pm_array_node_t *node, const */ static pm_array_pattern_node_t * pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *nodes) { - pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); - - *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, PM_LOCATION_INIT_NODES(nodes->nodes[0], nodes->nodes[nodes->size - 1])), - .constant = NULL, - .rest = NULL, - .requireds = { 0 }, - .posts = { 0 }, - .opening_loc = { 0 }, - .closing_loc = { 0 } - }; + pm_array_pattern_node_t *node = pm_array_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(nodes->nodes[0], nodes->nodes[nodes->size - 1]), + NULL, + ((pm_node_list_t) { 0 }), + NULL, + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); // For now we're going to just copy over each pointer manually. This could be // much more efficient, as we could instead resize the node list. @@ -2260,19 +2252,18 @@ pm_array_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *node */ static pm_array_pattern_node_t * pm_array_pattern_node_rest_create(pm_parser_t *parser, pm_node_t *rest) { - pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); - - *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, PM_LOCATION_INIT_NODE(rest)), - .constant = NULL, - .rest = rest, - .requireds = { 0 }, - .posts = { 0 }, - .opening_loc = { 0 }, - .closing_loc = { 0 } - }; - - return node; + return pm_array_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODE(rest), + NULL, + ((pm_node_list_t) { 0 }), + rest, + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); } /** @@ -2281,19 +2272,18 @@ pm_array_pattern_node_rest_create(pm_parser_t *parser, pm_node_t *rest) { */ static pm_array_pattern_node_t * pm_array_pattern_node_constant_create(pm_parser_t *parser, pm_node_t *constant, const pm_token_t *opening, const pm_token_t *closing) { - pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); - - *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, PM_LOCATION_INIT_NODE_TOKEN(parser, constant, closing)), - .constant = constant, - .rest = NULL, - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing), - .requireds = { 0 }, - .posts = { 0 } - }; - - return node; + return pm_array_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODE_TOKEN(parser, constant, closing), + constant, + ((pm_node_list_t) { 0 }), + NULL, + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -2302,19 +2292,18 @@ pm_array_pattern_node_constant_create(pm_parser_t *parser, pm_node_t *constant, */ static pm_array_pattern_node_t * pm_array_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *closing) { - pm_array_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_array_pattern_node_t); - - *node = (pm_array_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_ARRAY_PATTERN_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .constant = NULL, - .rest = NULL, - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing), - .requireds = { 0 }, - .posts = { 0 } - }; - - return node; + return pm_array_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + NULL, + ((pm_node_list_t) { 0 }), + NULL, + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } static inline void @@ -2327,7 +2316,6 @@ pm_array_pattern_node_requireds_append(pm_arena_t *arena, pm_array_pattern_node_ */ static pm_assoc_node_t * pm_assoc_node_create(pm_parser_t *parser, pm_node_t *key, const pm_token_t *operator, pm_node_t *value) { - pm_assoc_node_t *node = PM_NODE_ALLOC(parser, pm_assoc_node_t); uint32_t end; if (value != NULL && PM_NODE_END(value) > PM_NODE_END(key)) { @@ -2355,14 +2343,15 @@ pm_assoc_node_create(pm_parser_t *parser, pm_node_t *key, const pm_token_t *oper flags = key->flags & value->flags & PM_NODE_FLAG_STATIC_LITERAL; } - *node = (pm_assoc_node_t) { - .base = PM_NODE_INIT(parser, PM_ASSOC_NODE, flags, ((pm_location_t) { .start = PM_NODE_START(key), .length = U32(end - PM_NODE_START(key)) })), - .key = key, - .operator_loc = NTOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_assoc_node_new( + parser->arena, + ++parser->node_id, + flags, + ((pm_location_t) { .start = PM_NODE_START(key), .length = U32(end - PM_NODE_START(key)) }), + key, + value, + NTOK2LOC(parser, operator) + ); } /** @@ -2371,15 +2360,15 @@ pm_assoc_node_create(pm_parser_t *parser, pm_node_t *key, const pm_token_t *oper static pm_assoc_splat_node_t * pm_assoc_splat_node_create(pm_parser_t *parser, pm_node_t *value, const pm_token_t *operator) { assert(operator->type == PM_TOKEN_USTAR_STAR); - pm_assoc_splat_node_t *node = PM_NODE_ALLOC(parser, pm_assoc_splat_node_t); - - *node = (pm_assoc_splat_node_t) { - .base = PM_NODE_INIT(parser, PM_ASSOC_SPLAT_NODE, 0, (value == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, value)), - .value = value, - .operator_loc = TOK2LOC(parser, operator) - }; - return node; + return pm_assoc_splat_node_new( + parser->arena, + ++parser->node_id, + 0, + (value == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, value), + value, + TOK2LOC(parser, operator) + ); } /** @@ -2388,14 +2377,14 @@ pm_assoc_splat_node_create(pm_parser_t *parser, pm_node_t *value, const pm_token static pm_back_reference_read_node_t * pm_back_reference_read_node_create(pm_parser_t *parser, const pm_token_t *name) { assert(name->type == PM_TOKEN_BACK_REFERENCE); - pm_back_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_back_reference_read_node_t); - - *node = (pm_back_reference_read_node_t) { - .base = PM_NODE_INIT(parser, PM_BACK_REFERENCE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = pm_parser_constant_id_token(parser, name) - }; - return node; + return pm_back_reference_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -2403,19 +2392,21 @@ pm_back_reference_read_node_create(pm_parser_t *parser, const pm_token_t *name) */ static pm_begin_node_t * pm_begin_node_create(pm_parser_t *parser, const pm_token_t *begin_keyword, pm_statements_node_t *statements) { - pm_begin_node_t *node = PM_NODE_ALLOC(parser, pm_begin_node_t); - uint32_t start = begin_keyword == NULL ? 0 : PM_TOKEN_START(parser, begin_keyword); uint32_t end = statements == NULL ? (begin_keyword == NULL ? 0 : PM_TOKEN_END(parser, begin_keyword)) : PM_NODE_END(statements); - *node = (pm_begin_node_t) { - .base = PM_NODE_INIT(parser, PM_BEGIN_NODE, 0, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .begin_keyword_loc = NTOK2LOC(parser, begin_keyword), - .statements = statements, - .end_keyword_loc = { 0 } - }; - - return node; + return pm_begin_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NTOK2LOC(parser, begin_keyword), + statements, + NULL, + NULL, + NULL, + ((pm_location_t) { 0 }) + ); } /** @@ -2470,15 +2461,15 @@ pm_begin_node_end_keyword_set(const pm_parser_t *parser, pm_begin_node_t *node, static pm_block_argument_node_t * pm_block_argument_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t *expression) { assert(operator->type == PM_TOKEN_UAMPERSAND); - pm_block_argument_node_t *node = PM_NODE_ALLOC(parser, pm_block_argument_node_t); - *node = (pm_block_argument_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_ARGUMENT_NODE, 0, (expression == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, expression)), - .expression = expression, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_block_argument_node_new( + parser->arena, + ++parser->node_id, + 0, + (expression == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, expression), + expression, + TOK2LOC(parser, operator) + ); } /** @@ -2486,18 +2477,17 @@ pm_block_argument_node_create(pm_parser_t *parser, const pm_token_t *operator, p */ static pm_block_node_t * pm_block_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_token_t *opening, pm_node_t *parameters, pm_node_t *body, const pm_token_t *closing) { - pm_block_node_t *node = PM_NODE_ALLOC(parser, pm_block_node_t); - - *node = (pm_block_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .locals = *locals, - .parameters = parameters, - .body = body, - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing) - }; - - return node; + return pm_block_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + *locals, + parameters, + body, + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -2506,16 +2496,16 @@ pm_block_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p static pm_block_parameter_node_t * pm_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, const pm_token_t *operator) { assert(operator->type == PM_TOKEN_UAMPERSAND || operator->type == PM_TOKEN_AMPERSAND); - pm_block_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_block_parameter_node_t); - *node = (pm_block_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETER_NODE, 0, (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name)), - .name = name == NULL ? 0 : pm_parser_constant_id_token(parser, name), - .name_loc = NTOK2LOC(parser, name), - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_block_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name), + name == NULL ? 0 : pm_parser_constant_id_token(parser, name), + NTOK2LOC(parser, name), + TOK2LOC(parser, operator) + ); } /** @@ -2523,8 +2513,6 @@ pm_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, cons */ static pm_block_parameters_node_t * pm_block_parameters_node_create(pm_parser_t *parser, pm_parameters_node_t *parameters, const pm_token_t *opening) { - pm_block_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_block_parameters_node_t); - uint32_t start; if (opening != NULL) { start = PM_TOKEN_START(parser, opening); @@ -2543,15 +2531,16 @@ pm_block_parameters_node_create(pm_parser_t *parser, pm_parameters_node_t *param end = 0; } - *node = (pm_block_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_PARAMETERS_NODE, 0, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .parameters = parameters, - .opening_loc = NTOK2LOC(parser, opening), - .closing_loc = { 0 }, - .locals = { 0 } - }; - - return node; + return pm_block_parameters_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + parameters, + ((pm_node_list_t) { 0 }), + NTOK2LOC(parser, opening), + ((pm_location_t) { 0 }) + ); } /** @@ -2569,14 +2558,13 @@ pm_block_parameters_node_closing_set(const pm_parser_t *parser, pm_block_paramet */ static pm_block_local_variable_node_t * pm_block_local_variable_node_create(pm_parser_t *parser, const pm_token_t *name) { - pm_block_local_variable_node_t *node = PM_NODE_ALLOC(parser, pm_block_local_variable_node_t); - - *node = (pm_block_local_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_BLOCK_LOCAL_VARIABLE_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = pm_parser_constant_id_token(parser, name) - }; - - return node; + return pm_block_local_variable_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -2599,15 +2587,15 @@ pm_block_parameters_node_append_local(pm_arena_t *arena, pm_block_parameters_nod static pm_break_node_t * pm_break_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments_node_t *arguments) { assert(keyword->type == PM_TOKEN_KEYWORD_BREAK); - pm_break_node_t *node = PM_NODE_ALLOC(parser, pm_break_node_t); - - *node = (pm_break_node_t) { - .base = PM_NODE_INIT(parser, PM_BREAK_NODE, 0, (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments)), - .arguments = arguments, - .keyword_loc = TOK2LOC(parser, keyword) - }; - return node; + return pm_break_node_new( + parser->arena, + ++parser->node_id, + 0, + (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments), + arguments, + TOK2LOC(parser, keyword) + ); } // There are certain flags that we want to use internally but don't want to @@ -2626,22 +2614,21 @@ static const pm_node_flags_t PM_CALL_NODE_FLAGS_INDEX = ((PM_CALL_NODE_FLAGS_LAS */ static pm_call_node_t * pm_call_node_create(pm_parser_t *parser, pm_node_flags_t flags) { - pm_call_node_t *node = PM_NODE_ALLOC(parser, pm_call_node_t); - - *node = (pm_call_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_NODE, flags, PM_LOCATION_INIT_UNSET), - .receiver = NULL, - .call_operator_loc = { 0 }, - .message_loc = { 0 }, - .opening_loc = { 0 }, - .arguments = NULL, - .closing_loc = { 0 }, - .equal_loc = { 0 }, - .block = NULL, - .name = 0 - }; - - return node; + return pm_call_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_UNSET, + NULL, + ((pm_location_t) { 0 }), + 0, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + NULL, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + NULL + ); } /** @@ -2938,18 +2925,20 @@ static pm_call_and_write_node_t * pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(target->block == NULL); assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_call_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_and_write_node_t); - - *node = (pm_call_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_AND_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .message_loc = target->message_loc, - .read_name = 0, - .write_name = target->name, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_call_and_write_node_t *node = pm_call_and_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->message_loc, + 0, + target->name, + TOK2LOC(parser, operator), + value + ); pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); @@ -2988,22 +2977,25 @@ pm_index_arguments_check(pm_parser_t *parser, const pm_arguments_node_t *argumen static pm_index_and_write_node_t * pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_index_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_index_and_write_node_t); pm_index_arguments_check(parser, target->arguments, target->block); assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); - *node = (pm_index_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_AND_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .opening_loc = target->opening_loc, - .arguments = target->arguments, - .closing_loc = target->closing_loc, - .block = (pm_block_argument_node_t *) target->block, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_index_and_write_node_t *node = pm_index_and_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->opening_loc, + target->arguments, + target->closing_loc, + (pm_block_argument_node_t *) target->block, + TOK2LOC(parser, operator), + value + ); // The target is no longer necessary because we've reused its children. // It is arena-allocated so no explicit free is needed. @@ -3017,19 +3009,21 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons static pm_call_operator_write_node_t * pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(target->block == NULL); - pm_call_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_operator_write_node_t); - - *node = (pm_call_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_OPERATOR_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .message_loc = target->message_loc, - .read_name = 0, - .write_name = target->name, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_call_operator_write_node_t *node = pm_call_operator_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->message_loc, + 0, + target->name, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), + TOK2LOC(parser, operator), + value + ); pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); @@ -3044,23 +3038,25 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, */ static pm_index_operator_write_node_t * pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_index_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_index_operator_write_node_t); - pm_index_arguments_check(parser, target->arguments, target->block); assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); - *node = (pm_index_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_OPERATOR_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .opening_loc = target->opening_loc, - .arguments = target->arguments, - .closing_loc = target->closing_loc, - .block = (pm_block_argument_node_t *) target->block, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_index_operator_write_node_t *node = pm_index_operator_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->opening_loc, + target->arguments, + target->closing_loc, + (pm_block_argument_node_t *) target->block, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), + TOK2LOC(parser, operator), + value + ); // The target is no longer necessary because we've reused its children. // It is arena-allocated so no explicit free is needed. @@ -3075,18 +3071,20 @@ static pm_call_or_write_node_t * pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(target->block == NULL); assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_call_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_call_or_write_node_t); - - *node = (pm_call_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_OR_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .message_loc = target->message_loc, - .read_name = 0, - .write_name = target->name, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_call_or_write_node_t *node = pm_call_or_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->message_loc, + 0, + target->name, + TOK2LOC(parser, operator), + value + ); pm_call_write_read_name_init(parser, &node->read_name, &node->write_name); @@ -3102,22 +3100,25 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const static pm_index_or_write_node_t * pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_index_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_index_or_write_node_t); pm_index_arguments_check(parser, target->arguments, target->block); assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); - *node = (pm_index_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_OR_WRITE_NODE, FL(target), PM_LOCATION_INIT_NODES(target, value)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .opening_loc = target->opening_loc, - .arguments = target->arguments, - .closing_loc = target->closing_loc, - .block = (pm_block_argument_node_t *) target->block, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; + + pm_index_or_write_node_t *node = pm_index_or_write_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODES(target, value), + target->receiver, + target->call_operator_loc, + target->opening_loc, + target->arguments, + target->closing_loc, + (pm_block_argument_node_t *) target->block, + TOK2LOC(parser, operator), + value + ); // The target is no longer necessary because we've reused its children. // It is arena-allocated so no explicit free is needed. @@ -3131,15 +3132,16 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const */ static pm_call_target_node_t * pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { - pm_call_target_node_t *node = PM_NODE_ALLOC(parser, pm_call_target_node_t); - - *node = (pm_call_target_node_t) { - .base = PM_NODE_INIT(parser, PM_CALL_TARGET_NODE, FL(target), PM_LOCATION_INIT_NODE(target)), - .receiver = target->receiver, - .call_operator_loc = target->call_operator_loc, - .name = target->name, - .message_loc = target->message_loc - }; + pm_call_target_node_t *node = pm_call_target_node_new( + parser->arena, + ++parser->node_id, + FL(target), + PM_LOCATION_INIT_NODE(target), + target->receiver, + target->call_operator_loc, + target->name, + target->message_loc + ); /* It is possible to get here where we have parsed an invalid syntax tree * where the call operator was not present. In that case we will have a @@ -3161,19 +3163,20 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { */ static pm_index_target_node_t * pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { - pm_index_target_node_t *node = PM_NODE_ALLOC(parser, pm_index_target_node_t); - pm_index_arguments_check(parser, target->arguments, target->block); assert(!target->block || PM_NODE_TYPE_P(target->block, PM_BLOCK_ARGUMENT_NODE)); - *node = (pm_index_target_node_t) { - .base = PM_NODE_INIT(parser, PM_INDEX_TARGET_NODE, FL(target) | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, PM_LOCATION_INIT_NODE(target)), - .receiver = target->receiver, - .opening_loc = target->opening_loc, - .arguments = target->arguments, - .closing_loc = target->closing_loc, - .block = (pm_block_argument_node_t *) target->block, - }; + pm_index_target_node_t *node = pm_index_target_node_new( + parser->arena, + ++parser->node_id, + FL(target) | PM_CALL_NODE_FLAGS_ATTRIBUTE_WRITE, + PM_LOCATION_INIT_NODE(target), + target->receiver, + target->opening_loc, + target->arguments, + target->closing_loc, + (pm_block_argument_node_t *) target->block + ); // The target is no longer necessary because we've reused its children. // It is arena-allocated so no explicit free is needed. @@ -3186,16 +3189,15 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { */ static pm_capture_pattern_node_t * pm_capture_pattern_node_create(pm_parser_t *parser, pm_node_t *value, pm_local_variable_target_node_t *target, const pm_token_t *operator) { - pm_capture_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_capture_pattern_node_t); - - *node = (pm_capture_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_CAPTURE_PATTERN_NODE, 0, PM_LOCATION_INIT_NODES(value, target)), - .value = value, - .target = target, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_capture_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(value, target), + value, + target, + TOK2LOC(parser, operator) + ); } /** @@ -3203,18 +3205,17 @@ pm_capture_pattern_node_create(pm_parser_t *parser, pm_node_t *value, pm_local_v */ static pm_case_node_t * pm_case_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, pm_node_t *predicate, const pm_token_t *end_keyword) { - pm_case_node_t *node = PM_NODE_ALLOC(parser, pm_case_node_t); - - *node = (pm_case_node_t) { - .base = PM_NODE_INIT(parser, PM_CASE_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, case_keyword, end_keyword == NULL ? case_keyword : end_keyword)), - .predicate = predicate, - .else_clause = NULL, - .case_keyword_loc = TOK2LOC(parser, case_keyword), - .end_keyword_loc = NTOK2LOC(parser, end_keyword), - .conditions = { 0 } - }; - - return node; + return pm_case_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, case_keyword, end_keyword == NULL ? case_keyword : end_keyword), + predicate, + ((pm_node_list_t) { 0 }), + NULL, + TOK2LOC(parser, case_keyword), + NTOK2LOC(parser, end_keyword) + ); } /** @@ -3251,18 +3252,17 @@ pm_case_node_end_keyword_loc_set(const pm_parser_t *parser, pm_case_node_t *node */ static pm_case_match_node_t * pm_case_match_node_create(pm_parser_t *parser, const pm_token_t *case_keyword, pm_node_t *predicate) { - pm_case_match_node_t *node = PM_NODE_ALLOC(parser, pm_case_match_node_t); - - *node = (pm_case_match_node_t) { - .base = PM_NODE_INIT(parser, PM_CASE_MATCH_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, case_keyword)), - .predicate = predicate, - .else_clause = NULL, - .case_keyword_loc = TOK2LOC(parser, case_keyword), - .end_keyword_loc = { 0 }, - .conditions = { 0 } - }; - - return node; + return pm_case_match_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, case_keyword), + predicate, + ((pm_node_list_t) { 0 }), + NULL, + TOK2LOC(parser, case_keyword), + ((pm_location_t) { 0 }) + ); } /** @@ -3299,21 +3299,20 @@ pm_case_match_node_end_keyword_loc_set(const pm_parser_t *parser, pm_case_match_ */ static pm_class_node_t * pm_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_token_t *class_keyword, pm_node_t *constant_path, const pm_token_t *name, const pm_token_t *inheritance_operator, pm_node_t *superclass, pm_node_t *body, const pm_token_t *end_keyword) { - pm_class_node_t *node = PM_NODE_ALLOC(parser, pm_class_node_t); - - *node = (pm_class_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, class_keyword, end_keyword)), - .locals = *locals, - .class_keyword_loc = TOK2LOC(parser, class_keyword), - .constant_path = constant_path, - .inheritance_operator_loc = NTOK2LOC(parser, inheritance_operator), - .superclass = superclass, - .body = body, - .end_keyword_loc = TOK2LOC(parser, end_keyword), - .name = pm_parser_constant_id_token(parser, name) - }; - - return node; + return pm_class_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, class_keyword, end_keyword), + *locals, + TOK2LOC(parser, class_keyword), + constant_path, + NTOK2LOC(parser, inheritance_operator), + superclass, + body, + TOK2LOC(parser, end_keyword), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -3322,17 +3321,17 @@ pm_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const p static pm_class_variable_and_write_node_t * pm_class_variable_and_write_node_create(pm_parser_t *parser, pm_class_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_class_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_and_write_node_t); - - *node = (pm_class_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_class_variable_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -3340,18 +3339,17 @@ pm_class_variable_and_write_node_create(pm_parser_t *parser, pm_class_variable_r */ static pm_class_variable_operator_write_node_t * pm_class_variable_operator_write_node_create(pm_parser_t *parser, pm_class_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_class_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_operator_write_node_t); - - *node = (pm_class_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) - }; - - return node; + return pm_class_variable_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) + ); } /** @@ -3360,17 +3358,17 @@ pm_class_variable_operator_write_node_create(pm_parser_t *parser, pm_class_varia static pm_class_variable_or_write_node_t * pm_class_variable_or_write_node_create(pm_parser_t *parser, pm_class_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_class_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_or_write_node_t); - - *node = (pm_class_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_class_variable_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -3379,14 +3377,14 @@ pm_class_variable_or_write_node_create(pm_parser_t *parser, pm_class_variable_re static pm_class_variable_read_node_t * pm_class_variable_read_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_CLASS_VARIABLE); - pm_class_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_read_node_t); - - *node = (pm_class_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)), - .name = pm_parser_constant_id_token(parser, token) - }; - return node; + return pm_class_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token), + pm_parser_constant_id_token(parser, token) + ); } /** @@ -3408,18 +3406,16 @@ pm_implicit_array_write_flags(const pm_node_t *node, pm_node_flags_t flags) { */ static pm_class_variable_write_node_t * pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_node_t *read_node, pm_token_t *operator, pm_node_t *value) { - pm_class_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_class_variable_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_class_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CLASS_VARIABLE_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(read_node, value)), - .name = read_node->name, - .name_loc = read_node->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_class_variable_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(read_node, value), + read_node->name, + read_node->base.location, + value, + TOK2LOC(parser, operator) + ); } /** @@ -3428,16 +3424,16 @@ pm_class_variable_write_node_create(pm_parser_t *parser, pm_class_variable_read_ static pm_constant_path_and_write_node_t * pm_constant_path_and_write_node_create(pm_parser_t *parser, pm_constant_path_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_constant_path_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_and_write_node_t); - - *node = (pm_constant_path_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .target = target, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_constant_path_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target, + TOK2LOC(parser, operator), + value + ); } /** @@ -3445,17 +3441,16 @@ pm_constant_path_and_write_node_create(pm_parser_t *parser, pm_constant_path_nod */ static pm_constant_path_operator_write_node_t * pm_constant_path_operator_write_node_create(pm_parser_t *parser, pm_constant_path_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_constant_path_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_operator_write_node_t); - - *node = (pm_constant_path_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .target = target, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) - }; - - return node; + return pm_constant_path_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target, + TOK2LOC(parser, operator), + value, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) + ); } /** @@ -3464,16 +3459,16 @@ pm_constant_path_operator_write_node_create(pm_parser_t *parser, pm_constant_pat static pm_constant_path_or_write_node_t * pm_constant_path_or_write_node_create(pm_parser_t *parser, pm_constant_path_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_constant_path_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_or_write_node_t); - - *node = (pm_constant_path_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .target = target, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_constant_path_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target, + TOK2LOC(parser, operator), + value + ); } /** @@ -3482,22 +3477,22 @@ pm_constant_path_or_write_node_create(pm_parser_t *parser, pm_constant_path_node static pm_constant_path_node_t * pm_constant_path_node_create(pm_parser_t *parser, pm_node_t *parent, const pm_token_t *delimiter, const pm_token_t *name_token) { pm_assert_value_expression(parser, parent); - pm_constant_path_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_node_t); pm_constant_id_t name = PM_CONSTANT_ID_UNSET; if (name_token->type == PM_TOKEN_CONSTANT) { name = pm_parser_constant_id_token(parser, name_token); } - *node = (pm_constant_path_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_NODE, 0, (parent == NULL) ? PM_LOCATION_INIT_TOKENS(parser, delimiter, name_token) : PM_LOCATION_INIT_NODE_TOKEN(parser, parent, name_token)), - .parent = parent, - .name = name, - .delimiter_loc = TOK2LOC(parser, delimiter), - .name_loc = TOK2LOC(parser, name_token) - }; - - return node; + return pm_constant_path_node_new( + parser->arena, + ++parser->node_id, + 0, + (parent == NULL) ? PM_LOCATION_INIT_TOKENS(parser, delimiter, name_token) : PM_LOCATION_INIT_NODE_TOKEN(parser, parent, name_token), + parent, + name, + TOK2LOC(parser, delimiter), + TOK2LOC(parser, name_token) + ); } /** @@ -3505,17 +3500,15 @@ pm_constant_path_node_create(pm_parser_t *parser, pm_node_t *parent, const pm_to */ static pm_constant_path_write_node_t * pm_constant_path_write_node_create(pm_parser_t *parser, pm_constant_path_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_constant_path_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_path_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_constant_path_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_PATH_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(target, value)), - .target = target, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_constant_path_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(target, value), + target, + TOK2LOC(parser, operator), + value + ); } /** @@ -3524,17 +3517,17 @@ pm_constant_path_write_node_create(pm_parser_t *parser, pm_constant_path_node_t static pm_constant_and_write_node_t * pm_constant_and_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_constant_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_and_write_node_t); - - *node = (pm_constant_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_constant_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -3542,18 +3535,17 @@ pm_constant_and_write_node_create(pm_parser_t *parser, pm_constant_read_node_t * */ static pm_constant_operator_write_node_t * pm_constant_operator_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_constant_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_operator_write_node_t); - - *node = (pm_constant_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) - }; - - return node; + return pm_constant_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) + ); } /** @@ -3562,17 +3554,17 @@ pm_constant_operator_write_node_create(pm_parser_t *parser, pm_constant_read_nod static pm_constant_or_write_node_t * pm_constant_or_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_constant_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_or_write_node_t); - - *node = (pm_constant_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_constant_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -3581,14 +3573,14 @@ pm_constant_or_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *t static pm_constant_read_node_t * pm_constant_read_node_create(pm_parser_t *parser, const pm_token_t *name) { assert(name->type == PM_TOKEN_CONSTANT || name->type == 0); - pm_constant_read_node_t *node = PM_NODE_ALLOC(parser, pm_constant_read_node_t); - - *node = (pm_constant_read_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = pm_parser_constant_id_token(parser, name) - }; - return node; + return pm_constant_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -3596,18 +3588,16 @@ pm_constant_read_node_create(pm_parser_t *parser, const pm_token_t *name) { */ static pm_constant_write_node_t * pm_constant_write_node_create(pm_parser_t *parser, pm_constant_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_constant_write_node_t *node = PM_NODE_ALLOC(parser, pm_constant_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_constant_write_node_t) { - .base = PM_NODE_INIT(parser, PM_CONSTANT_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_constant_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + value, + TOK2LOC(parser, operator) + ); } /** @@ -3673,29 +3663,28 @@ pm_def_node_create( const pm_token_t *equal, const pm_token_t *end_keyword ) { - pm_def_node_t *node = PM_NODE_ALLOC(parser, pm_def_node_t); - if (receiver != NULL) { pm_def_node_receiver_check(parser, receiver); } - *node = (pm_def_node_t) { - .base = PM_NODE_INIT(parser, PM_DEF_NODE, 0, (end_keyword == NULL) ? PM_LOCATION_INIT_TOKEN_NODE(parser, def_keyword, body) : PM_LOCATION_INIT_TOKENS(parser, def_keyword, end_keyword)), - .name = name, - .name_loc = TOK2LOC(parser, name_loc), - .receiver = receiver, - .parameters = parameters, - .body = body, - .locals = *locals, - .def_keyword_loc = TOK2LOC(parser, def_keyword), - .operator_loc = NTOK2LOC(parser, operator), - .lparen_loc = NTOK2LOC(parser, lparen), - .rparen_loc = NTOK2LOC(parser, rparen), - .equal_loc = NTOK2LOC(parser, equal), - .end_keyword_loc = NTOK2LOC(parser, end_keyword) - }; - - return node; + return pm_def_node_new( + parser->arena, + ++parser->node_id, + 0, + (end_keyword == NULL) ? PM_LOCATION_INIT_TOKEN_NODE(parser, def_keyword, body) : PM_LOCATION_INIT_TOKENS(parser, def_keyword, end_keyword), + name, + TOK2LOC(parser, name_loc), + receiver, + parameters, + body, + *locals, + TOK2LOC(parser, def_keyword), + NTOK2LOC(parser, operator), + NTOK2LOC(parser, lparen), + NTOK2LOC(parser, rparen), + NTOK2LOC(parser, equal), + NTOK2LOC(parser, end_keyword) + ); } /** @@ -3703,17 +3692,16 @@ pm_def_node_create( */ static pm_defined_node_t * pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t *value, const pm_token_t *rparen, const pm_token_t *keyword) { - pm_defined_node_t *node = PM_NODE_ALLOC(parser, pm_defined_node_t); - - *node = (pm_defined_node_t) { - .base = PM_NODE_INIT(parser, PM_DEFINED_NODE, 0, (rparen == NULL) ? PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, value) : PM_LOCATION_INIT_TOKENS(parser, keyword, rparen)), - .lparen_loc = NTOK2LOC(parser, lparen), - .value = value, - .rparen_loc = NTOK2LOC(parser, rparen), - .keyword_loc = TOK2LOC(parser, keyword) - }; - - return node; + return pm_defined_node_new( + parser->arena, + ++parser->node_id, + 0, + (rparen == NULL) ? PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, value) : PM_LOCATION_INIT_TOKENS(parser, keyword, rparen), + NTOK2LOC(parser, lparen), + value, + NTOK2LOC(parser, rparen), + TOK2LOC(parser, keyword) + ); } /** @@ -3721,16 +3709,15 @@ pm_defined_node_create(pm_parser_t *parser, const pm_token_t *lparen, pm_node_t */ static pm_else_node_t * pm_else_node_create(pm_parser_t *parser, const pm_token_t *else_keyword, pm_statements_node_t *statements, const pm_token_t *end_keyword) { - pm_else_node_t *node = PM_NODE_ALLOC(parser, pm_else_node_t); - - *node = (pm_else_node_t) { - .base = PM_NODE_INIT(parser, PM_ELSE_NODE, 0, ((end_keyword == NULL) && (statements != NULL)) ? PM_LOCATION_INIT_TOKEN_NODE(parser, else_keyword, statements) : PM_LOCATION_INIT_TOKENS(parser, else_keyword, end_keyword)), - .else_keyword_loc = TOK2LOC(parser, else_keyword), - .statements = statements, - .end_keyword_loc = NTOK2LOC(parser, end_keyword) - }; - - return node; + return pm_else_node_new( + parser->arena, + ++parser->node_id, + 0, + ((end_keyword == NULL) && (statements != NULL)) ? PM_LOCATION_INIT_TOKEN_NODE(parser, else_keyword, statements) : PM_LOCATION_INIT_TOKENS(parser, else_keyword, end_keyword), + TOK2LOC(parser, else_keyword), + statements, + NTOK2LOC(parser, end_keyword) + ); } /** @@ -3738,16 +3725,15 @@ pm_else_node_create(pm_parser_t *parser, const pm_token_t *else_keyword, pm_stat */ static pm_embedded_statements_node_t * pm_embedded_statements_node_create(pm_parser_t *parser, const pm_token_t *opening, pm_statements_node_t *statements, const pm_token_t *closing) { - pm_embedded_statements_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_statements_node_t); - - *node = (pm_embedded_statements_node_t) { - .base = PM_NODE_INIT(parser, PM_EMBEDDED_STATEMENTS_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .opening_loc = TOK2LOC(parser, opening), - .statements = statements, - .closing_loc = TOK2LOC(parser, closing) - }; - - return node; + return pm_embedded_statements_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + TOK2LOC(parser, opening), + statements, + TOK2LOC(parser, closing) + ); } /** @@ -3755,15 +3741,14 @@ pm_embedded_statements_node_create(pm_parser_t *parser, const pm_token_t *openin */ static pm_embedded_variable_node_t * pm_embedded_variable_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t *variable) { - pm_embedded_variable_node_t *node = PM_NODE_ALLOC(parser, pm_embedded_variable_node_t); - - *node = (pm_embedded_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_EMBEDDED_VARIABLE_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, operator, variable)), - .operator_loc = TOK2LOC(parser, operator), - .variable = variable - }; - - return node; + return pm_embedded_variable_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, operator, variable), + TOK2LOC(parser, operator), + variable + ); } /** @@ -3771,16 +3756,15 @@ pm_embedded_variable_node_create(pm_parser_t *parser, const pm_token_t *operator */ static pm_ensure_node_t * pm_ensure_node_create(pm_parser_t *parser, const pm_token_t *ensure_keyword, pm_statements_node_t *statements, const pm_token_t *end_keyword) { - pm_ensure_node_t *node = PM_NODE_ALLOC(parser, pm_ensure_node_t); - - *node = (pm_ensure_node_t) { - .base = PM_NODE_INIT(parser, PM_ENSURE_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, ensure_keyword, end_keyword)), - .ensure_keyword_loc = TOK2LOC(parser, ensure_keyword), - .statements = statements, - .end_keyword_loc = TOK2LOC(parser, end_keyword) - }; - - return node; + return pm_ensure_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, ensure_keyword, end_keyword), + TOK2LOC(parser, ensure_keyword), + statements, + TOK2LOC(parser, end_keyword) + ); } /** @@ -3789,13 +3773,13 @@ pm_ensure_node_create(pm_parser_t *parser, const pm_token_t *ensure_keyword, pm_ static pm_false_node_t * pm_false_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_FALSE); - pm_false_node_t *node = PM_NODE_ALLOC(parser, pm_false_node_t); - *node = (pm_false_node_t) { - .base = PM_NODE_INIT(parser, PM_FALSE_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - - return node; + return pm_false_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -3804,8 +3788,6 @@ pm_false_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_find_pattern_node_t * pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { - pm_find_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_find_pattern_node_t); - pm_node_t *left = nodes->nodes[0]; assert(PM_NODE_TYPE_P(left, PM_SPLAT_NODE)); pm_splat_node_t *left_splat_node = (pm_splat_node_t *) left; @@ -3826,15 +3808,19 @@ pm_find_pattern_node_create(pm_parser_t *parser, pm_node_list_t *nodes) { #else pm_node_t *right_splat_node = right; #endif - *node = (pm_find_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_FIND_PATTERN_NODE, 0, PM_LOCATION_INIT_NODES(left, right)), - .constant = NULL, - .left = left_splat_node, - .right = right_splat_node, - .requireds = { 0 }, - .opening_loc = { 0 }, - .closing_loc = { 0 } - }; + + pm_find_pattern_node_t *node = pm_find_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(left, right), + NULL, + left_splat_node, + ((pm_node_list_t) { 0 }), + right_splat_node, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); // For now we're going to just copy over each pointer manually. This could be // much more efficient, as we could instead resize the node list to only point @@ -3926,14 +3912,14 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) { static pm_float_node_t * pm_float_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_FLOAT); - pm_float_node_t *node = PM_NODE_ALLOC(parser, pm_float_node_t); - *node = (pm_float_node_t) { - .base = PM_NODE_INIT(parser, PM_FLOAT_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .value = pm_double_parse(parser, token) - }; - - return node; + return pm_float_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + pm_double_parse(parser, token) + ); } /** @@ -3943,17 +3929,17 @@ static pm_imaginary_node_t * pm_float_node_imaginary_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_FLOAT_IMAGINARY); - pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); - *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numeric = UP(pm_float_node_create(parser, &((pm_token_t) { + return pm_imaginary_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + UP(pm_float_node_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT, .start = token->start, .end = token->end - 1 }))) - }; - - return node; + ); } /** @@ -3963,12 +3949,14 @@ static pm_rational_node_t * pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_FLOAT_RATIONAL); - pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); - *node = (pm_rational_node_t) { - .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numerator = { 0 }, - .denominator = { 0 } - }; + pm_rational_node_t *node = pm_rational_node_new( + parser->arena, + ++parser->node_id, + PM_INTEGER_BASE_FLAGS_DECIMAL | PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + ((pm_integer_t) { 0 }), + ((pm_integer_t) { 0 }) + ); const uint8_t *start = token->start; const uint8_t *end = token->end - 1; // r @@ -4018,17 +4006,17 @@ static pm_imaginary_node_t * pm_float_node_rational_imaginary_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_FLOAT_RATIONAL_IMAGINARY); - pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); - *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numeric = UP(pm_float_node_rational_create(parser, &((pm_token_t) { + return pm_imaginary_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + UP(pm_float_node_rational_create(parser, &((pm_token_t) { .type = PM_TOKEN_FLOAT_RATIONAL, .start = token->start, .end = token->end - 1 }))) - }; - - return node; + ); } /** @@ -4045,20 +4033,19 @@ pm_for_node_create( const pm_token_t *do_keyword, const pm_token_t *end_keyword ) { - pm_for_node_t *node = PM_NODE_ALLOC(parser, pm_for_node_t); - - *node = (pm_for_node_t) { - .base = PM_NODE_INIT(parser, PM_FOR_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, for_keyword, end_keyword)), - .index = index, - .collection = collection, - .statements = statements, - .for_keyword_loc = TOK2LOC(parser, for_keyword), - .in_keyword_loc = TOK2LOC(parser, in_keyword), - .do_keyword_loc = NTOK2LOC(parser, do_keyword), - .end_keyword_loc = TOK2LOC(parser, end_keyword) - }; - - return node; + return pm_for_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, for_keyword, end_keyword), + index, + collection, + statements, + TOK2LOC(parser, for_keyword), + TOK2LOC(parser, in_keyword), + NTOK2LOC(parser, do_keyword), + TOK2LOC(parser, end_keyword) + ); } /** @@ -4067,13 +4054,13 @@ pm_for_node_create( static pm_forwarding_arguments_node_t * pm_forwarding_arguments_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_UDOT_DOT_DOT); - pm_forwarding_arguments_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_arguments_node_t); - - *node = (pm_forwarding_arguments_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_ARGUMENTS_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_forwarding_arguments_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -4082,13 +4069,13 @@ pm_forwarding_arguments_node_create(pm_parser_t *parser, const pm_token_t *token static pm_forwarding_parameter_node_t * pm_forwarding_parameter_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_UDOT_DOT_DOT); - pm_forwarding_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_parameter_node_t); - - *node = (pm_forwarding_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_forwarding_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -4098,19 +4085,19 @@ static pm_forwarding_super_node_t * pm_forwarding_super_node_create(pm_parser_t *parser, const pm_token_t *token, pm_arguments_t *arguments) { assert(arguments->block == NULL || PM_NODE_TYPE_P(arguments->block, PM_BLOCK_NODE)); assert(token->type == PM_TOKEN_KEYWORD_SUPER); - pm_forwarding_super_node_t *node = PM_NODE_ALLOC(parser, pm_forwarding_super_node_t); pm_block_node_t *block = NULL; if (arguments->block != NULL) { block = (pm_block_node_t *) arguments->block; } - *node = (pm_forwarding_super_node_t) { - .base = PM_NODE_INIT(parser, PM_FORWARDING_SUPER_NODE, 0, (block == NULL) ? PM_LOCATION_INIT_TOKEN(parser, token) : PM_LOCATION_INIT_TOKEN_NODE(parser, token, block)), - .block = block - }; - - return node; + return pm_forwarding_super_node_new( + parser->arena, + ++parser->node_id, + 0, + (block == NULL) ? PM_LOCATION_INIT_TOKEN(parser, token) : PM_LOCATION_INIT_TOKEN_NODE(parser, token, block), + block + ); } /** @@ -4119,18 +4106,17 @@ pm_forwarding_super_node_create(pm_parser_t *parser, const pm_token_t *token, pm */ static pm_hash_pattern_node_t * pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *closing) { - pm_hash_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_hash_pattern_node_t); - - *node = (pm_hash_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_HASH_PATTERN_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .constant = NULL, - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing), - .elements = { 0 }, - .rest = NULL - }; - - return node; + return pm_hash_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + NULL, + ((pm_node_list_t) { 0 }), + NULL, + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -4138,8 +4124,6 @@ pm_hash_pattern_node_empty_create(pm_parser_t *parser, const pm_token_t *opening */ static pm_hash_pattern_node_t * pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *elements, pm_node_t *rest) { - pm_hash_pattern_node_t *node = PM_NODE_ALLOC(parser, pm_hash_pattern_node_t); - uint32_t start; uint32_t end; @@ -4157,14 +4141,17 @@ pm_hash_pattern_node_node_list_create(pm_parser_t *parser, pm_node_list_t *eleme end = PM_NODE_END(rest); } - *node = (pm_hash_pattern_node_t) { - .base = PM_NODE_INIT(parser, PM_HASH_PATTERN_NODE, 0, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .constant = NULL, - .elements = { 0 }, - .rest = rest, - .opening_loc = { 0 }, - .closing_loc = { 0 } - }; + pm_hash_pattern_node_t *node = pm_hash_pattern_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NULL, + ((pm_node_list_t) { 0 }), + rest, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); pm_node_list_concat(parser->arena, &node->elements, elements); return node; @@ -4196,17 +4183,17 @@ pm_global_variable_write_name(pm_parser_t *parser, const pm_node_t *target) { static pm_global_variable_and_write_node_t * pm_global_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_global_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_and_write_node_t); - - *node = (pm_global_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = pm_global_variable_write_name(parser, target), - .name_loc = target->location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_global_variable_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + pm_global_variable_write_name(parser, target), + target->location, + TOK2LOC(parser, operator), + value + ); } /** @@ -4214,18 +4201,17 @@ pm_global_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, */ static pm_global_variable_operator_write_node_t * pm_global_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_global_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_operator_write_node_t); - - *node = (pm_global_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = pm_global_variable_write_name(parser, target), - .name_loc = target->location, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) - }; - - return node; + return pm_global_variable_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + pm_global_variable_write_name(parser, target), + target->location, + TOK2LOC(parser, operator), + value, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) + ); } /** @@ -4234,17 +4220,17 @@ pm_global_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *ta static pm_global_variable_or_write_node_t * pm_global_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_global_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_or_write_node_t); - - *node = (pm_global_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = pm_global_variable_write_name(parser, target), - .name_loc = target->location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_global_variable_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + pm_global_variable_write_name(parser, target), + target->location, + TOK2LOC(parser, operator), + value + ); } /** @@ -4252,14 +4238,13 @@ pm_global_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, */ static pm_global_variable_read_node_t * pm_global_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) { - pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); - - *node = (pm_global_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = pm_parser_constant_id_token(parser, name) - }; - - return node; + return pm_global_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -4267,14 +4252,13 @@ pm_global_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) */ static pm_global_variable_read_node_t * pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant_id_t name) { - pm_global_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_read_node_t); - - *node = (pm_global_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_UNSET), - .name = name - }; - - return node; + return pm_global_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + name + ); } /** @@ -4282,18 +4266,16 @@ pm_global_variable_read_node_synthesized_create(pm_parser_t *parser, pm_constant */ static pm_global_variable_write_node_t * pm_global_variable_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_global_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(target, value)), - .name = pm_global_variable_write_name(parser, target), - .name_loc = target->location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_global_variable_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(target, value), + pm_global_variable_write_name(parser, target), + target->location, + value, + TOK2LOC(parser, operator) + ); } /** @@ -4301,17 +4283,16 @@ pm_global_variable_write_node_create(pm_parser_t *parser, pm_node_t *target, con */ static pm_global_variable_write_node_t * pm_global_variable_write_node_synthesized_create(pm_parser_t *parser, pm_constant_id_t name, pm_node_t *value) { - pm_global_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_global_variable_write_node_t); - - *node = (pm_global_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_GLOBAL_VARIABLE_WRITE_NODE, 0, PM_LOCATION_INIT_UNSET), - .name = name, - .name_loc = { 0 }, - .operator_loc = { 0 }, - .value = value - }; - - return node; + return pm_global_variable_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + name, + ((pm_location_t) { 0 }), + value, + ((pm_location_t) { 0 }) + ); } /** @@ -4320,16 +4301,16 @@ pm_global_variable_write_node_synthesized_create(pm_parser_t *parser, pm_constan static pm_hash_node_t * pm_hash_node_create(pm_parser_t *parser, const pm_token_t *opening) { assert(opening != NULL); - pm_hash_node_t *node = PM_NODE_ALLOC(parser, pm_hash_node_t); - - *node = (pm_hash_node_t) { - .base = PM_NODE_INIT(parser, PM_HASH_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, opening)), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = { 0 }, - .elements = { 0 } - }; - return node; + return pm_hash_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, opening), + TOK2LOC(parser, opening), + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }) + ); } /** @@ -4371,7 +4352,6 @@ pm_if_node_create(pm_parser_t *parser, const pm_token_t *end_keyword ) { pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - pm_if_node_t *node = PM_NODE_ALLOC(parser, pm_if_node_t); uint32_t start = PM_TOKEN_START(parser, if_keyword); uint32_t end; @@ -4386,17 +4366,18 @@ pm_if_node_create(pm_parser_t *parser, end = PM_NODE_END(predicate); } - *node = (pm_if_node_t) { - .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .if_keyword_loc = TOK2LOC(parser, if_keyword), - .predicate = predicate, - .then_keyword_loc = NTOK2LOC(parser, then_keyword), - .statements = statements, - .subsequent = subsequent, - .end_keyword_loc = NTOK2LOC(parser, end_keyword) - }; - - return node; + return pm_if_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_NEWLINE, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + TOK2LOC(parser, if_keyword), + predicate, + NTOK2LOC(parser, then_keyword), + statements, + subsequent, + NTOK2LOC(parser, end_keyword) + ); } /** @@ -4405,22 +4386,22 @@ pm_if_node_create(pm_parser_t *parser, static pm_if_node_t * pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_token_t *if_keyword, pm_node_t *predicate) { pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - pm_if_node_t *node = PM_NODE_ALLOC(parser, pm_if_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); pm_statements_node_body_append(parser, statements, statement, true); - *node = (pm_if_node_t) { - .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, PM_LOCATION_INIT_NODES(statement, predicate)), - .if_keyword_loc = TOK2LOC(parser, if_keyword), - .predicate = predicate, - .then_keyword_loc = { 0 }, - .statements = statements, - .subsequent = NULL, - .end_keyword_loc = { 0 } - }; - - return node; + return pm_if_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_NEWLINE, + PM_LOCATION_INIT_NODES(statement, predicate), + TOK2LOC(parser, if_keyword), + predicate, + ((pm_location_t) { 0 }), + statements, + NULL, + ((pm_location_t) { 0 }) + ); } /** @@ -4438,20 +4419,18 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to pm_statements_node_body_append(parser, else_statements, false_expression, true); pm_else_node_t *else_node = pm_else_node_create(parser, colon, else_statements, NULL); - pm_if_node_t *node = PM_NODE_ALLOC(parser, pm_if_node_t); - - *node = (pm_if_node_t) { - .base = PM_NODE_INIT(parser, PM_IF_NODE, PM_NODE_FLAG_NEWLINE, PM_LOCATION_INIT_NODES(predicate, false_expression)), - .if_keyword_loc = { 0 }, - .predicate = predicate, - .then_keyword_loc = TOK2LOC(parser, qmark), - .statements = if_statements, - .subsequent = UP(else_node), - .end_keyword_loc = { 0 } - }; - - return node; - + return pm_if_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_NEWLINE, + PM_LOCATION_INIT_NODES(predicate, false_expression), + ((pm_location_t) { 0 }), + predicate, + TOK2LOC(parser, qmark), + if_statements, + UP(else_node), + ((pm_location_t) { 0 }) + ); } static inline void @@ -4471,14 +4450,13 @@ pm_else_node_end_keyword_loc_set(const pm_parser_t *parser, pm_else_node_t *node */ static pm_implicit_node_t * pm_implicit_node_create(pm_parser_t *parser, pm_node_t *value) { - pm_implicit_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_node_t); - - *node = (pm_implicit_node_t) { - .base = PM_NODE_INIT(parser, PM_IMPLICIT_NODE, 0, PM_LOCATION_INIT_NODE(value)), - .value = value - }; - - return node; + return pm_implicit_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODE(value), + value + ); } /** @@ -4488,13 +4466,12 @@ static pm_implicit_rest_node_t * pm_implicit_rest_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_COMMA); - pm_implicit_rest_node_t *node = PM_NODE_ALLOC(parser, pm_implicit_rest_node_t); - - *node = (pm_implicit_rest_node_t) { - .base = PM_NODE_INIT(parser, PM_IMPLICIT_REST_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - - return node; + return pm_implicit_rest_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -4503,12 +4480,14 @@ pm_implicit_rest_node_create(pm_parser_t *parser, const pm_token_t *token) { static pm_integer_node_t * pm_integer_node_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token_t *token) { assert(token->type == PM_TOKEN_INTEGER); - pm_integer_node_t *node = PM_NODE_ALLOC(parser, pm_integer_node_t); - *node = (pm_integer_node_t) { - .base = PM_NODE_INIT(parser, PM_INTEGER_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .value = { 0 } - }; + pm_integer_node_t *node = pm_integer_node_new( + parser->arena, + ++parser->node_id, + base | PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + ((pm_integer_t) { 0 }) + ); pm_integer_base_t integer_base = PM_INTEGER_BASE_DECIMAL; switch (base) { @@ -4532,17 +4511,17 @@ static pm_imaginary_node_t * pm_integer_node_imaginary_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token_t *token) { assert(token->type == PM_TOKEN_INTEGER_IMAGINARY); - pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); - *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numeric = UP(pm_integer_node_create(parser, base, &((pm_token_t) { + return pm_imaginary_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + UP(pm_integer_node_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER, .start = token->start, .end = token->end - 1 }))) - }; - - return node; + ); } /** @@ -4553,12 +4532,14 @@ static pm_rational_node_t * pm_integer_node_rational_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token_t *token) { assert(token->type == PM_TOKEN_INTEGER_RATIONAL); - pm_rational_node_t *node = PM_NODE_ALLOC(parser, pm_rational_node_t); - *node = (pm_rational_node_t) { - .base = PM_NODE_INIT(parser, PM_RATIONAL_NODE, base | PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numerator = { 0 }, - .denominator = { .value = 1, 0 } - }; + pm_rational_node_t *node = pm_rational_node_new( + parser->arena, + ++parser->node_id, + base | PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + ((pm_integer_t) { 0 }), + ((pm_integer_t) { .value = 1 }) + ); pm_integer_base_t integer_base = PM_INTEGER_BASE_DECIMAL; switch (base) { @@ -4583,17 +4564,17 @@ static pm_imaginary_node_t * pm_integer_node_rational_imaginary_create(pm_parser_t *parser, pm_node_flags_t base, const pm_token_t *token) { assert(token->type == PM_TOKEN_INTEGER_RATIONAL_IMAGINARY); - pm_imaginary_node_t *node = PM_NODE_ALLOC(parser, pm_imaginary_node_t); - *node = (pm_imaginary_node_t) { - .base = PM_NODE_INIT(parser, PM_IMAGINARY_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)), - .numeric = UP(pm_integer_node_rational_create(parser, base, &((pm_token_t) { + return pm_imaginary_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token), + UP(pm_integer_node_rational_create(parser, base, &((pm_token_t) { .type = PM_TOKEN_INTEGER_RATIONAL, .start = token->start, .end = token->end - 1 }))) - }; - - return node; + ); } /** @@ -4601,8 +4582,6 @@ pm_integer_node_rational_imaginary_create(pm_parser_t *parser, pm_node_flags_t b */ static pm_in_node_t * pm_in_node_create(pm_parser_t *parser, pm_node_t *pattern, pm_statements_node_t *statements, const pm_token_t *in_keyword, const pm_token_t *then_keyword) { - pm_in_node_t *node = PM_NODE_ALLOC(parser, pm_in_node_t); - uint32_t start = PM_TOKEN_START(parser, in_keyword); uint32_t end; @@ -4614,15 +4593,16 @@ pm_in_node_create(pm_parser_t *parser, pm_node_t *pattern, pm_statements_node_t end = PM_NODE_END(pattern); } - *node = (pm_in_node_t) { - .base = PM_NODE_INIT(parser, PM_IN_NODE, 0, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .pattern = pattern, - .statements = statements, - .in_loc = TOK2LOC(parser, in_keyword), - .then_loc = NTOK2LOC(parser, then_keyword) - }; - - return node; + return pm_in_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + pattern, + statements, + TOK2LOC(parser, in_keyword), + NTOK2LOC(parser, then_keyword) + ); } /** @@ -4631,17 +4611,17 @@ pm_in_node_create(pm_parser_t *parser, pm_node_t *pattern, pm_statements_node_t static pm_instance_variable_and_write_node_t * pm_instance_variable_and_write_node_create(pm_parser_t *parser, pm_instance_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_instance_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_and_write_node_t); - - *node = (pm_instance_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_instance_variable_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -4649,18 +4629,17 @@ pm_instance_variable_and_write_node_create(pm_parser_t *parser, pm_instance_vari */ static pm_instance_variable_operator_write_node_t * pm_instance_variable_operator_write_node_create(pm_parser_t *parser, pm_instance_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_instance_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_operator_write_node_t); - - *node = (pm_instance_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) - }; - - return node; + return pm_instance_variable_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1) + ); } /** @@ -4669,17 +4648,17 @@ pm_instance_variable_operator_write_node_create(pm_parser_t *parser, pm_instance static pm_instance_variable_or_write_node_t * pm_instance_variable_or_write_node_create(pm_parser_t *parser, pm_instance_variable_read_node_t *target, const pm_token_t *operator, pm_node_t *value) { assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_instance_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_or_write_node_t); - - *node = (pm_instance_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name = target->name, - .name_loc = target->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - return node; + return pm_instance_variable_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->name, + target->base.location, + TOK2LOC(parser, operator), + value + ); } /** @@ -4688,14 +4667,14 @@ pm_instance_variable_or_write_node_create(pm_parser_t *parser, pm_instance_varia static pm_instance_variable_read_node_t * pm_instance_variable_read_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_INSTANCE_VARIABLE); - pm_instance_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_read_node_t); - - *node = (pm_instance_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)), - .name = pm_parser_constant_id_token(parser, token) - }; - return node; + return pm_instance_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token), + pm_parser_constant_id_token(parser, token) + ); } /** @@ -4704,18 +4683,16 @@ pm_instance_variable_read_node_create(pm_parser_t *parser, const pm_token_t *tok */ static pm_instance_variable_write_node_t * pm_instance_variable_write_node_create(pm_parser_t *parser, pm_instance_variable_read_node_t *read_node, pm_token_t *operator, pm_node_t *value) { - pm_instance_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_instance_variable_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_instance_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_INSTANCE_VARIABLE_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(read_node, value)), - .name = read_node->name, - .name_loc = read_node->base.location, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_instance_variable_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(read_node, value), + read_node->name, + read_node->base.location, + value, + TOK2LOC(parser, operator) + ); } /** @@ -4767,16 +4744,15 @@ pm_interpolated_node_append(pm_arena_t *arena, pm_node_t *node, pm_node_list_t * */ static pm_interpolated_regular_expression_node_t * pm_interpolated_regular_expression_node_create(pm_parser_t *parser, const pm_token_t *opening) { - pm_interpolated_regular_expression_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_regular_expression_node_t); - - *node = (pm_interpolated_regular_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_REGULAR_EXPRESSION_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, opening)), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, opening), - .parts = { 0 } - }; - - return node; + return pm_interpolated_regular_expression_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, opening), + TOK2LOC(parser, opening), + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, opening) + ); } static inline void @@ -4918,7 +4894,6 @@ pm_interpolated_string_node_append(pm_arena_t *arena, pm_interpolated_string_nod */ static pm_interpolated_string_node_t * pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_node_list_t *parts, const pm_token_t *closing) { - pm_interpolated_string_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_string_node_t); pm_node_flags_t flags = PM_NODE_FLAG_STATIC_LITERAL; switch (parser->frozen_string_literal) { @@ -4933,12 +4908,15 @@ pm_interpolated_string_node_create(pm_parser_t *parser, const pm_token_t *openin uint32_t start = opening == NULL ? 0 : PM_TOKEN_START(parser, opening); uint32_t end = closing == NULL ? 0 : PM_TOKEN_END(parser, closing); - *node = (pm_interpolated_string_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_STRING_NODE, flags, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .opening_loc = NTOK2LOC(parser, opening), - .closing_loc = NTOK2LOC(parser, closing), - .parts = { 0 } - }; + pm_interpolated_string_node_t *node = pm_interpolated_string_node_new( + parser->arena, + ++parser->node_id, + flags, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NTOK2LOC(parser, opening), + ((pm_node_list_t) { 0 }), + NTOK2LOC(parser, closing) + ); if (parts != NULL) { pm_node_t *part; @@ -4983,17 +4961,18 @@ pm_interpolated_symbol_node_closing_loc_set(const pm_parser_t *parser, pm_interp */ static pm_interpolated_symbol_node_t * pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_node_list_t *parts, const pm_token_t *closing) { - pm_interpolated_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_symbol_node_t); - uint32_t start = opening == NULL ? 0 : PM_TOKEN_START(parser, opening); uint32_t end = closing == NULL ? 0 : PM_TOKEN_END(parser, closing); - *node = (pm_interpolated_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .opening_loc = NTOK2LOC(parser, opening), - .closing_loc = NTOK2LOC(parser, closing), - .parts = { 0 } - }; + pm_interpolated_symbol_node_t *node = pm_interpolated_symbol_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NTOK2LOC(parser, opening), + ((pm_node_list_t) { 0 }), + NTOK2LOC(parser, closing) + ); if (parts != NULL) { pm_node_t *part; @@ -5010,16 +4989,15 @@ pm_interpolated_symbol_node_create(pm_parser_t *parser, const pm_token_t *openin */ static pm_interpolated_x_string_node_t * pm_interpolated_xstring_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *closing) { - pm_interpolated_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_interpolated_x_string_node_t); - - *node = (pm_interpolated_x_string_node_t) { - .base = PM_NODE_INIT(parser, PM_INTERPOLATED_X_STRING_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing), - .parts = { 0 } - }; - - return node; + return pm_interpolated_x_string_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + TOK2LOC(parser, opening), + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, closing) + ); } static inline void @@ -5039,13 +5017,12 @@ pm_interpolated_xstring_node_closing_set(const pm_parser_t *parser, pm_interpola */ static pm_it_local_variable_read_node_t * pm_it_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *name) { - pm_it_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_it_local_variable_read_node_t); - - *node = (pm_it_local_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_IT_LOCAL_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - }; - - return node; + return pm_it_local_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name) + ); } /** @@ -5053,13 +5030,12 @@ pm_it_local_variable_read_node_create(pm_parser_t *parser, const pm_token_t *nam */ static pm_it_parameters_node_t * pm_it_parameters_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *closing) { - pm_it_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_it_parameters_node_t); - - *node = (pm_it_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_IT_PARAMETERS_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - }; - - return node; + return pm_it_parameters_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing) + ); } /** @@ -5067,14 +5043,13 @@ pm_it_parameters_node_create(pm_parser_t *parser, const pm_token_t *opening, con */ static pm_keyword_hash_node_t * pm_keyword_hash_node_create(pm_parser_t *parser) { - pm_keyword_hash_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_hash_node_t); - - *node = (pm_keyword_hash_node_t) { - .base = PM_NODE_INIT(parser, PM_KEYWORD_HASH_NODE, PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS, PM_LOCATION_INIT_UNSET), - .elements = { 0 } - }; - - return node; + return pm_keyword_hash_node_new( + parser->arena, + ++parser->node_id, + PM_KEYWORD_HASH_NODE_FLAGS_SYMBOL_KEYS, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }) + ); } /** @@ -5100,15 +5075,14 @@ pm_keyword_hash_node_elements_append(pm_arena_t *arena, pm_keyword_hash_node_t * */ static pm_required_keyword_parameter_node_t * pm_required_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t *name) { - pm_required_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_keyword_parameter_node_t); - - *node = (pm_required_keyword_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REQUIRED_KEYWORD_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = pm_parser_constant_id_raw(parser, name->start, name->end - 1), - .name_loc = TOK2LOC(parser, name), - }; - - return node; + return pm_required_keyword_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_parser_constant_id_raw(parser, name->start, name->end - 1), + TOK2LOC(parser, name) + ); } /** @@ -5116,16 +5090,15 @@ pm_required_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t */ static pm_optional_keyword_parameter_node_t * pm_optional_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, pm_node_t *value) { - pm_optional_keyword_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_keyword_parameter_node_t); - - *node = (pm_optional_keyword_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_OPTIONAL_KEYWORD_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, name, value)), - .name = pm_parser_constant_id_raw(parser, name->start, name->end - 1), - .name_loc = TOK2LOC(parser, name), - .value = value - }; - - return node; + return pm_optional_keyword_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, name, value), + pm_parser_constant_id_raw(parser, name->start, name->end - 1), + TOK2LOC(parser, name), + value + ); } /** @@ -5133,16 +5106,15 @@ pm_optional_keyword_parameter_node_create(pm_parser_t *parser, const pm_token_t */ static pm_keyword_rest_parameter_node_t * pm_keyword_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, const pm_token_t *name) { - pm_keyword_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_keyword_rest_parameter_node_t); - - *node = (pm_keyword_rest_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_KEYWORD_REST_PARAMETER_NODE, 0, (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name)), - .name = name == NULL ? 0 : pm_parser_constant_id_token(parser, name), - .name_loc = NTOK2LOC(parser, name), - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_keyword_rest_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name), + name == NULL ? 0 : pm_parser_constant_id_token(parser, name), + NTOK2LOC(parser, name), + TOK2LOC(parser, operator) + ); } /** @@ -5158,19 +5130,18 @@ pm_lambda_node_create( pm_node_t *parameters, pm_node_t *body ) { - pm_lambda_node_t *node = PM_NODE_ALLOC(parser, pm_lambda_node_t); - - *node = (pm_lambda_node_t) { - .base = PM_NODE_INIT(parser, PM_LAMBDA_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, operator, closing)), - .locals = *locals, - .operator_loc = TOK2LOC(parser, operator), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing), - .parameters = parameters, - .body = body - }; - - return node; + return pm_lambda_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, operator, closing), + *locals, + TOK2LOC(parser, operator), + TOK2LOC(parser, opening), + TOK2LOC(parser, closing), + parameters, + body + ); } /** @@ -5180,18 +5151,18 @@ static pm_local_variable_and_write_node_t * pm_local_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value, pm_constant_id_t name, uint32_t depth) { assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_IT_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); assert(operator->type == PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL); - pm_local_variable_and_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_and_write_node_t); - - *node = (pm_local_variable_and_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_AND_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name_loc = target->location, - .operator_loc = TOK2LOC(parser, operator), - .value = value, - .name = name, - .depth = depth - }; - return node; + return pm_local_variable_and_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->location, + TOK2LOC(parser, operator), + value, + name, + depth + ); } /** @@ -5199,19 +5170,18 @@ pm_local_variable_and_write_node_create(pm_parser_t *parser, pm_node_t *target, */ static pm_local_variable_operator_write_node_t * pm_local_variable_operator_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value, pm_constant_id_t name, uint32_t depth) { - pm_local_variable_operator_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_operator_write_node_t); - - *node = (pm_local_variable_operator_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OPERATOR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name_loc = target->location, - .binary_operator_loc = TOK2LOC(parser, operator), - .value = value, - .name = name, - .binary_operator = pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), - .depth = depth - }; - - return node; + return pm_local_variable_operator_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->location, + TOK2LOC(parser, operator), + value, + name, + pm_parser_constant_id_raw(parser, operator->start, operator->end - 1), + depth + ); } /** @@ -5221,18 +5191,18 @@ static pm_local_variable_or_write_node_t * pm_local_variable_or_write_node_create(pm_parser_t *parser, pm_node_t *target, const pm_token_t *operator, pm_node_t *value, pm_constant_id_t name, uint32_t depth) { assert(PM_NODE_TYPE_P(target, PM_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_IT_LOCAL_VARIABLE_READ_NODE) || PM_NODE_TYPE_P(target, PM_CALL_NODE)); assert(operator->type == PM_TOKEN_PIPE_PIPE_EQUAL); - pm_local_variable_or_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_or_write_node_t); - - *node = (pm_local_variable_or_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_OR_WRITE_NODE, 0, PM_LOCATION_INIT_NODES(target, value)), - .name_loc = target->location, - .operator_loc = TOK2LOC(parser, operator), - .value = value, - .name = name, - .depth = depth - }; - return node; + return pm_local_variable_or_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(target, value), + target->location, + TOK2LOC(parser, operator), + value, + name, + depth + ); } /** @@ -5242,15 +5212,14 @@ static pm_local_variable_read_node_t * pm_local_variable_read_node_create_constant_id(pm_parser_t *parser, const pm_token_t *name, pm_constant_id_t name_id, uint32_t depth, bool missing) { if (!missing) pm_locals_read(&pm_parser_scope_find(parser, depth)->locals, name_id); - pm_local_variable_read_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_read_node_t); - - *node = (pm_local_variable_read_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .name = name_id, - .depth = depth - }; - - return node; + return pm_local_variable_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + name_id, + depth + ); } /** @@ -5277,19 +5246,17 @@ pm_local_variable_read_node_missing_create(pm_parser_t *parser, const pm_token_t */ static pm_local_variable_write_node_t * pm_local_variable_write_node_create(pm_parser_t *parser, pm_constant_id_t name, uint32_t depth, pm_node_t *value, const pm_location_t *name_loc, const pm_token_t *operator) { - pm_local_variable_write_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_local_variable_write_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_WRITE_NODE, flags, ((pm_location_t) { .start = name_loc->start, .length = PM_NODE_END(value) - name_loc->start })), - .name = name, - .depth = depth, - .value = value, - .name_loc = *name_loc, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_local_variable_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + ((pm_location_t) { .start = name_loc->start, .length = PM_NODE_END(value) - name_loc->start }), + name, + depth, + *name_loc, + value, + TOK2LOC(parser, operator) + ); } /** @@ -5332,15 +5299,15 @@ pm_refute_numbered_parameter(pm_parser_t *parser, uint32_t start, uint32_t lengt static pm_local_variable_target_node_t * pm_local_variable_target_node_create(pm_parser_t *parser, const pm_location_t *location, pm_constant_id_t name, uint32_t depth) { pm_refute_numbered_parameter(parser, location->start, location->length); - pm_local_variable_target_node_t *node = PM_NODE_ALLOC(parser, pm_local_variable_target_node_t); - - *node = (pm_local_variable_target_node_t) { - .base = PM_NODE_INIT(parser, PM_LOCAL_VARIABLE_TARGET_NODE, 0, ((pm_location_t) { .start = location->start, .length = location->length })), - .name = name, - .depth = depth - }; - return node; + return pm_local_variable_target_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = location->start, .length = location->length }), + name, + depth + ); } /** @@ -5350,16 +5317,15 @@ static pm_match_predicate_node_t * pm_match_predicate_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t *pattern, const pm_token_t *operator) { pm_assert_value_expression(parser, value); - pm_match_predicate_node_t *node = PM_NODE_ALLOC(parser, pm_match_predicate_node_t); - - *node = (pm_match_predicate_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_PREDICATE_NODE, 0, PM_LOCATION_INIT_NODES(value, pattern)), - .value = value, - .pattern = pattern, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_match_predicate_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(value, pattern), + value, + pattern, + TOK2LOC(parser, operator) + ); } /** @@ -5369,16 +5335,15 @@ static pm_match_required_node_t * pm_match_required_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t *pattern, const pm_token_t *operator) { pm_assert_value_expression(parser, value); - pm_match_required_node_t *node = PM_NODE_ALLOC(parser, pm_match_required_node_t); - - *node = (pm_match_required_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_REQUIRED_NODE, 0, PM_LOCATION_INIT_NODES(value, pattern)), - .value = value, - .pattern = pattern, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_match_required_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(value, pattern), + value, + pattern, + TOK2LOC(parser, operator) + ); } /** @@ -5386,15 +5351,14 @@ pm_match_required_node_create(pm_parser_t *parser, pm_node_t *value, pm_node_t * */ static pm_match_write_node_t * pm_match_write_node_create(pm_parser_t *parser, pm_call_node_t *call) { - pm_match_write_node_t *node = PM_NODE_ALLOC(parser, pm_match_write_node_t); - - *node = (pm_match_write_node_t) { - .base = PM_NODE_INIT(parser, PM_MATCH_WRITE_NODE, 0, PM_LOCATION_INIT_NODE(call)), - .call = call, - .targets = { 0 } - }; - - return node; + return pm_match_write_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODE(call), + call, + ((pm_node_list_t) { 0 }) + ); } /** @@ -5402,19 +5366,18 @@ pm_match_write_node_create(pm_parser_t *parser, pm_call_node_t *call) { */ static pm_module_node_t * pm_module_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_token_t *module_keyword, pm_node_t *constant_path, const pm_token_t *name, pm_node_t *body, const pm_token_t *end_keyword) { - pm_module_node_t *node = PM_NODE_ALLOC(parser, pm_module_node_t); - - *node = (pm_module_node_t) { - .base = PM_NODE_INIT(parser, PM_MODULE_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, module_keyword, end_keyword)), - .locals = (locals == NULL ? ((pm_constant_id_list_t) { .ids = NULL, .size = 0, .capacity = 0 }) : *locals), - .module_keyword_loc = TOK2LOC(parser, module_keyword), - .constant_path = constant_path, - .body = body, - .end_keyword_loc = TOK2LOC(parser, end_keyword), - .name = pm_parser_constant_id_token(parser, name) - }; - - return node; + return pm_module_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, module_keyword, end_keyword), + (locals == NULL ? ((pm_constant_id_list_t) { .ids = NULL, .size = 0, .capacity = 0 }) : *locals), + TOK2LOC(parser, module_keyword), + constant_path, + body, + TOK2LOC(parser, end_keyword), + pm_parser_constant_id_token(parser, name) + ); } /** @@ -5422,18 +5385,17 @@ pm_module_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const */ static pm_multi_target_node_t * pm_multi_target_node_create(pm_parser_t *parser) { - pm_multi_target_node_t *node = PM_NODE_ALLOC(parser, pm_multi_target_node_t); - - *node = (pm_multi_target_node_t) { - .base = PM_NODE_INIT(parser, PM_MULTI_TARGET_NODE, 0, PM_LOCATION_INIT_UNSET), - .lefts = { 0 }, - .rest = NULL, - .rights = { 0 }, - .lparen_loc = { 0 }, - .rparen_loc = { 0 } - }; - - return node; + return pm_multi_target_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }), + NULL, + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }) + ); } /** @@ -5494,24 +5456,21 @@ pm_multi_target_node_closing_set(const pm_parser_t *parser, pm_multi_target_node */ static pm_multi_write_node_t * pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, const pm_token_t *operator, pm_node_t *value) { - pm_multi_write_node_t *node = PM_NODE_ALLOC(parser, pm_multi_write_node_t); - pm_node_flags_t flags = pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY); - - *node = (pm_multi_write_node_t) { - .base = PM_NODE_INIT(parser, PM_MULTI_WRITE_NODE, flags, PM_LOCATION_INIT_NODES(target, value)), - .lefts = target->lefts, - .rest = target->rest, - .rights = target->rights, - .lparen_loc = target->lparen_loc, - .rparen_loc = target->rparen_loc, - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - // The target is no longer necessary because we've reused its children. - // It is arena-allocated so no explicit free is needed. - - return node; + /* The target is no longer necessary because we have reused its children. It + * is arena-allocated so no explicit free is needed. */ + return pm_multi_write_node_new( + parser->arena, + ++parser->node_id, + pm_implicit_array_write_flags(value, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY), + PM_LOCATION_INIT_NODES(target, value), + target->lefts, + target->rest, + target->rights, + target->lparen_loc, + target->rparen_loc, + TOK2LOC(parser, operator), + value + ); } /** @@ -5520,15 +5479,15 @@ pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, static pm_next_node_t * pm_next_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments_node_t *arguments) { assert(keyword->type == PM_TOKEN_KEYWORD_NEXT); - pm_next_node_t *node = PM_NODE_ALLOC(parser, pm_next_node_t); - *node = (pm_next_node_t) { - .base = PM_NODE_INIT(parser, PM_NEXT_NODE, 0, (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments)), - .keyword_loc = TOK2LOC(parser, keyword), - .arguments = arguments - }; - - return node; + return pm_next_node_new( + parser->arena, + ++parser->node_id, + 0, + (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments), + arguments, + TOK2LOC(parser, keyword) + ); } /** @@ -5537,13 +5496,13 @@ pm_next_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments static pm_nil_node_t * pm_nil_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_NIL); - pm_nil_node_t *node = PM_NODE_ALLOC(parser, pm_nil_node_t); - - *node = (pm_nil_node_t) { - .base = PM_NODE_INIT(parser, PM_NIL_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_nil_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -5553,15 +5512,15 @@ static pm_no_block_parameter_node_t * pm_no_block_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, const pm_token_t *keyword) { assert(operator->type == PM_TOKEN_AMPERSAND || operator->type == PM_TOKEN_UAMPERSAND); assert(keyword->type == PM_TOKEN_KEYWORD_NIL); - pm_no_block_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_no_block_parameter_node_t); - - *node = (pm_no_block_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_NO_BLOCK_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, operator, keyword)), - .operator_loc = TOK2LOC(parser, operator), - .keyword_loc = TOK2LOC(parser, keyword) - }; - return node; + return pm_no_block_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, operator, keyword), + TOK2LOC(parser, operator), + TOK2LOC(parser, keyword) + ); } /** @@ -5571,15 +5530,15 @@ static pm_no_keywords_parameter_node_t * pm_no_keywords_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, const pm_token_t *keyword) { assert(operator->type == PM_TOKEN_USTAR_STAR || operator->type == PM_TOKEN_STAR_STAR); assert(keyword->type == PM_TOKEN_KEYWORD_NIL); - pm_no_keywords_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_no_keywords_parameter_node_t); - - *node = (pm_no_keywords_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_NO_KEYWORDS_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, operator, keyword)), - .operator_loc = TOK2LOC(parser, operator), - .keyword_loc = TOK2LOC(parser, keyword) - }; - return node; + return pm_no_keywords_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, operator, keyword), + TOK2LOC(parser, operator), + TOK2LOC(parser, keyword) + ); } /** @@ -5587,14 +5546,13 @@ pm_no_keywords_parameter_node_create(pm_parser_t *parser, const pm_token_t *oper */ static pm_numbered_parameters_node_t * pm_numbered_parameters_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *closing, uint8_t maximum) { - pm_numbered_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_parameters_node_t); - - *node = (pm_numbered_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_NUMBERED_PARAMETERS_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .maximum = maximum - }; - - return node; + return pm_numbered_parameters_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + maximum + ); } /** @@ -5652,14 +5610,14 @@ pm_numbered_reference_read_node_number(pm_parser_t *parser, const pm_token_t *to static pm_numbered_reference_read_node_t * pm_numbered_reference_read_node_create(pm_parser_t *parser, const pm_token_t *name) { assert(name->type == PM_TOKEN_NUMBERED_REFERENCE); - pm_numbered_reference_read_node_t *node = PM_NODE_ALLOC(parser, pm_numbered_reference_read_node_t); - - *node = (pm_numbered_reference_read_node_t) { - .base = PM_NODE_INIT(parser, PM_NUMBERED_REFERENCE_READ_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, name)), - .number = pm_numbered_reference_read_node_number(parser, name) - }; - return node; + return pm_numbered_reference_read_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, name), + pm_numbered_reference_read_node_number(parser, name) + ); } /** @@ -5667,17 +5625,16 @@ pm_numbered_reference_read_node_create(pm_parser_t *parser, const pm_token_t *na */ static pm_optional_parameter_node_t * pm_optional_parameter_node_create(pm_parser_t *parser, const pm_token_t *name, const pm_token_t *operator, pm_node_t *value) { - pm_optional_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_optional_parameter_node_t); - - *node = (pm_optional_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_OPTIONAL_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, name, value)), - .name = pm_parser_constant_id_token(parser, name), - .name_loc = TOK2LOC(parser, name), - .operator_loc = TOK2LOC(parser, operator), - .value = value - }; - - return node; + return pm_optional_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, name, value), + pm_parser_constant_id_token(parser, name), + TOK2LOC(parser, name), + TOK2LOC(parser, operator), + value + ); } /** @@ -5687,16 +5644,15 @@ static pm_or_node_t * pm_or_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operator, pm_node_t *right) { pm_assert_value_expression(parser, left); - pm_or_node_t *node = PM_NODE_ALLOC(parser, pm_or_node_t); - - *node = (pm_or_node_t) { - .base = PM_NODE_INIT(parser, PM_OR_NODE, 0, PM_LOCATION_INIT_NODES(left, right)), - .left = left, - .right = right, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_or_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(left, right), + left, + right, + TOK2LOC(parser, operator) + ); } /** @@ -5704,20 +5660,19 @@ pm_or_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operat */ static pm_parameters_node_t * pm_parameters_node_create(pm_parser_t *parser) { - pm_parameters_node_t *node = PM_NODE_ALLOC(parser, pm_parameters_node_t); - - *node = (pm_parameters_node_t) { - .base = PM_NODE_INIT(parser, PM_PARAMETERS_NODE, 0, PM_LOCATION_INIT_UNSET), - .rest = NULL, - .keyword_rest = NULL, - .block = NULL, - .requireds = { 0 }, - .optionals = { 0 }, - .posts = { 0 }, - .keywords = { 0 } - }; - - return node; + return pm_parameters_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }), + ((pm_node_list_t) { 0 }), + NULL, + ((pm_node_list_t) { 0 }), + ((pm_node_list_t) { 0 }), + NULL, + NULL + ); } /** @@ -5804,15 +5759,14 @@ pm_parameters_node_block_set(pm_parameters_node_t *params, pm_node_t *param) { */ static pm_program_node_t * pm_program_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, pm_statements_node_t *statements) { - pm_program_node_t *node = PM_NODE_ALLOC(parser, pm_program_node_t); - - *node = (pm_program_node_t) { - .base = PM_NODE_INIT(parser, PM_PROGRAM_NODE, 0, PM_LOCATION_INIT_NODE(statements)), - .locals = *locals, - .statements = statements - }; - - return node; + return pm_program_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODE(statements), + *locals, + statements + ); } /** @@ -5820,16 +5774,15 @@ pm_program_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, pm_st */ static pm_parentheses_node_t * pm_parentheses_node_create(pm_parser_t *parser, const pm_token_t *opening, pm_node_t *body, const pm_token_t *closing, pm_node_flags_t flags) { - pm_parentheses_node_t *node = PM_NODE_ALLOC(parser, pm_parentheses_node_t); - - *node = (pm_parentheses_node_t) { - .base = PM_NODE_INIT(parser, PM_PARENTHESES_NODE, flags, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .body = body, - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing) - }; - - return node; + return pm_parentheses_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + body, + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -5837,17 +5790,16 @@ pm_parentheses_node_create(pm_parser_t *parser, const pm_token_t *opening, pm_no */ static pm_pinned_expression_node_t * pm_pinned_expression_node_create(pm_parser_t *parser, pm_node_t *expression, const pm_token_t *operator, const pm_token_t *lparen, const pm_token_t *rparen) { - pm_pinned_expression_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_expression_node_t); - - *node = (pm_pinned_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_PINNED_EXPRESSION_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, operator, rparen)), - .expression = expression, - .operator_loc = TOK2LOC(parser, operator), - .lparen_loc = TOK2LOC(parser, lparen), - .rparen_loc = TOK2LOC(parser, rparen) - }; - - return node; + return pm_pinned_expression_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, operator, rparen), + expression, + TOK2LOC(parser, operator), + TOK2LOC(parser, lparen), + TOK2LOC(parser, rparen) + ); } /** @@ -5855,15 +5807,14 @@ pm_pinned_expression_node_create(pm_parser_t *parser, pm_node_t *expression, con */ static pm_pinned_variable_node_t * pm_pinned_variable_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t *variable) { - pm_pinned_variable_node_t *node = PM_NODE_ALLOC(parser, pm_pinned_variable_node_t); - - *node = (pm_pinned_variable_node_t) { - .base = PM_NODE_INIT(parser, PM_PINNED_VARIABLE_NODE, 0, PM_LOCATION_INIT_TOKEN_NODE(parser, operator, variable)), - .variable = variable, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_pinned_variable_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN_NODE(parser, operator, variable), + variable, + TOK2LOC(parser, operator) + ); } /** @@ -5871,17 +5822,16 @@ pm_pinned_variable_node_create(pm_parser_t *parser, const pm_token_t *operator, */ static pm_post_execution_node_t * pm_post_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *opening, pm_statements_node_t *statements, const pm_token_t *closing) { - pm_post_execution_node_t *node = PM_NODE_ALLOC(parser, pm_post_execution_node_t); - - *node = (pm_post_execution_node_t) { - .base = PM_NODE_INIT(parser, PM_POST_EXECUTION_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, keyword, closing)), - .statements = statements, - .keyword_loc = TOK2LOC(parser, keyword), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing) - }; - - return node; + return pm_post_execution_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, keyword, closing), + statements, + TOK2LOC(parser, keyword), + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -5889,17 +5839,16 @@ pm_post_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, co */ static pm_pre_execution_node_t * pm_pre_execution_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *opening, pm_statements_node_t *statements, const pm_token_t *closing) { - pm_pre_execution_node_t *node = PM_NODE_ALLOC(parser, pm_pre_execution_node_t); - - *node = (pm_pre_execution_node_t) { - .base = PM_NODE_INIT(parser, PM_PRE_EXECUTION_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, keyword, closing)), - .statements = statements, - .keyword_loc = TOK2LOC(parser, keyword), - .opening_loc = TOK2LOC(parser, opening), - .closing_loc = TOK2LOC(parser, closing) - }; - - return node; + return pm_pre_execution_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, keyword, closing), + statements, + TOK2LOC(parser, keyword), + TOK2LOC(parser, opening), + TOK2LOC(parser, closing) + ); } /** @@ -5909,8 +5858,6 @@ static pm_range_node_t * pm_range_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *operator, pm_node_t *right) { pm_assert_value_expression(parser, left); pm_assert_value_expression(parser, right); - - pm_range_node_t *node = PM_NODE_ALLOC(parser, pm_range_node_t); pm_node_flags_t flags = 0; // Indicate that this node is an exclusive range if the operator is `...`. @@ -5931,14 +5878,15 @@ pm_range_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *ope uint32_t start = left == NULL ? PM_TOKEN_START(parser, operator) : PM_NODE_START(left); uint32_t end = right == NULL ? PM_TOKEN_END(parser, operator) : PM_NODE_END(right); - *node = (pm_range_node_t) { - .base = PM_NODE_INIT(parser, PM_RANGE_NODE, flags, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .left = left, - .right = right, - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_range_node_new( + parser->arena, + ++parser->node_id, + flags, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + left, + right, + TOK2LOC(parser, operator) + ); } /** @@ -5947,13 +5895,13 @@ pm_range_node_create(pm_parser_t *parser, pm_node_t *left, const pm_token_t *ope static pm_redo_node_t * pm_redo_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_REDO); - pm_redo_node_t *node = PM_NODE_ALLOC(parser, pm_redo_node_t); - *node = (pm_redo_node_t) { - .base = PM_NODE_INIT(parser, PM_REDO_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - - return node; + return pm_redo_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -5962,18 +5910,16 @@ pm_redo_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_regular_expression_node_t * pm_regular_expression_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing, const pm_string_t *unescaped) { - pm_regular_expression_node_t *node = PM_NODE_ALLOC(parser, pm_regular_expression_node_t); - pm_node_flags_t flags = pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL; - - *node = (pm_regular_expression_node_t) { - .base = PM_NODE_INIT(parser, PM_REGULAR_EXPRESSION_NODE, flags, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .opening_loc = TOK2LOC(parser, opening), - .content_loc = TOK2LOC(parser, content), - .closing_loc = TOK2LOC(parser, closing), - .unescaped = *unescaped - }; - - return node; + return pm_regular_expression_node_new( + parser->arena, + ++parser->node_id, + pm_regular_expression_flags_create(parser, closing) | PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + TOK2LOC(parser, opening), + TOK2LOC(parser, content), + TOK2LOC(parser, closing), + *unescaped + ); } /** @@ -5989,14 +5935,13 @@ pm_regular_expression_node_create(pm_parser_t *parser, const pm_token_t *opening */ static pm_required_parameter_node_t * pm_required_parameter_node_create(pm_parser_t *parser, const pm_token_t *token) { - pm_required_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_required_parameter_node_t); - - *node = (pm_required_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REQUIRED_PARAMETER_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)), - .name = pm_parser_constant_id_token(parser, token) - }; - - return node; + return pm_required_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token), + pm_parser_constant_id_token(parser, token) + ); } /** @@ -6004,16 +5949,15 @@ pm_required_parameter_node_create(pm_parser_t *parser, const pm_token_t *token) */ static pm_rescue_modifier_node_t * pm_rescue_modifier_node_create(pm_parser_t *parser, pm_node_t *expression, const pm_token_t *keyword, pm_node_t *rescue_expression) { - pm_rescue_modifier_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_modifier_node_t); - - *node = (pm_rescue_modifier_node_t) { - .base = PM_NODE_INIT(parser, PM_RESCUE_MODIFIER_NODE, 0, PM_LOCATION_INIT_NODES(expression, rescue_expression)), - .expression = expression, - .keyword_loc = TOK2LOC(parser, keyword), - .rescue_expression = rescue_expression - }; - - return node; + return pm_rescue_modifier_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_NODES(expression, rescue_expression), + expression, + TOK2LOC(parser, keyword), + rescue_expression + ); } /** @@ -6021,20 +5965,19 @@ pm_rescue_modifier_node_create(pm_parser_t *parser, pm_node_t *expression, const */ static pm_rescue_node_t * pm_rescue_node_create(pm_parser_t *parser, const pm_token_t *keyword) { - pm_rescue_node_t *node = PM_NODE_ALLOC(parser, pm_rescue_node_t); - - *node = (pm_rescue_node_t) { - .base = PM_NODE_INIT(parser, PM_RESCUE_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, keyword)), - .keyword_loc = TOK2LOC(parser, keyword), - .operator_loc = { 0 }, - .then_keyword_loc = { 0 }, - .reference = NULL, - .statements = NULL, - .subsequent = NULL, - .exceptions = { 0 } - }; - - return node; + return pm_rescue_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, keyword), + TOK2LOC(parser, keyword), + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + NULL, + ((pm_location_t) { 0 }), + NULL, + NULL + ); } static inline void @@ -6085,16 +6028,15 @@ pm_rescue_node_exceptions_append(pm_arena_t *arena, pm_rescue_node_t *node, pm_n */ static pm_rest_parameter_node_t * pm_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, const pm_token_t *name) { - pm_rest_parameter_node_t *node = PM_NODE_ALLOC(parser, pm_rest_parameter_node_t); - - *node = (pm_rest_parameter_node_t) { - .base = PM_NODE_INIT(parser, PM_REST_PARAMETER_NODE, 0, (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name)), - .name = name == NULL ? 0 : pm_parser_constant_id_token(parser, name), - .name_loc = NTOK2LOC(parser, name), - .operator_loc = TOK2LOC(parser, operator) - }; - - return node; + return pm_rest_parameter_node_new( + parser->arena, + ++parser->node_id, + 0, + (name == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKENS(parser, operator, name), + name == NULL ? 0 : pm_parser_constant_id_token(parser, name), + NTOK2LOC(parser, name), + TOK2LOC(parser, operator) + ); } /** @@ -6103,13 +6045,13 @@ pm_rest_parameter_node_create(pm_parser_t *parser, const pm_token_t *operator, c static pm_retry_node_t * pm_retry_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_RETRY); - pm_retry_node_t *node = PM_NODE_ALLOC(parser, pm_retry_node_t); - - *node = (pm_retry_node_t) { - .base = PM_NODE_INIT(parser, PM_RETRY_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_retry_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -6117,15 +6059,14 @@ pm_retry_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_return_node_t * pm_return_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments_node_t *arguments) { - pm_return_node_t *node = PM_NODE_ALLOC(parser, pm_return_node_t); - - *node = (pm_return_node_t) { - .base = PM_NODE_INIT(parser, PM_RETURN_NODE, 0, (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments)), - .keyword_loc = TOK2LOC(parser, keyword), - .arguments = arguments - }; - - return node; + return pm_return_node_new( + parser->arena, + ++parser->node_id, + 0, + (arguments == NULL) ? PM_LOCATION_INIT_TOKEN(parser, keyword) : PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, arguments), + TOK2LOC(parser, keyword), + arguments + ); } /** @@ -6134,13 +6075,13 @@ pm_return_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_argumen static pm_self_node_t * pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_SELF); - pm_self_node_t *node = PM_NODE_ALLOC(parser, pm_self_node_t); - *node = (pm_self_node_t) { - .base = PM_NODE_INIT(parser, PM_SELF_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - - return node; + return pm_self_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -6148,14 +6089,13 @@ pm_self_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_shareable_constant_node_t * pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shareable_constant_value_t value) { - pm_shareable_constant_node_t *node = PM_NODE_ALLOC(parser, pm_shareable_constant_node_t); - - *node = (pm_shareable_constant_node_t) { - .base = PM_NODE_INIT(parser, PM_SHAREABLE_CONSTANT_NODE, (pm_node_flags_t) value, PM_LOCATION_INIT_NODE(write)), - .write = write - }; - - return node; + return pm_shareable_constant_node_new( + parser->arena, + ++parser->node_id, + (pm_node_flags_t) value, + PM_LOCATION_INIT_NODE(write), + write + ); } /** @@ -6163,19 +6103,18 @@ pm_shareable_constant_node_create(pm_parser_t *parser, pm_node_t *write, pm_shar */ static pm_singleton_class_node_t * pm_singleton_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *locals, const pm_token_t *class_keyword, const pm_token_t *operator, pm_node_t *expression, pm_node_t *body, const pm_token_t *end_keyword) { - pm_singleton_class_node_t *node = PM_NODE_ALLOC(parser, pm_singleton_class_node_t); - - *node = (pm_singleton_class_node_t) { - .base = PM_NODE_INIT(parser, PM_SINGLETON_CLASS_NODE, 0, PM_LOCATION_INIT_TOKENS(parser, class_keyword, end_keyword)), - .locals = *locals, - .class_keyword_loc = TOK2LOC(parser, class_keyword), - .operator_loc = TOK2LOC(parser, operator), - .expression = expression, - .body = body, - .end_keyword_loc = TOK2LOC(parser, end_keyword) - }; - - return node; + return pm_singleton_class_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKENS(parser, class_keyword, end_keyword), + *locals, + TOK2LOC(parser, class_keyword), + TOK2LOC(parser, operator), + expression, + body, + TOK2LOC(parser, end_keyword) + ); } /** @@ -6184,13 +6123,13 @@ pm_singleton_class_node_create(pm_parser_t *parser, pm_constant_id_list_t *local static pm_source_encoding_node_t * pm_source_encoding_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD___ENCODING__); - pm_source_encoding_node_t *node = PM_NODE_ALLOC(parser, pm_source_encoding_node_t); - *node = (pm_source_encoding_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_ENCODING_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - - return node; + return pm_source_encoding_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -6198,7 +6137,6 @@ pm_source_encoding_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_source_file_node_t* pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) { - pm_source_file_node_t *node = PM_NODE_ALLOC(parser, pm_source_file_node_t); assert(file_keyword->type == PM_TOKEN_KEYWORD___FILE__); pm_node_flags_t flags = 0; @@ -6212,12 +6150,13 @@ pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) break; } - *node = (pm_source_file_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_FILE_NODE, flags, PM_LOCATION_INIT_TOKEN(parser, file_keyword)), - .filepath = parser->filepath - }; - - return node; + return pm_source_file_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_TOKEN(parser, file_keyword), + parser->filepath + ); } /** @@ -6226,13 +6165,13 @@ pm_source_file_node_create(pm_parser_t *parser, const pm_token_t *file_keyword) static pm_source_line_node_t * pm_source_line_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD___LINE__); - pm_source_line_node_t *node = PM_NODE_ALLOC(parser, pm_source_line_node_t); - - *node = (pm_source_line_node_t) { - .base = PM_NODE_INIT(parser, PM_SOURCE_LINE_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_source_line_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -6240,15 +6179,14 @@ pm_source_line_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_splat_node_t * pm_splat_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t *expression) { - pm_splat_node_t *node = PM_NODE_ALLOC(parser, pm_splat_node_t); - - *node = (pm_splat_node_t) { - .base = PM_NODE_INIT(parser, PM_SPLAT_NODE, 0, (expression == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, expression)), - .operator_loc = TOK2LOC(parser, operator), - .expression = expression - }; - - return node; + return pm_splat_node_new( + parser->arena, + ++parser->node_id, + 0, + (expression == NULL) ? PM_LOCATION_INIT_TOKEN(parser, operator) : PM_LOCATION_INIT_TOKEN_NODE(parser, operator, expression), + TOK2LOC(parser, operator), + expression + ); } /** @@ -6256,14 +6194,13 @@ pm_splat_node_create(pm_parser_t *parser, const pm_token_t *operator, pm_node_t */ static pm_statements_node_t * pm_statements_node_create(pm_parser_t *parser) { - pm_statements_node_t *node = PM_NODE_ALLOC(parser, pm_statements_node_t); - - *node = (pm_statements_node_t) { - .base = PM_NODE_INIT(parser, PM_STATEMENTS_NODE, 0, PM_LOCATION_INIT_UNSET), - .body = { 0 } - }; - - return node; + return pm_statements_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + ((pm_node_list_t) { 0 }) + ); } /** @@ -6331,7 +6268,6 @@ pm_statements_node_body_prepend(pm_arena_t *arena, pm_statements_node_t *node, p */ static inline pm_string_node_t * pm_string_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing, const pm_string_t *string) { - pm_string_node_t *node = PM_NODE_ALLOC(parser, pm_string_node_t); pm_node_flags_t flags = 0; switch (parser->frozen_string_literal) { @@ -6346,15 +6282,16 @@ pm_string_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, uint32_t start = PM_TOKEN_START(parser, opening == NULL ? content : opening); uint32_t end = PM_TOKEN_END(parser, closing == NULL ? content : closing); - *node = (pm_string_node_t) { - .base = PM_NODE_INIT(parser, PM_STRING_NODE, flags, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .opening_loc = NTOK2LOC(parser, opening), - .content_loc = TOK2LOC(parser, content), - .closing_loc = NTOK2LOC(parser, closing), - .unescaped = *string - }; - - return node; + return pm_string_node_new( + parser->arena, + ++parser->node_id, + flags, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NTOK2LOC(parser, opening), + TOK2LOC(parser, content), + NTOK2LOC(parser, closing), + *string + ); } /** @@ -6382,21 +6319,21 @@ pm_string_node_create_current_string(pm_parser_t *parser, const pm_token_t *open static pm_super_node_t * pm_super_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_arguments_t *arguments) { assert(keyword->type == PM_TOKEN_KEYWORD_SUPER); - pm_super_node_t *node = PM_NODE_ALLOC(parser, pm_super_node_t); const pm_location_t *end = pm_arguments_end(arguments); assert(end != NULL && "unreachable"); - *node = (pm_super_node_t) { - .base = PM_NODE_INIT(parser, PM_SUPER_NODE, 0, ((pm_location_t) { .start = PM_TOKEN_START(parser, keyword), .length = PM_LOCATION_END(end) - PM_TOKEN_START(parser, keyword) })), - .keyword_loc = TOK2LOC(parser, keyword), - .lparen_loc = arguments->opening_loc, - .arguments = arguments->arguments, - .rparen_loc = arguments->closing_loc, - .block = arguments->block - }; - - return node; + return pm_super_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = PM_TOKEN_START(parser, keyword), .length = PM_LOCATION_END(end) - PM_TOKEN_START(parser, keyword) }), + TOK2LOC(parser, keyword), + arguments->opening_loc, + arguments->arguments, + arguments->closing_loc, + arguments->block + ); } /** @@ -6615,20 +6552,19 @@ parse_and_validate_regular_expression_encoding(pm_parser_t *parser, const pm_str */ static pm_symbol_node_t * pm_symbol_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *value, const pm_token_t *closing, const pm_string_t *unescaped, pm_node_flags_t flags) { - pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); - uint32_t start = opening == NULL ? PM_TOKEN_START(parser, value) : PM_TOKEN_START(parser, opening); uint32_t end = closing == NULL ? PM_TOKEN_END(parser, value) : PM_TOKEN_END(parser, closing); - *node = (pm_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | flags, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .opening_loc = NTOK2LOC(parser, opening), - .value_loc = NTOK2LOC(parser, value), - .closing_loc = NTOK2LOC(parser, closing), - .unescaped = *unescaped - }; - - return node; + return pm_symbol_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL | flags, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + NTOK2LOC(parser, opening), + NTOK2LOC(parser, value), + NTOK2LOC(parser, closing), + *unescaped + ); } /** @@ -6672,13 +6608,16 @@ pm_symbol_node_label_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_symbol_node_t * pm_symbol_node_synthesized_create(pm_parser_t *parser, const char *content) { - pm_symbol_node_t *node = PM_NODE_ALLOC(parser, pm_symbol_node_t); - - *node = (pm_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING, PM_LOCATION_INIT_UNSET), - .value_loc = { 0 }, - .unescaped = { 0 } - }; + pm_symbol_node_t *node = pm_symbol_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL | PM_SYMBOL_FLAGS_FORCED_US_ASCII_ENCODING, + PM_LOCATION_INIT_UNSET, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_string_t) { 0 }) + ); pm_string_constant_init(&node->unescaped, content, strlen(content)); return node; @@ -6718,15 +6657,16 @@ pm_symbol_node_label_p(const pm_parser_t *parser, const pm_node_t *node) { */ static pm_symbol_node_t * pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const pm_token_t *opening, const pm_token_t *closing) { - pm_symbol_node_t *new_node = PM_NODE_ALLOC(parser, pm_symbol_node_t); - - *new_node = (pm_symbol_node_t) { - .base = PM_NODE_INIT(parser, PM_SYMBOL_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .opening_loc = TOK2LOC(parser, opening), - .value_loc = node->content_loc, - .closing_loc = TOK2LOC(parser, closing), - .unescaped = node->unescaped - }; + pm_symbol_node_t *new_node = pm_symbol_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + TOK2LOC(parser, opening), + node->content_loc, + TOK2LOC(parser, closing), + node->unescaped + ); pm_token_t content = { .type = PM_TOKEN_IDENTIFIER, @@ -6736,8 +6676,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const pm_node_flag_set(UP(new_node), parse_symbol_encoding(parser, &content, &node->unescaped, true)); - // The old node is arena-allocated so no explicit free is needed. - + /* The old node is arena-allocated so no explicit free is needed. */ return new_node; } @@ -6746,7 +6685,6 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const */ static pm_string_node_t * pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { - pm_string_node_t *new_node = PM_NODE_ALLOC(parser, pm_string_node_t); pm_node_flags_t flags = 0; switch (parser->frozen_string_literal) { @@ -6758,16 +6696,18 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { break; } - *new_node = (pm_string_node_t) { - .base = PM_NODE_INIT(parser, PM_STRING_NODE, flags, PM_LOCATION_INIT_NODE(node)), - .opening_loc = node->opening_loc, - .content_loc = node->value_loc, - .closing_loc = node->closing_loc, - .unescaped = node->unescaped - }; - - // The old node is arena-allocated so no explicit free is needed. + pm_string_node_t *new_node = pm_string_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_NODE(node), + node->opening_loc, + node->value_loc, + node->closing_loc, + node->unescaped + ); + /* The old node is arena-allocated so no explicit free is needed. */ return new_node; } @@ -6777,13 +6717,13 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { static pm_true_node_t * pm_true_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_TRUE); - pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); - - *node = (pm_true_node_t) { - .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_TOKEN(parser, token)) - }; - return node; + return pm_true_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_TOKEN(parser, token) + ); } /** @@ -6791,13 +6731,12 @@ pm_true_node_create(pm_parser_t *parser, const pm_token_t *token) { */ static pm_true_node_t * pm_true_node_synthesized_create(pm_parser_t *parser) { - pm_true_node_t *node = PM_NODE_ALLOC(parser, pm_true_node_t); - - *node = (pm_true_node_t) { - .base = PM_NODE_INIT(parser, PM_TRUE_NODE, PM_NODE_FLAG_STATIC_LITERAL, PM_LOCATION_INIT_UNSET) - }; - - return node; + return pm_true_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_STATIC_LITERAL, + PM_LOCATION_INIT_UNSET + ); } /** @@ -6806,15 +6745,15 @@ pm_true_node_synthesized_create(pm_parser_t *parser) { static pm_undef_node_t * pm_undef_node_create(pm_parser_t *parser, const pm_token_t *token) { assert(token->type == PM_TOKEN_KEYWORD_UNDEF); - pm_undef_node_t *node = PM_NODE_ALLOC(parser, pm_undef_node_t); - *node = (pm_undef_node_t) { - .base = PM_NODE_INIT(parser, PM_UNDEF_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, token)), - .keyword_loc = TOK2LOC(parser, token), - .names = { 0 } - }; - - return node; + return pm_undef_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, token), + ((pm_node_list_t) { 0 }), + TOK2LOC(parser, token) + ); } /** @@ -6832,21 +6771,20 @@ pm_undef_node_append(pm_arena_t *arena, pm_undef_node_t *node, pm_node_t *name) static pm_unless_node_t * pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, const pm_token_t *then_keyword, pm_statements_node_t *statements) { pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - - pm_unless_node_t *node = PM_NODE_ALLOC(parser, pm_unless_node_t); pm_node_t *end = statements == NULL ? predicate : UP(statements); - *node = (pm_unless_node_t) { - .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, end)), - .keyword_loc = TOK2LOC(parser, keyword), - .predicate = predicate, - .then_keyword_loc = NTOK2LOC(parser, then_keyword), - .statements = statements, - .else_clause = NULL, - .end_keyword_loc = { 0 } - }; - - return node; + return pm_unless_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_NEWLINE, + PM_LOCATION_INIT_TOKEN_NODE(parser, keyword, end), + TOK2LOC(parser, keyword), + predicate, + NTOK2LOC(parser, then_keyword), + statements, + NULL, + ((pm_location_t) { 0 }) + ); } /** @@ -6855,22 +6793,22 @@ pm_unless_node_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t static pm_unless_node_t * pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_token_t *unless_keyword, pm_node_t *predicate) { pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - pm_unless_node_t *node = PM_NODE_ALLOC(parser, pm_unless_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); pm_statements_node_body_append(parser, statements, statement, true); - *node = (pm_unless_node_t) { - .base = PM_NODE_INIT(parser, PM_UNLESS_NODE, PM_NODE_FLAG_NEWLINE, PM_LOCATION_INIT_NODES(statement, predicate)), - .keyword_loc = TOK2LOC(parser, unless_keyword), - .predicate = predicate, - .then_keyword_loc = { 0 }, - .statements = statements, - .else_clause = NULL, - .end_keyword_loc = { 0 } - }; - - return node; + return pm_unless_node_new( + parser->arena, + ++parser->node_id, + PM_NODE_FLAG_NEWLINE, + PM_LOCATION_INIT_NODES(statement, predicate), + TOK2LOC(parser, unless_keyword), + predicate, + ((pm_location_t) { 0 }), + statements, + NULL, + ((pm_location_t) { 0 }) + ); } static inline void @@ -6907,19 +6845,19 @@ pm_loop_modifier_block_exits(pm_parser_t *parser, pm_statements_node_t *statemen */ static pm_until_node_t * pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *do_keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { - pm_until_node_t *node = PM_NODE_ALLOC(parser, pm_until_node_t); pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - *node = (pm_until_node_t) { - .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, PM_LOCATION_INIT_TOKENS(parser, keyword, closing)), - .keyword_loc = TOK2LOC(parser, keyword), - .do_keyword_loc = NTOK2LOC(parser, do_keyword), - .closing_loc = TOK2LOC(parser, closing), - .predicate = predicate, - .statements = statements - }; - - return node; + return pm_until_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_TOKENS(parser, keyword, closing), + TOK2LOC(parser, keyword), + NTOK2LOC(parser, do_keyword), + TOK2LOC(parser, closing), + predicate, + statements + ); } /** @@ -6927,20 +6865,20 @@ pm_until_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to */ static pm_until_node_t * pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { - pm_until_node_t *node = PM_NODE_ALLOC(parser, pm_until_node_t); pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_loop_modifier_block_exits(parser, statements); - *node = (pm_until_node_t) { - .base = PM_NODE_INIT(parser, PM_UNTIL_NODE, flags, PM_LOCATION_INIT_NODES(statements, predicate)), - .keyword_loc = TOK2LOC(parser, keyword), - .do_keyword_loc = { 0 }, - .closing_loc = { 0 }, - .predicate = predicate, - .statements = statements - }; - - return node; + return pm_until_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_NODES(statements, predicate), + TOK2LOC(parser, keyword), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + predicate, + statements + ); } /** @@ -6948,17 +6886,16 @@ pm_until_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm */ static pm_when_node_t * pm_when_node_create(pm_parser_t *parser, const pm_token_t *keyword) { - pm_when_node_t *node = PM_NODE_ALLOC(parser, pm_when_node_t); - - *node = (pm_when_node_t) { - .base = PM_NODE_INIT(parser, PM_WHEN_NODE, 0, PM_LOCATION_INIT_TOKEN(parser, keyword)), - .keyword_loc = TOK2LOC(parser, keyword), - .statements = NULL, - .then_keyword_loc = { 0 }, - .conditions = { 0 } - }; - - return node; + return pm_when_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_TOKEN(parser, keyword), + TOK2LOC(parser, keyword), + ((pm_node_list_t) { 0 }), + ((pm_location_t) { 0 }), + NULL + ); } /** @@ -6996,19 +6933,19 @@ pm_when_node_statements_set(pm_when_node_t *node, pm_statements_node_t *statemen */ static pm_while_node_t * pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_token_t *do_keyword, const pm_token_t *closing, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { - pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); - *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, PM_LOCATION_INIT_TOKENS(parser, keyword, closing)), - .keyword_loc = TOK2LOC(parser, keyword), - .do_keyword_loc = NTOK2LOC(parser, do_keyword), - .closing_loc = TOK2LOC(parser, closing), - .predicate = predicate, - .statements = statements - }; - - return node; + return pm_while_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_TOKENS(parser, keyword, closing), + TOK2LOC(parser, keyword), + NTOK2LOC(parser, do_keyword), + TOK2LOC(parser, closing), + predicate, + statements + ); } /** @@ -7016,20 +6953,20 @@ pm_while_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_to */ static pm_while_node_t * pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm_node_t *predicate, pm_statements_node_t *statements, pm_node_flags_t flags) { - pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_loop_modifier_block_exits(parser, statements); - *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, flags, PM_LOCATION_INIT_NODES(statements, predicate)), - .keyword_loc = TOK2LOC(parser, keyword), - .do_keyword_loc = { 0 }, - .closing_loc = { 0 }, - .predicate = predicate, - .statements = statements - }; - - return node; + return pm_while_node_new( + parser->arena, + ++parser->node_id, + flags, + PM_LOCATION_INIT_NODES(statements, predicate), + TOK2LOC(parser, keyword), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + predicate, + statements + ); } /** @@ -7037,18 +6974,17 @@ pm_while_node_modifier_create(pm_parser_t *parser, const pm_token_t *keyword, pm */ static pm_while_node_t * pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_statements_node_t *statements) { - pm_while_node_t *node = PM_NODE_ALLOC(parser, pm_while_node_t); - - *node = (pm_while_node_t) { - .base = PM_NODE_INIT(parser, PM_WHILE_NODE, 0, PM_LOCATION_INIT_UNSET), - .keyword_loc = { 0 }, - .do_keyword_loc = { 0 }, - .closing_loc = { 0 }, - .predicate = predicate, - .statements = statements - }; - - return node; + return pm_while_node_new( + parser->arena, + ++parser->node_id, + 0, + PM_LOCATION_INIT_UNSET, + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + ((pm_location_t) { 0 }), + predicate, + statements + ); } /** @@ -7057,17 +6993,16 @@ pm_while_node_synthesized_create(pm_parser_t *parser, pm_node_t *predicate, pm_s */ static pm_x_string_node_t * pm_xstring_node_create_unescaped(pm_parser_t *parser, const pm_token_t *opening, const pm_token_t *content, const pm_token_t *closing, const pm_string_t *unescaped) { - pm_x_string_node_t *node = PM_NODE_ALLOC(parser, pm_x_string_node_t); - - *node = (pm_x_string_node_t) { - .base = PM_NODE_INIT(parser, PM_X_STRING_NODE, PM_STRING_FLAGS_FROZEN, PM_LOCATION_INIT_TOKENS(parser, opening, closing)), - .opening_loc = TOK2LOC(parser, opening), - .content_loc = TOK2LOC(parser, content), - .closing_loc = TOK2LOC(parser, closing), - .unescaped = *unescaped - }; - - return node; + return pm_x_string_node_new( + parser->arena, + ++parser->node_id, + PM_STRING_FLAGS_FROZEN, + PM_LOCATION_INIT_TOKENS(parser, opening, closing), + TOK2LOC(parser, opening), + TOK2LOC(parser, content), + TOK2LOC(parser, closing), + *unescaped + ); } /** @@ -7083,8 +7018,6 @@ pm_xstring_node_create(pm_parser_t *parser, const pm_token_t *opening, const pm_ */ static pm_yield_node_t * pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_location_t *lparen_loc, pm_arguments_node_t *arguments, const pm_location_t *rparen_loc) { - pm_yield_node_t *node = PM_NODE_ALLOC(parser, pm_yield_node_t); - uint32_t start = PM_TOKEN_START(parser, keyword); uint32_t end; @@ -7098,15 +7031,16 @@ pm_yield_node_create(pm_parser_t *parser, const pm_token_t *keyword, const pm_lo end = PM_TOKEN_END(parser, keyword); } - *node = (pm_yield_node_t) { - .base = PM_NODE_INIT(parser, PM_YIELD_NODE, 0, ((pm_location_t) { .start = start, .length = U32(end - start) })), - .keyword_loc = TOK2LOC(parser, keyword), - .lparen_loc = *lparen_loc, - .arguments = arguments, - .rparen_loc = *rparen_loc - }; - - return node; + return pm_yield_node_new( + parser->arena, + ++parser->node_id, + 0, + ((pm_location_t) { .start = start, .length = U32(end - start) }), + TOK2LOC(parser, keyword), + *lparen_loc, + arguments, + *rparen_loc + ); } /** diff --git a/prism/srcs.mk b/prism/srcs.mk index 022662a00b5f30..ca3f86d3162621 100644 --- a/prism/srcs.mk +++ b/prism/srcs.mk @@ -44,6 +44,13 @@ $(srcdir)/prism/diagnostic.h: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATE realclean-prism-srcs:: $(RM) $(srcdir)/prism/diagnostic.h +main incs: $(srcdir)/prism/node_new.h +$(srcdir)/prism/node_new.h: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/include/prism/node_new.h.erb + $(Q) $(BASERUBY) $(PRISM_TEMPLATE) include/prism/node_new.h $@ + +realclean-prism-srcs:: + $(RM) $(srcdir)/prism/node_new.h + main srcs: $(srcdir)/lib/prism/compiler.rb $(srcdir)/lib/prism/compiler.rb: $(PRISM_CONFIG) $(PRISM_TEMPLATE) $(PRISM_TEMPLATES_DIR)/lib/prism/compiler.rb.erb $(Q) $(BASERUBY) $(PRISM_TEMPLATE) lib/prism/compiler.rb $@ diff --git a/prism/templates/include/prism/node_new.h.erb b/prism/templates/include/prism/node_new.h.erb new file mode 100644 index 00000000000000..56c214e00634b1 --- /dev/null +++ b/prism/templates/include/prism/node_new.h.erb @@ -0,0 +1,42 @@ +/** + * @file node_new.h + * + * Static inline functions for allocating and initializing AST nodes. + * + * -- + */ +#ifndef PRISM_NODE_NEW_H +#define PRISM_NODE_NEW_H + +#include "prism/node.h" + +<%- nodes.each do |node| -%> +<%- params = node.fields.map(&:c_param) -%> +/** + * Allocate and initialize a new <%= node.name %> node. + * + * @param arena The arena to allocate from. + * @param node_id The unique identifier for this node. + * @param flags The flags for this node. + * @param location The location of this node in the source. +<%- node.fields.each do |field| -%> + * @param <%= field.name %> <%= field.comment ? Prism::Template::Doxygen.verbatim(field.comment.lines.first.strip) : "The #{field.name} field." %> +<%- end -%> + * @return The newly allocated and initialized node. + */ +static inline pm_<%= node.human %>_t * +pm_<%= node.human %>_new(pm_arena_t *arena, uint32_t node_id, pm_node_flags_t flags, pm_location_t location<%= params.empty? ? "" : ", #{params.join(", ")}" %>) { + pm_<%= node.human %>_t *node = (pm_<%= node.human %>_t *) pm_arena_alloc(arena, sizeof(pm_<%= node.human %>_t), PRISM_ALIGNOF(pm_<%= node.human %>_t)); + + *node = (pm_<%= node.human %>_t) { + .base = { .type = <%= node.type %>, .flags = flags, .node_id = node_id, .location = location }<%= node.fields.empty? ? "" : "," %> +<%- node.fields.each_with_index do |field, index| -%> + .<%= field.name %> = <%= field.name %><%= index < node.fields.size - 1 ? "," : "" %> +<%- end -%> + }; + + return node; +} + +<%- end -%> +#endif diff --git a/prism/templates/template.rb b/prism/templates/template.rb index e571c58bf298a8..70fa17c83d59b9 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -105,6 +105,11 @@ def should_be_serialized? # Some node fields can be specialized if they point to a specific kind of # node and not just a generic node. class NodeKindField < Field + # The C type to use for this field as a function parameter. + def c_param + "struct #{c_type} *#{name}" + end + def initialize(kind:, **options) @kind = kind super(**options) @@ -210,6 +215,10 @@ def check_field_kind # This represents a field on a node that is a list of nodes. We pass them as # references and store them directly on the struct. class NodeListField < NodeKindField + def c_param + "pm_node_list_t #{name}" + end + def element_rbs_class if specific_kind "#{specific_kind}" @@ -250,6 +259,10 @@ def check_field_kind # This represents a field on a node that is the ID of a string interned # through the parser's constant pool. class ConstantField < Field + def c_param + "pm_constant_id_t #{name}" + end + def rbs_class "Symbol" end @@ -266,6 +279,10 @@ def java_type # This represents a field on a node that is the ID of a string interned # through the parser's constant pool and can be optionally null. class OptionalConstantField < Field + def c_param + "pm_constant_id_t #{name}" + end + def rbs_class "Symbol?" end @@ -282,6 +299,10 @@ def java_type # This represents a field on a node that is a list of IDs that are associated # with strings interned through the parser's constant pool. class ConstantListField < Field + def c_param + "pm_constant_id_list_t #{name}" + end + def rbs_class "Array[Symbol]" end @@ -297,6 +318,10 @@ def java_type # This represents a field on a node that is a string. class StringField < Field + def c_param + "pm_string_t #{name}" + end + def rbs_class "String" end @@ -312,6 +337,10 @@ def java_type # This represents a field on a node that is a location. class LocationField < Field + def c_param + "pm_location_t #{name}" + end + def semantic_field? false end @@ -331,6 +360,10 @@ def java_type # This represents a field on a node that is a location that is optional. class OptionalLocationField < Field + def c_param + "pm_location_t #{name}" + end + def semantic_field? false end @@ -350,6 +383,10 @@ def java_type # This represents an integer field. class UInt8Field < Field + def c_param + "uint8_t #{name}" + end + def rbs_class "Integer" end @@ -365,6 +402,10 @@ def java_type # This represents an integer field. class UInt32Field < Field + def c_param + "uint32_t #{name}" + end + def rbs_class "Integer" end @@ -381,6 +422,10 @@ def java_type # This represents an arbitrarily-sized integer. When it gets to Ruby it will # be an Integer. class IntegerField < Field + def c_param + "pm_integer_t #{name}" + end + def rbs_class "Integer" end @@ -397,6 +442,10 @@ def java_type # This represents a double-precision floating point number. When it gets to # Ruby it will be a Float. class DoubleField < Field + def c_param + "double #{name}" + end + def rbs_class "Float" end @@ -636,6 +685,7 @@ def locals "ext/prism/api_node.c", "include/prism/ast.h", "include/prism/diagnostic.h", + "include/prism/node_new.h", "javascript/src/deserialize.js", "javascript/src/nodes.js", "javascript/src/visitor.js", From cd54232bd81ba994a04fd5701053d638e0ac12f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 02:10:33 +0000 Subject: [PATCH 20/87] Bump github/codeql-action in the github-actions group across 1 directory Bumps the github-actions group with 1 update in the / directory: [github/codeql-action](https://github.com/github/codeql-action). Updates `github/codeql-action` from 4.32.5 to 4.32.6 - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c793b717bc78562f491db7b0e93a3a178b099162...0d579ffd059c29b07949a3cce3983f0780820c98) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 4.32.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/check_sast.yml | 8 ++++---- .github/workflows/scorecards.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index 6fed88a6e6d8cc..b02f6fb76ad047 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -95,17 +95,17 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: languages: ${{ matrix.language }} trap-caching: false debug: true - name: Autobuild - uses: github/codeql-action/autobuild@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 + uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: category: '/language:${{ matrix.language }}' upload: False @@ -135,7 +135,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 907519cb4d6621..8c061b16dcfb0b 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -73,6 +73,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c793b717bc78562f491db7b0e93a3a178b099162 # v4.32.5 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif From a6cb8f0774987082c217b185a50cd474cb38672d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Fri, 20 Feb 2026 13:46:47 +0100 Subject: [PATCH 21/87] Check for -fdeclspec consistently When checking whether CXXFLAGS is valid, we try to compile with a stdio include which causes a warning. This does the same when we check whether "-fdeclspec" can be used, that way the flag is not added if it would lead to a warning later. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 05e2e9aca26ddf..1b59942463bad6 100644 --- a/configure.ac +++ b/configure.ac @@ -715,7 +715,7 @@ AS_IF([test "$fdeclspec" = yes], [ RUBY_APPEND_OPTIONS(cflags, -fdeclspec) RUBY_APPEND_OPTIONS(orig_cflags, -fdeclspec) ]) -RUBY_TRY_CXXFLAGS(-fdeclspec, [fdeclspec=yes], [fdeclspec=no]) +RUBY_TRY_CXXFLAGS(-fdeclspec, [fdeclspec=yes], [fdeclspec=no], [@%:@include ]) AS_IF([test "$fdeclspec" = yes], [ RUBY_APPEND_OPTIONS(CXXFLAGS, -fdeclspec) ]) From 7a1d47aec1b47f22f3acc0a89c872d804dd29c79 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Thu, 5 Mar 2026 20:33:54 -0700 Subject: [PATCH 22/87] ZJIT: Remove duplicate CheckInterrupts within basic blocks (#16317) Add a new optimization pass that eliminates redundant CheckInterrupts instructions within each basic block. Only the first CheckInterrupts is needed per stretch of non-call code, since the interrupt flag only needs to be checked once. The flag resets when an intervening instruction writes to InterruptFlag (e.g. a Send). --- zjit/src/hir.rs | 25 +++++++++++++++++++++++++ zjit/src/hir/opt_tests.rs | 30 ------------------------------ zjit/src/stats.rs | 1 + 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a71946b3c915e3..3382747db71353 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5318,6 +5318,28 @@ impl Function { } } + /// Remove duplicate CheckInterrupts instructions within each basic block. + /// Only the first CheckInterrupts in a block is needed unless an intervening + /// instruction writes to InterruptFlag (e.g. a call), which resets tracking. + fn remove_duplicate_check_interrupts(&mut self) { + for block_id in self.rpo() { + let mut seen = false; + let insns = std::mem::take(&mut self.blocks[block_id.0].insns); + let mut new_insns = Vec::with_capacity(insns.len()); + for insn_id in insns { + let insn = &self.insns[insn_id.0]; + if matches!(insn, Insn::CheckInterrupts { .. }) { + if seen { continue; } + seen = true; + } else if insn.effects_of().write_bits().overlaps(abstract_heaps::InterruptFlag) { + seen = false; + } + new_insns.push(insn_id); + } + self.blocks[block_id.0].insns = new_insns; + } + } + /// Return a list that has entry_block and then jit_entry_blocks fn entry_blocks(&self) -> Vec { let mut entry_blocks = self.jit_entry_blocks.clone(); @@ -5545,6 +5567,8 @@ impl Function { Counter::compile_hir_clean_cfg_time_ns } else if ident_equal!($name, remove_redundant_patch_points) { Counter::compile_hir_remove_redundant_patch_points_time_ns + } else if ident_equal!($name, remove_duplicate_check_interrupts) { + Counter::compile_hir_remove_duplicate_check_interrupts_time_ns } else if ident_equal!($name, eliminate_dead_code) { Counter::compile_hir_eliminate_dead_code_time_ns } else { @@ -5573,6 +5597,7 @@ impl Function { run_pass!(fold_constants); run_pass!(clean_cfg); run_pass!(remove_redundant_patch_points); + run_pass!(remove_duplicate_check_interrupts); run_pass!(eliminate_dead_code); if should_dump { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 239e3c65949860..440d8a5d17d83a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -53,9 +53,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:NilClass): v13:TrueClass = Const Value(true) CheckInterrupts - v22:TrueClass = RefineType v13, Truthy v25:Fixnum[3] = Const Value(3) - CheckInterrupts Return v25 "); } @@ -87,9 +85,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:NilClass): v13:FalseClass = Const Value(false) CheckInterrupts - v20:FalseClass = RefineType v13, Falsy v35:Fixnum[4] = Const Value(4) - CheckInterrupts Return v35 "); } @@ -681,7 +677,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v24:Fixnum[3] = Const Value(3) - CheckInterrupts Return v24 "); } @@ -714,11 +709,8 @@ mod hir_opt_tests { v60:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - v62:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts v37:Fixnum[3] = Const Value(3) - CheckInterrupts Return v37 "); } @@ -752,7 +744,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v24:Fixnum[3] = Const Value(3) - CheckInterrupts Return v24 "); } @@ -785,11 +776,8 @@ mod hir_opt_tests { v60:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - v62:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts v37:Fixnum[3] = Const Value(3) - CheckInterrupts Return v37 "); } @@ -823,7 +811,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v33:Fixnum[4] = Const Value(4) - CheckInterrupts Return v33 "); } @@ -857,7 +844,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v24:Fixnum[3] = Const Value(3) - CheckInterrupts Return v24 "); } @@ -892,7 +878,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v24:Fixnum[3] = Const Value(3) - CheckInterrupts Return v24 "); } @@ -927,7 +912,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v33:Fixnum[4] = Const Value(4) - CheckInterrupts Return v33 "); } @@ -4283,7 +4267,6 @@ mod hir_opt_tests { v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - CheckInterrupts Return v46 "); } @@ -4321,7 +4304,6 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) v52:BasicObject = SendDirect v49, 0x1068, :initialize (0x1078), v15 CheckInterrupts - CheckInterrupts Return v49 "); } @@ -4354,7 +4336,6 @@ mod hir_opt_tests { v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - CheckInterrupts Return v46 "); } @@ -4387,7 +4368,6 @@ mod hir_opt_tests { v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - CheckInterrupts Return v46 "); } @@ -4418,7 +4398,6 @@ mod hir_opt_tests { IncrCounter complex_arg_pass_param_block v19:BasicObject = Send v46, :initialize # SendFallbackReason: Complex argument passing CheckInterrupts - CheckInterrupts Return v46 "); assert_snapshot!(inspect("test"), @"{}"); @@ -4483,7 +4462,6 @@ mod hir_opt_tests { v49:SetExact = GuardType v17, SetExact v50:BasicObject = CCallVariadic v49, :Set#initialize@0x1068 CheckInterrupts - CheckInterrupts Return v17 "); } @@ -4547,7 +4525,6 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) v54:BasicObject = CCallVariadic v50, :Regexp#initialize@0x1078, v16 CheckInterrupts - CheckInterrupts Return v50 "); } @@ -5914,7 +5891,6 @@ mod hir_opt_tests { v13:NilClass = Const Value(nil) CheckInterrupts v21:NilClass = Const Value(nil) - CheckInterrupts Return v21 "); } @@ -5946,7 +5922,6 @@ mod hir_opt_tests { v23:Fixnum[1] = RefineType v13, NotNil PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count - CheckInterrupts Return v23 "); } @@ -12110,7 +12085,6 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count CheckInterrupts v26:StaticSymbol[:CORRECT] = Const Value(VALUE(0x10a8)) - CheckInterrupts Return v26 "); } @@ -13509,11 +13483,7 @@ mod hir_opt_tests { IfFalse v16, bb6(v9, v17) v19:Truthy = RefineType v10, Truthy CheckInterrupts - v27:Truthy = RefineType v19, Truthy - CheckInterrupts - v35:Truthy = RefineType v27, Truthy v38:Fixnum[3] = Const Value(3) - CheckInterrupts Return v38 bb6(v43:BasicObject, v44:Falsy): v48:Fixnum[6] = Const Value(6) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 42dd39fdd047e9..aca4ae8051657b 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -171,6 +171,7 @@ make_counters! { compile_hir_fold_constants_time_ns, compile_hir_clean_cfg_time_ns, compile_hir_remove_redundant_patch_points_time_ns, + compile_hir_remove_duplicate_check_interrupts_time_ns, compile_hir_eliminate_dead_code_time_ns, compile_lir_time_ns, } From 85453b75b1f0b1c8a7585e67788dab8a9811b78e Mon Sep 17 00:00:00 2001 From: Kouhei Yanagita Date: Wed, 1 Nov 2023 13:04:50 +0900 Subject: [PATCH 23/87] Use OPTIMIZED_CMP in r_less instead of <=> --- range.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/range.c b/range.c index c3b3813cb1eef7..041cd93f73ee4e 100644 --- a/range.c +++ b/range.c @@ -200,11 +200,10 @@ range_eq(VALUE range, VALUE obj) static int r_less(VALUE a, VALUE b) { - VALUE r = rb_funcall(a, id_cmp, 1, b); - - if (NIL_P(r)) - return INT_MAX; - return rb_cmpint(r, a, b); + VALUE r; +#define rb_cmpint(cmp, a, b) (NIL_P(r = (cmp)) ? INT_MAX : rb_cmpint(r, (a), (b))) + return OPTIMIZED_CMP(a, b); +#undef rb_cmpint } static VALUE From 9aca729140424bbf465c11ab8ab53e5cc6602c01 Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Fri, 6 Sep 2013 22:22:42 -0400 Subject: [PATCH 24/87] Improve comparison error message. In certain cases, things like Array#sort can result in a confusing error message. For instance where a and b are characters in a string, `"string"`: ```ruby array.sort { |a, b| string.index(a) <=> string.index(b) } ``` If one of the index calls returns nil, we will get "comparison of String with String failed", which is somewhat unhelpful, since it's easy to be confused, given that what is really being compared is a Fixnum or NilClass (the cause of the error). Yes, as far as Array#sort is concerned, the two characters are the things being sorted, but it's useful to call attention to the return value of the comparison in this case. This patch adds a "reason" argument to rb_cmperr, which will provide an error message of "comparison of String with String failed: comparator returned nil" in the case above, or, in the case of: ```ruby 1.upto('10').to_a ``` it will provide the message: "comparison of Fixnum with String failed: coercion was not possible" --- bignum.c | 2 +- compar.c | 19 +++++++++++++++++-- depend | 28 ++++++++++++++++++++++++++++ ext/-test-/integer/depend | 3 +++ ext/-test-/rational/depend | 3 +++ ext/ripper/depend | 3 +++ internal/compar.h | 1 + internal/numeric.h | 4 ++-- numeric.c | 6 +++--- spec/ruby/core/comparable/lt_spec.rb | 2 +- 10 files changed, 62 insertions(+), 9 deletions(-) diff --git a/bignum.c b/bignum.c index c39f74576a2c31..f5c58ab74312a4 100644 --- a/bignum.c +++ b/bignum.c @@ -2964,7 +2964,7 @@ int rb_cmpint(VALUE val, VALUE a, VALUE b) { if (NIL_P(val)) { - rb_cmperr(a, b); + rb_cmperr_reason(a, b, "comparator returned nil"); } if (FIXNUM_P(val)) { long l = FIX2LONG(val); diff --git a/compar.c b/compar.c index 142cb12a0cfe77..1ab2520f1e6638 100644 --- a/compar.c +++ b/compar.c @@ -24,8 +24,8 @@ rb_cmp(VALUE x, VALUE y) return rb_funcallv(x, idCmp, 1, &y); } -void -rb_cmperr(VALUE x, VALUE y) +static VALUE +cmperr_subject(VALUE y) { VALUE classname; @@ -35,10 +35,25 @@ rb_cmperr(VALUE x, VALUE y) else { classname = rb_obj_class(y); } + return classname; +} + +void +rb_cmperr(VALUE x, VALUE y) +{ + VALUE classname = cmperr_subject(y); rb_raise(rb_eArgError, "comparison of %"PRIsVALUE" with %"PRIsVALUE" failed", rb_obj_class(x), classname); } +void +rb_cmperr_reason(VALUE x, VALUE y, const char *reason) +{ + VALUE classname = cmperr_subject(y); + rb_raise(rb_eArgError, "comparison of %"PRIsVALUE" with %"PRIsVALUE" failed: %s", + rb_obj_class(x), classname, reason); +} + static VALUE invcmp_recursive(VALUE x, VALUE y, int recursive) { diff --git a/depend b/depend index ced1dfd6ad8a75..f04e40e16f705d 100644 --- a/depend +++ b/depend @@ -287,6 +287,7 @@ ast.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ast.$(OBJEXT): $(top_srcdir)/internal/bignum.h ast.$(OBJEXT): $(top_srcdir)/internal/bits.h ast.$(OBJEXT): $(top_srcdir)/internal/box.h +ast.$(OBJEXT): $(top_srcdir)/internal/compar.h ast.$(OBJEXT): $(top_srcdir)/internal/compilers.h ast.$(OBJEXT): $(top_srcdir)/internal/complex.h ast.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -530,6 +531,7 @@ bignum.$(OBJEXT): $(top_srcdir)/internal/bignum.h bignum.$(OBJEXT): $(top_srcdir)/internal/bits.h bignum.$(OBJEXT): $(top_srcdir)/internal/box.h bignum.$(OBJEXT): $(top_srcdir)/internal/class.h +bignum.$(OBJEXT): $(top_srcdir)/internal/compar.h bignum.$(OBJEXT): $(top_srcdir)/internal/compilers.h bignum.$(OBJEXT): $(top_srcdir)/internal/complex.h bignum.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -1627,6 +1629,7 @@ compile.$(OBJEXT): $(top_srcdir)/internal/bignum.h compile.$(OBJEXT): $(top_srcdir)/internal/bits.h compile.$(OBJEXT): $(top_srcdir)/internal/box.h compile.$(OBJEXT): $(top_srcdir)/internal/class.h +compile.$(OBJEXT): $(top_srcdir)/internal/compar.h compile.$(OBJEXT): $(top_srcdir)/internal/compile.h compile.$(OBJEXT): $(top_srcdir)/internal/compilers.h compile.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -1895,6 +1898,7 @@ complex.$(OBJEXT): $(top_srcdir)/internal/bignum.h complex.$(OBJEXT): $(top_srcdir)/internal/bits.h complex.$(OBJEXT): $(top_srcdir)/internal/box.h complex.$(OBJEXT): $(top_srcdir)/internal/class.h +complex.$(OBJEXT): $(top_srcdir)/internal/compar.h complex.$(OBJEXT): $(top_srcdir)/internal/compilers.h complex.$(OBJEXT): $(top_srcdir)/internal/complex.h complex.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -4885,6 +4889,7 @@ enumerator.$(OBJEXT): $(top_srcdir)/internal/bignum.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bits.h enumerator.$(OBJEXT): $(top_srcdir)/internal/box.h enumerator.$(OBJEXT): $(top_srcdir)/internal/class.h +enumerator.$(OBJEXT): $(top_srcdir)/internal/compar.h enumerator.$(OBJEXT): $(top_srcdir)/internal/compilers.h enumerator.$(OBJEXT): $(top_srcdir)/internal/enumerator.h enumerator.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -5815,6 +5820,7 @@ gc.$(OBJEXT): $(top_srcdir)/internal/bignum.h gc.$(OBJEXT): $(top_srcdir)/internal/bits.h gc.$(OBJEXT): $(top_srcdir)/internal/box.h gc.$(OBJEXT): $(top_srcdir)/internal/class.h +gc.$(OBJEXT): $(top_srcdir)/internal/compar.h gc.$(OBJEXT): $(top_srcdir)/internal/compile.h gc.$(OBJEXT): $(top_srcdir)/internal/compilers.h gc.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -6966,6 +6972,7 @@ io.$(OBJEXT): $(top_srcdir)/internal/bignum.h io.$(OBJEXT): $(top_srcdir)/internal/bits.h io.$(OBJEXT): $(top_srcdir)/internal/box.h io.$(OBJEXT): $(top_srcdir)/internal/class.h +io.$(OBJEXT): $(top_srcdir)/internal/compar.h io.$(OBJEXT): $(top_srcdir)/internal/compilers.h io.$(OBJEXT): $(top_srcdir)/internal/encoding.h io.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -7197,6 +7204,7 @@ io_buffer.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/box.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/compar.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/compilers.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/error.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -7413,6 +7421,7 @@ iseq.$(OBJEXT): $(top_srcdir)/internal/bignum.h iseq.$(OBJEXT): $(top_srcdir)/internal/bits.h iseq.$(OBJEXT): $(top_srcdir)/internal/box.h iseq.$(OBJEXT): $(top_srcdir)/internal/class.h +iseq.$(OBJEXT): $(top_srcdir)/internal/compar.h iseq.$(OBJEXT): $(top_srcdir)/internal/compile.h iseq.$(OBJEXT): $(top_srcdir)/internal/compilers.h iseq.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -7919,6 +7928,7 @@ load.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h load.$(OBJEXT): $(top_srcdir)/internal/bignum.h load.$(OBJEXT): $(top_srcdir)/internal/bits.h load.$(OBJEXT): $(top_srcdir)/internal/box.h +load.$(OBJEXT): $(top_srcdir)/internal/compar.h load.$(OBJEXT): $(top_srcdir)/internal/compilers.h load.$(OBJEXT): $(top_srcdir)/internal/complex.h load.$(OBJEXT): $(top_srcdir)/internal/dir.h @@ -8675,6 +8685,7 @@ marshal.$(OBJEXT): $(top_srcdir)/internal/bignum.h marshal.$(OBJEXT): $(top_srcdir)/internal/bits.h marshal.$(OBJEXT): $(top_srcdir)/internal/box.h marshal.$(OBJEXT): $(top_srcdir)/internal/class.h +marshal.$(OBJEXT): $(top_srcdir)/internal/compar.h marshal.$(OBJEXT): $(top_srcdir)/internal/compilers.h marshal.$(OBJEXT): $(top_srcdir)/internal/encoding.h marshal.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -9284,6 +9295,7 @@ miniinit.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h miniinit.$(OBJEXT): $(top_srcdir)/internal/bignum.h miniinit.$(OBJEXT): $(top_srcdir)/internal/bits.h miniinit.$(OBJEXT): $(top_srcdir)/internal/box.h +miniinit.$(OBJEXT): $(top_srcdir)/internal/compar.h miniinit.$(OBJEXT): $(top_srcdir)/internal/compilers.h miniinit.$(OBJEXT): $(top_srcdir)/internal/complex.h miniinit.$(OBJEXT): $(top_srcdir)/internal/eval.h @@ -9757,6 +9769,7 @@ node_dump.$(OBJEXT): $(top_srcdir)/internal/bignum.h node_dump.$(OBJEXT): $(top_srcdir)/internal/bits.h node_dump.$(OBJEXT): $(top_srcdir)/internal/box.h node_dump.$(OBJEXT): $(top_srcdir)/internal/class.h +node_dump.$(OBJEXT): $(top_srcdir)/internal/compar.h node_dump.$(OBJEXT): $(top_srcdir)/internal/compilers.h node_dump.$(OBJEXT): $(top_srcdir)/internal/complex.h node_dump.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -9974,6 +9987,7 @@ numeric.$(OBJEXT): $(top_srcdir)/internal/bignum.h numeric.$(OBJEXT): $(top_srcdir)/internal/bits.h numeric.$(OBJEXT): $(top_srcdir)/internal/box.h numeric.$(OBJEXT): $(top_srcdir)/internal/class.h +numeric.$(OBJEXT): $(top_srcdir)/internal/compar.h numeric.$(OBJEXT): $(top_srcdir)/internal/compilers.h numeric.$(OBJEXT): $(top_srcdir)/internal/complex.h numeric.$(OBJEXT): $(top_srcdir)/internal/enumerator.h @@ -10196,6 +10210,7 @@ object.$(OBJEXT): $(top_srcdir)/internal/bignum.h object.$(OBJEXT): $(top_srcdir)/internal/bits.h object.$(OBJEXT): $(top_srcdir)/internal/box.h object.$(OBJEXT): $(top_srcdir)/internal/class.h +object.$(OBJEXT): $(top_srcdir)/internal/compar.h object.$(OBJEXT): $(top_srcdir)/internal/compilers.h object.$(OBJEXT): $(top_srcdir)/internal/error.h object.$(OBJEXT): $(top_srcdir)/internal/eval.h @@ -10424,6 +10439,7 @@ pack.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h pack.$(OBJEXT): $(top_srcdir)/internal/bignum.h pack.$(OBJEXT): $(top_srcdir)/internal/bits.h pack.$(OBJEXT): $(top_srcdir)/internal/box.h +pack.$(OBJEXT): $(top_srcdir)/internal/compar.h pack.$(OBJEXT): $(top_srcdir)/internal/compilers.h pack.$(OBJEXT): $(top_srcdir)/internal/fixnum.h pack.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -10640,6 +10656,7 @@ parse.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h parse.$(OBJEXT): $(top_srcdir)/internal/bignum.h parse.$(OBJEXT): $(top_srcdir)/internal/bits.h parse.$(OBJEXT): $(top_srcdir)/internal/box.h +parse.$(OBJEXT): $(top_srcdir)/internal/compar.h parse.$(OBJEXT): $(top_srcdir)/internal/compile.h parse.$(OBJEXT): $(top_srcdir)/internal/compilers.h parse.$(OBJEXT): $(top_srcdir)/internal/complex.h @@ -12680,6 +12697,7 @@ process.$(OBJEXT): $(top_srcdir)/internal/bignum.h process.$(OBJEXT): $(top_srcdir)/internal/bits.h process.$(OBJEXT): $(top_srcdir)/internal/box.h process.$(OBJEXT): $(top_srcdir)/internal/class.h +process.$(OBJEXT): $(top_srcdir)/internal/compar.h process.$(OBJEXT): $(top_srcdir)/internal/compilers.h process.$(OBJEXT): $(top_srcdir)/internal/dir.h process.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -12912,6 +12930,7 @@ ractor.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ractor.$(OBJEXT): $(top_srcdir)/internal/bignum.h ractor.$(OBJEXT): $(top_srcdir)/internal/bits.h ractor.$(OBJEXT): $(top_srcdir)/internal/box.h +ractor.$(OBJEXT): $(top_srcdir)/internal/compar.h ractor.$(OBJEXT): $(top_srcdir)/internal/compilers.h ractor.$(OBJEXT): $(top_srcdir)/internal/complex.h ractor.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -13145,6 +13164,7 @@ random.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h random.$(OBJEXT): $(top_srcdir)/internal/bignum.h random.$(OBJEXT): $(top_srcdir)/internal/bits.h random.$(OBJEXT): $(top_srcdir)/internal/box.h +random.$(OBJEXT): $(top_srcdir)/internal/compar.h random.$(OBJEXT): $(top_srcdir)/internal/compilers.h random.$(OBJEXT): $(top_srcdir)/internal/fixnum.h random.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -13557,6 +13577,7 @@ rational.$(OBJEXT): $(top_srcdir)/internal/bignum.h rational.$(OBJEXT): $(top_srcdir)/internal/bits.h rational.$(OBJEXT): $(top_srcdir)/internal/box.h rational.$(OBJEXT): $(top_srcdir)/internal/class.h +rational.$(OBJEXT): $(top_srcdir)/internal/compar.h rational.$(OBJEXT): $(top_srcdir)/internal/compilers.h rational.$(OBJEXT): $(top_srcdir)/internal/complex.h rational.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -14990,6 +15011,7 @@ ruby.$(OBJEXT): $(top_srcdir)/internal/bits.h ruby.$(OBJEXT): $(top_srcdir)/internal/box.h ruby.$(OBJEXT): $(top_srcdir)/internal/class.h ruby.$(OBJEXT): $(top_srcdir)/internal/cmdlineopt.h +ruby.$(OBJEXT): $(top_srcdir)/internal/compar.h ruby.$(OBJEXT): $(top_srcdir)/internal/compilers.h ruby.$(OBJEXT): $(top_srcdir)/internal/complex.h ruby.$(OBJEXT): $(top_srcdir)/internal/cont.h @@ -15239,8 +15261,10 @@ ruby.$(OBJEXT): {$(VPATH)}vm_opts.h ruby.$(OBJEXT): {$(VPATH)}yjit.h ruby_parser.$(OBJEXT): $(hdrdir)/ruby/ruby.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/array.h +ruby_parser.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/bignum.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/bits.h +ruby_parser.$(OBJEXT): $(top_srcdir)/internal/compar.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/compilers.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/complex.h ruby_parser.$(OBJEXT): $(top_srcdir)/internal/error.h @@ -16445,9 +16469,11 @@ signal.$(OBJEXT): {$(VPATH)}vm_debug.h signal.$(OBJEXT): {$(VPATH)}vm_opts.h sprintf.$(OBJEXT): $(hdrdir)/ruby/ruby.h sprintf.$(OBJEXT): $(hdrdir)/ruby/version.h +sprintf.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bignum.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bits.h sprintf.$(OBJEXT): $(top_srcdir)/internal/class.h +sprintf.$(OBJEXT): $(top_srcdir)/internal/compar.h sprintf.$(OBJEXT): $(top_srcdir)/internal/compilers.h sprintf.$(OBJEXT): $(top_srcdir)/internal/error.h sprintf.$(OBJEXT): $(top_srcdir)/internal/fixnum.h @@ -20475,6 +20501,7 @@ yjit.$(OBJEXT): $(top_srcdir)/internal/bignum.h yjit.$(OBJEXT): $(top_srcdir)/internal/bits.h yjit.$(OBJEXT): $(top_srcdir)/internal/box.h yjit.$(OBJEXT): $(top_srcdir)/internal/class.h +yjit.$(OBJEXT): $(top_srcdir)/internal/compar.h yjit.$(OBJEXT): $(top_srcdir)/internal/compile.h yjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h yjit.$(OBJEXT): $(top_srcdir)/internal/cont.h @@ -20732,6 +20759,7 @@ zjit.$(OBJEXT): $(top_srcdir)/internal/bignum.h zjit.$(OBJEXT): $(top_srcdir)/internal/bits.h zjit.$(OBJEXT): $(top_srcdir)/internal/box.h zjit.$(OBJEXT): $(top_srcdir)/internal/class.h +zjit.$(OBJEXT): $(top_srcdir)/internal/compar.h zjit.$(OBJEXT): $(top_srcdir)/internal/compile.h zjit.$(OBJEXT): $(top_srcdir)/internal/compilers.h zjit.$(OBJEXT): $(top_srcdir)/internal/cont.h diff --git a/ext/-test-/integer/depend b/ext/-test-/integer/depend index 0ea007e814128d..d0589b5e5dd8a8 100644 --- a/ext/-test-/integer/depend +++ b/ext/-test-/integer/depend @@ -159,8 +159,11 @@ core_ext.o: $(hdrdir)/ruby/missing.h core_ext.o: $(hdrdir)/ruby/ruby.h core_ext.o: $(hdrdir)/ruby/st.h core_ext.o: $(hdrdir)/ruby/subst.h +core_ext.o: $(top_srcdir)/internal.h +core_ext.o: $(top_srcdir)/internal/basic_operators.h core_ext.o: $(top_srcdir)/internal/bignum.h core_ext.o: $(top_srcdir)/internal/bits.h +core_ext.o: $(top_srcdir)/internal/compar.h core_ext.o: $(top_srcdir)/internal/compilers.h core_ext.o: $(top_srcdir)/internal/fixnum.h core_ext.o: $(top_srcdir)/internal/numeric.h diff --git a/ext/-test-/rational/depend b/ext/-test-/rational/depend index 56d6ab77d6c36a..d949fca66bcc88 100644 --- a/ext/-test-/rational/depend +++ b/ext/-test-/rational/depend @@ -163,8 +163,11 @@ rat.o: $(hdrdir)/ruby/missing.h rat.o: $(hdrdir)/ruby/ruby.h rat.o: $(hdrdir)/ruby/st.h rat.o: $(hdrdir)/ruby/subst.h +rat.o: $(top_srcdir)/internal.h +rat.o: $(top_srcdir)/internal/basic_operators.h rat.o: $(top_srcdir)/internal/bignum.h rat.o: $(top_srcdir)/internal/bits.h +rat.o: $(top_srcdir)/internal/compar.h rat.o: $(top_srcdir)/internal/compilers.h rat.o: $(top_srcdir)/internal/fixnum.h rat.o: $(top_srcdir)/internal/gc.h diff --git a/ext/ripper/depend b/ext/ripper/depend index 944da25ee94f38..96d41c87b89ac0 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -586,6 +586,7 @@ ripper.o: $(top_srcdir)/internal/basic_operators.h ripper.o: $(top_srcdir)/internal/bignum.h ripper.o: $(top_srcdir)/internal/bits.h ripper.o: $(top_srcdir)/internal/box.h +ripper.o: $(top_srcdir)/internal/compar.h ripper.o: $(top_srcdir)/internal/compile.h ripper.o: $(top_srcdir)/internal/compilers.h ripper.o: $(top_srcdir)/internal/complex.h @@ -812,8 +813,10 @@ ripper_init.o: $(hdrdir)/ruby/st.h ripper_init.o: $(hdrdir)/ruby/subst.h ripper_init.o: $(top_srcdir)/internal.h ripper_init.o: $(top_srcdir)/internal/array.h +ripper_init.o: $(top_srcdir)/internal/basic_operators.h ripper_init.o: $(top_srcdir)/internal/bignum.h ripper_init.o: $(top_srcdir)/internal/bits.h +ripper_init.o: $(top_srcdir)/internal/compar.h ripper_init.o: $(top_srcdir)/internal/compilers.h ripper_init.o: $(top_srcdir)/internal/complex.h ripper_init.o: $(top_srcdir)/internal/fixnum.h diff --git a/internal/compar.h b/internal/compar.h index 9115e4bd63a8e4..5eb5e8714e7483 100644 --- a/internal/compar.h +++ b/internal/compar.h @@ -25,5 +25,6 @@ /* compar.c */ VALUE rb_invcmp(VALUE, VALUE); +NORETURN(void rb_cmperr_reason(VALUE, VALUE, const char*)); #endif /* INTERNAL_COMPAR_H */ diff --git a/internal/numeric.h b/internal/numeric.h index 6391b1e9bc0f5c..280c008952ed7d 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -10,9 +10,9 @@ */ #include "internal/bignum.h" /* for BIGNUM_POSITIVE_P */ #include "internal/bits.h" /* for RUBY_BIT_ROTL */ +#include "internal/compar.h" /* for rb_cmperr_reason */ #include "internal/fixnum.h" /* for FIXNUM_POSITIVE_P */ #include "internal/vm.h" /* for rb_method_basic_definition_p */ -#include "ruby/intern.h" /* for rb_cmperr */ #include "ruby/ruby.h" /* for USE_FLONUM */ #define ROUND_TO(mode, even, up, down) \ @@ -210,7 +210,7 @@ rb_num_compare_with_zero(VALUE num, ID mid) VALUE zero = INT2FIX(0); VALUE r = rb_check_funcall(num, mid, 1, &zero); if (RB_UNDEF_P(r)) { - rb_cmperr(num, zero); + rb_cmperr_reason(num, zero, "unable to compare with zero"); } return r; } diff --git a/numeric.c b/numeric.c index 10ce7358e7a370..226a47f7b8f38b 100644 --- a/numeric.c +++ b/numeric.c @@ -492,7 +492,7 @@ rb_num_coerce_cmp(VALUE x, VALUE y, ID func) static VALUE ensure_cmp(VALUE c, VALUE x, VALUE y) { - if (NIL_P(c)) rb_cmperr(x, y); + if (NIL_P(c)) rb_cmperr_reason(x, y, "comparator returned nil"); return c; } @@ -502,7 +502,7 @@ rb_num_coerce_relop(VALUE x, VALUE y, ID func) VALUE x0 = x, y0 = y; if (!do_coerce(&x, &y, FALSE)) { - rb_cmperr(x0, y0); + rb_cmperr_reason(x0, y0, "coercion was not possible"); UNREACHABLE_RETURN(Qnil); } return ensure_cmp(rb_funcall(x, func, 1, y), x0, y0); @@ -5911,7 +5911,7 @@ int_downto(VALUE from, VALUE to) rb_yield(i); i = rb_funcall(i, '-', 1, INT2FIX(1)); } - if (NIL_P(c)) rb_cmperr(i, to); + ensure_cmp(c, i, to); } return from; } diff --git a/spec/ruby/core/comparable/lt_spec.rb b/spec/ruby/core/comparable/lt_spec.rb index bca95f8d252bce..3c73e410ea14ed 100644 --- a/spec/ruby/core/comparable/lt_spec.rb +++ b/spec/ruby/core/comparable/lt_spec.rb @@ -43,7 +43,7 @@ it "raises an argument error with a message containing the value" do -> { ("foo" < 7) }.should raise_error(ArgumentError) { |e| - e.message.should == "comparison of String with 7 failed" + e.message.should.include? "String with 7 failed" } end end From dcd6b55e0c4d41bf2fe3423a54d0f87a3e99c03b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:32:38 +0900 Subject: [PATCH 25/87] [ruby/rubygems] Skip flaky webauthn test on TruffleRuby Pend test_with_webauthn_enabled_failure on TruffleRuby where it fails intermittently. https://github.com/ruby/rubygems/commit/6e062ccef1 --- test/rubygems/test_gem_commands_push_command.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 1477a749471443..978ed3ada88e8c 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -487,6 +487,7 @@ def test_with_webauthn_enabled_success end def test_with_webauthn_enabled_failure + pend "Flaky on TruffleRuby" if RUBY_ENGINE == "truffleruby" response_success = "Successfully registered gem: freewill (1.0.0)" server = Gem::MockTCPServer.new error = Gem::WebauthnVerificationError.new("Something went wrong") From f16961ec795776b283e51d6d768d579520f24196 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:32:33 +0900 Subject: [PATCH 26/87] [ruby/rubygems] Remove dead code in dependency installer tests Remove unused si.to_yaml calls that stored YAML at URLs that were never fetched. With the pure-Ruby parser, NilClass no longer has to_yaml, but these lines were dead code regardless. https://github.com/ruby/rubygems/commit/6ab25e49ac --- test/rubygems/test_gem_dependency_installer.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/rubygems/test_gem_dependency_installer.rb b/test/rubygems/test_gem_dependency_installer.rb index 3e10c0883aaebc..8d9caf7d90b9a4 100644 --- a/test/rubygems/test_gem_dependency_installer.rb +++ b/test/rubygems/test_gem_dependency_installer.rb @@ -677,8 +677,7 @@ def test_install_force util_setup_gems FileUtils.mv @b1_gem, @tempdir - si = util_setup_spec_fetcher @b1 - @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml + util_setup_spec_fetcher @b1 inst = nil Dir.chdir @tempdir do @@ -955,9 +954,7 @@ def test_install_remote_platform_newer s.platform = Gem::Platform.new %w[cpu other_platform 1] end - si = util_setup_spec_fetcher @a1, a2_o - - @fetcher.data["http://gems.example.com/gems/yaml"] = si.to_yaml + util_setup_spec_fetcher @a1, a2_o a1_data = nil a2_o_data = nil From f56310de4a2d557dca7693954949f19d15906965 Mon Sep 17 00:00:00 2001 From: thesmartshadow Date: Thu, 5 Mar 2026 18:58:27 +0000 Subject: [PATCH 27/87] [ruby/json] Reject negative depth; add overflow guards to prevent hang/crash https://github.com/ruby/json/commit/de993aa766 --- ext/json/generator/generator.c | 15 +++++++++++++-- test/json/json_generator_test.rb | 10 ++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index e699d31812c45e..a6302691d9fd6b 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -1575,6 +1575,17 @@ static long long_config(VALUE num) return RTEST(num) ? FIX2LONG(num) : 0; } +// depth must never be negative; reject early with a clear error. +static long depth_config(VALUE num) +{ + if (!RTEST(num)) return 0; + long d = NUM2LONG(num); + if (RB_UNLIKELY(d < 0)) { + rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d); + } + return d; +} + /* * call-seq: max_nesting=(depth) * @@ -1731,7 +1742,7 @@ static VALUE cState_depth_set(VALUE self, VALUE depth) { rb_check_frozen(self); GET_STATE(self); - state->depth = long_config(depth); + state->depth = depth_config(depth); return Qnil; } @@ -1796,7 +1807,7 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg) else if (key == sym_max_nesting) { state->max_nesting = long_config(val); } else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); } else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); } - else if (key == sym_depth) { state->depth = long_config(val); } + else if (key == sym_depth) { state->depth = depth_config(val); } else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); } else if (key == sym_script_safe) { state->script_safe = RTEST(val); } else if (key == sym_escape_slash) { state->script_safe = RTEST(val); } diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index 9a8ee5157ed6bc..eb5a8ca59e0ee6 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1045,4 +1045,14 @@ def test_nesting_recovery assert_equal 0, state.depth assert_equal '{"a":1}', state.generate({ a: 1 }) end + + def test_negative_depth_raises + assert_raise(ArgumentError) do + JSON.generate({"a" => 1}, depth: -1) + end + assert_raise(ArgumentError) do + JSON.state.new(depth: -1) + end + end + end From cd80e2382375ddb34c69079fa1e7f09821c4f528 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 6 Mar 2026 08:51:43 +0100 Subject: [PATCH 28/87] [ruby/json] fbuffer.h: Use size_t over unsigned long unsigned long is only 32b on some platforms. https://github.com/ruby/json/commit/0a4fb79cd9 --- ext/json/fbuffer/fbuffer.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index 8ce029a8c49dfd..d5fd5ea26adccc 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -11,11 +11,11 @@ enum fbuffer_type { typedef struct FBufferStruct { enum fbuffer_type type; - unsigned long initial_length; - unsigned long len; - unsigned long capa; + size_t initial_length; + size_t len; + size_t capa; #if JSON_DEBUG - unsigned long requested; + size_t requested; #endif char *ptr; VALUE io; @@ -32,12 +32,12 @@ typedef struct FBufferStruct { static void fbuffer_free(FBuffer *fb); static void fbuffer_clear(FBuffer *fb); -static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len); +static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len); static void fbuffer_append_long(FBuffer *fb, long number); static inline void fbuffer_append_char(FBuffer *fb, char newchr); static VALUE fbuffer_finalize(FBuffer *fb); -static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) +static void fbuffer_stack_init(FBuffer *fb, size_t initial_length, char *stack_buffer, size_t stack_buffer_size) { fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT; if (stack_buffer) { @@ -50,7 +50,7 @@ static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char * #endif } -static inline void fbuffer_consumed(FBuffer *fb, unsigned long consumed) +static inline void fbuffer_consumed(FBuffer *fb, size_t consumed) { #if JSON_DEBUG if (consumed > fb->requested) { @@ -79,7 +79,7 @@ static void fbuffer_flush(FBuffer *fb) fbuffer_clear(fb); } -static void fbuffer_realloc(FBuffer *fb, unsigned long required) +static void fbuffer_realloc(FBuffer *fb, size_t required) { if (required > fb->capa) { if (fb->type == FBUFFER_STACK_ALLOCATED) { @@ -94,7 +94,7 @@ static void fbuffer_realloc(FBuffer *fb, unsigned long required) } } -static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) +static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested) { if (RB_UNLIKELY(fb->io)) { if (fb->capa < FBUFFER_IO_BUFFER_SIZE) { @@ -108,7 +108,7 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) } } - unsigned long required; + size_t required; if (RB_UNLIKELY(!fb->ptr)) { fb->ptr = ALLOC_N(char, fb->initial_length); @@ -120,7 +120,7 @@ static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) fbuffer_realloc(fb, required); } -static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) +static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested) { #if JSON_DEBUG fb->requested = requested; @@ -131,13 +131,13 @@ static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) } } -static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, unsigned long len) +static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len) { MEMCPY(fb->ptr + fb->len, newstr, char, len); fbuffer_consumed(fb, len); } -static inline void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len) +static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len) { if (len > 0) { fbuffer_inc_capa(fb, len); @@ -162,7 +162,7 @@ static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) static void fbuffer_append_str(FBuffer *fb, VALUE str) { const char *ptr; - unsigned long len; + size_t len; RSTRING_GETMEM(str, ptr, len); fbuffer_append(fb, ptr, len); @@ -171,7 +171,7 @@ static void fbuffer_append_str(FBuffer *fb, VALUE str) static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) { const char *ptr; - unsigned long len; + size_t len; RSTRING_GETMEM(str, ptr, len); fbuffer_inc_capa(fb, repeat * len); From 9356837d1a436a75ba3b35234d7678eeee158ec5 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Fri, 6 Mar 2026 09:03:58 +0100 Subject: [PATCH 29/87] [ruby/json] Release 2.19.0 https://github.com/ruby/json/commit/a11acc1ff4 --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 95b88571005b7e..3f73c0d97dcc6c 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.18.1' + VERSION = '2.19.0' end From 66c3ff3f18f7fd6b46b14762352085650a13800b Mon Sep 17 00:00:00 2001 From: git Date: Fri, 6 Mar 2026 08:06:44 +0000 Subject: [PATCH 30/87] Update default gems list at 9356837d1a436a75ba3b35234d7678 [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 7a73e16f9f54cb..79b5cc9ff87330 100644 --- a/NEWS.md +++ b/NEWS.md @@ -64,7 +64,7 @@ releases. * RubyGems 4.1.0.dev * bundler 4.1.0.dev -* json 2.18.1 +* json 2.19.0 * 2.18.0 to [v2.18.1][json-v2.18.1] * openssl 4.0.1 * 4.0.0 to [v4.0.1][openssl-v4.0.1] From d5d144c149d3beabbfb262e3994f60552469181b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 2 Mar 2026 02:29:09 +0900 Subject: [PATCH 31/87] parse.y: Split forwarding argument in method and lambda Eliminate the lambda argument conditions from the action. --- parse.y | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/parse.y b/parse.y index 6813b396332b00..b91b141cc65375 100644 --- a/parse.y +++ b/parse.y @@ -2773,7 +2773,7 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) %type if_tail opt_else case_body case_args cases opt_rescue exc_list exc_var opt_ensure %type args arg_splat call_args opt_call_args %type paren_args opt_paren_args -%type args_tail block_args_tail f_args-opt_tail block_args-opt_tail +%type args_tail block_args_tail block_args-opt_tail %type command_args aref_args %type opt_block_arg block_arg %type var_ref var_lhs @@ -2788,7 +2788,7 @@ rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) %type do bv_decls opt_bv_decl bvar %type lambda brace_body do_body %type lambda_body -%type f_larglist +%type f_larglist f_largs largs_tail %type brace_block cmd_brace_block do_block lhs none fitem %type mlhs_head mlhs_item mlhs_node %type mlhs mlhs_basic mlhs_inner @@ -5103,19 +5103,19 @@ lambda : tLAMBDA[lpar] } ; -f_larglist : '(' f_args opt_bv_decl ')' +f_larglist : '(' f_largs[args] opt_bv_decl ')' { p->ctxt.in_argdef = 0; - $$ = $f_args; + $$ = $args; p->max_numparam = ORDINAL_PARAM; - /*% ripper: paren!($:f_args) %*/ + /*% ripper: paren!($:args) %*/ } - | f_args + | f_largs[args] { p->ctxt.in_argdef = 0; - if (!args_info_empty_p(&$f_args->nd_ainfo)) + if (!args_info_empty_p(&$args->nd_ainfo)) p->max_numparam = ORDINAL_PARAM; - $$ = $f_args; + $$ = $args; } ; @@ -6243,16 +6243,18 @@ f_arglist : f_paren_args args_tail : args_tail_basic(arg_value) | args_forward { - ID fwd = $args_forward; - if (lambda_beginning_p() || - (p->lex.lpar_beg >= 0 && p->lex.lpar_beg+1 == p->lex.paren_nest)) { - yyerror0("unexpected ... in lambda argument"); - fwd = 0; - } - else { - add_forwarding_args(p); - } - $$ = new_args_tail(p, 0, fwd, arg_FWD_BLOCK, &@args_forward); + add_forwarding_args(p); + $$ = new_args_tail(p, 0, $args_forward, arg_FWD_BLOCK, &@args_forward); + $$->nd_ainfo.forwarding = 1; + /*% ripper: [Qnil, $:args_forward, Qnil] %*/ + } + ; + +largs_tail : args_tail_basic(arg_value) + | args_forward + { + yyerror1(&@args_forward, "unexpected ... in lambda argument"); + $$ = new_args_tail(p, 0, 0, 0, &@args_forward); $$->nd_ainfo.forwarding = 1; /*% ripper: [Qnil, $:args_forward, Qnil] %*/ } @@ -6329,19 +6331,27 @@ args_tail : args_tail_basic(arg_value) } ; -f_args-opt_tail : opt_args_tail(args_tail) +%rule f_args-opt_tail(tail) + : opt_args_tail(tail) ; -f_args : args-list(arg_value, f_args-opt_tail) - | f_arg[pre] f_args-opt_tail[tail] + +%rule f_args-list(tail) + : args-list(arg_value, f_args-opt_tail(tail)) + | f_arg[pre] opt_args_tail(tail)[tail] { $$ = new_args(p, $pre, 0, 0, 0, $tail, &@$); /*% ripper: params!($:pre, Qnil, Qnil, Qnil, *$:tail[0..2]) %*/ } - | tail-only-args(args_tail) + | tail-only-args(tail) | f_empty_arg ; +f_args : f_args-list(args_tail) + ; + +f_largs : f_args-list(largs_tail) + ; args_forward : tBDOT3 { From db52cd6259e6d4d5dd8303e10262c3daff576a34 Mon Sep 17 00:00:00 2001 From: Steven Webb Date: Sat, 7 Mar 2026 01:27:30 +0800 Subject: [PATCH 32/87] ZJIT: Constant fold div (/) operations (#16168) (#16233) ZJIT: Constant fold modulus (%) operations (#16168) Similar to the way ZJIT already folds +, -, and * operations. One complication is that the / operator behaves differently in Ruby than in Rust for negative values. For example in Ruby: ``` ruby -e "puts 7 / -3" # => -3 ``` vs Rust: ``` println!("{}", 7 / -3); // => -2 ``` My solution is to pass this through to cruby rb_fix_div_fix to do the calculation for us. --- zjit/src/cruby.rs | 2 + zjit/src/hir.rs | 12 +++++ zjit/src/hir/opt_tests.rs | 111 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1ca29d5bc3312e..f762e7672d64be 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -135,6 +135,7 @@ unsafe extern "C" { pub fn rb_str_setbyte(str: VALUE, index: VALUE, value: VALUE) -> VALUE; pub fn rb_str_getbyte(str: VALUE, index: VALUE) -> VALUE; pub fn rb_vm_splat_array(flag: VALUE, ary: VALUE) -> VALUE; + pub fn rb_jit_fix_div_fix(x: VALUE, y: VALUE) -> VALUE; pub fn rb_jit_fix_mod_fix(x: VALUE, y: VALUE) -> VALUE; pub fn rb_vm_concat_array(ary1: VALUE, ary2st: VALUE) -> VALUE; pub fn rb_vm_get_special_object(reg_ep: *const VALUE, value_type: vm_special_object_type) -> VALUE; @@ -207,6 +208,7 @@ pub use rb_vm_ci_kwarg as vm_ci_kwarg; pub use rb_METHOD_ENTRY_VISI as METHOD_ENTRY_VISI; pub use rb_RCLASS_ORIGIN as RCLASS_ORIGIN; pub use rb_vm_get_special_object as vm_get_special_object; +pub use rb_jit_fix_div_fix as rb_fix_div_fix; pub use rb_jit_fix_mod_fix as rb_fix_mod_fix; /// Helper so we can get a Rust string for insn_name() diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3382747db71353..65c49444243145 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5080,6 +5080,18 @@ impl Function { _ => None, }) } + Insn::FixnumDiv { left, right, .. } => { + self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) { + (Some(l), Some(r)) if l == (RUBY_FIXNUM_MIN as i64) && r == -1 => None, // Avoid Fixnum overflow + (Some(_l), Some(r)) if r == 0 => None, // Avoid Divide by zero. + (Some(l), Some(r)) => { + let l_obj = VALUE::fixnum_from_isize(l as isize); + let r_obj = VALUE::fixnum_from_isize(r as isize); + Some(unsafe { rb_jit_fix_div_fix(l_obj, r_obj) }.as_fixnum()) + }, + _ => None, + }) + } Insn::FixnumMod { left, right, .. } => { self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) { (Some(l), Some(r)) if r != 0 => { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 440d8a5d17d83a..2054eb2c67d910 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -243,6 +243,117 @@ mod hir_opt_tests { "); } + #[test] + fn test_fold_fixnum_div() { + eval(" + def test + 7 / 3 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[7] = Const Value(7) + v12:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Integer@0x1000, /@0x1008, cme:0x1010) + v25:Fixnum[2] = Const Value(2) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_fold_fixnum_div_zero() { + eval(" + def test + 7 / 0 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[7] = Const Value(7) + v12:Fixnum[0] = Const Value(0) + PatchPoint MethodRedefined(Integer@0x1000, /@0x1008, cme:0x1010) + v23:Fixnum = FixnumDiv v10, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn test_fold_fixnum_div_negative() { + eval(" + def test + 7 / -3 + end + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[7] = Const Value(7) + v12:Fixnum[-3] = Const Value(-3) + PatchPoint MethodRedefined(Integer@0x1000, /@0x1008, cme:0x1010) + v25:Fixnum[-3] = Const Value(-3) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_dont_fold_fixnum_div_negative_one_overflow() { + eval(&format!(" + def test + {RUBY_FIXNUM_MIN} / -1 + end + ")); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[-4611686018427387904] = Const Value(-4611686018427387904) + v12:Fixnum[-1] = Const Value(-1) + PatchPoint MethodRedefined(Integer@0x1000, /@0x1008, cme:0x1010) + v23:Fixnum = FixnumDiv v10, v12 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v23 + "); + } #[test] fn test_fold_fixnum_mod_zero_by_zero() { From 980bc396c5e2ca7c076332704e20c944f73576e7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 6 Mar 2026 12:33:34 -0500 Subject: [PATCH 33/87] ZJIT: Count LoadField and StoreField executions (#16318) ## Runtime Counters on lobsters (before/after Jacob's load-store opt) | Stat | Before | After | Delta | |------|--------|-------|-------| | load_field_count | 110,410,542 | 106,547,275 | -3,863,267 (-3.5%) | | store_field_count | 9,196,747 | 9,196,759 | ~unchanged | | guard_shape_count | 31,973,410 | 29,743,901 | -2,229,509 (-7.0%) | --- zjit.rb | 3 +++ zjit/src/codegen.rs | 2 ++ zjit/src/stats.rs | 3 +++ 3 files changed, 8 insertions(+) diff --git a/zjit.rb b/zjit.rb index d98be33cd127b6..f2cd5330f414ce 100644 --- a/zjit.rb +++ b/zjit.rb @@ -253,6 +253,9 @@ def stats_string :guard_shape_count, :guard_shape_exit_ratio, + :load_field_count, + :store_field_count, + :side_exit_size, :code_region_bytes, :side_exit_size_ratio, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a715aaae9b9581..281c2d6e27d0a5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1210,12 +1210,14 @@ fn gen_load_self() -> Opnd { } fn gen_load_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, return_type: Type) -> Opnd { + gen_incr_counter(asm, Counter::load_field_count); asm_comment!(asm, "Load field id={} offset={}", id.contents_lossy(), offset); let recv = asm.load(recv); asm.load(Opnd::mem(return_type.num_bits(), recv, offset)) } fn gen_store_field(asm: &mut Assembler, recv: Opnd, id: ID, offset: i32, val: Opnd, val_type: Type) { + gen_incr_counter(asm, Counter::store_field_count); asm_comment!(asm, "Store field id={} offset={}", id.contents_lossy(), offset); let recv = asm.load(recv); asm.store(Opnd::mem(val_type.num_bits(), recv, offset), val); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index aca4ae8051657b..9d832420a6e776 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -436,6 +436,9 @@ make_counters! { guard_type_count, guard_shape_count, + load_field_count, + store_field_count, + invokeblock_handler_monomorphic_iseq, invokeblock_handler_monomorphic_ifunc, invokeblock_handler_monomorphic_other, From 9ecac8d4690ca4cf4e575bc2fdd31c99aa82e754 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 6 Mar 2026 13:21:23 -0500 Subject: [PATCH 34/87] [ruby/prism] Fix the handling of `do` on commands Introduce PM_TOKEN_KEYWORD_DO_BLOCK to distinguish do-blocks on command-style calls from regular `do` keywords. Add parse_command_do_block to attach these blocks to call nodes. Track in_endless_def_body to prevent do-block consumption inside endless method definitions, allowing blocks to correctly bubble up to outer contexts like `private def f = puts "Hello" do end`. https://github.com/ruby/prism/commit/7d17fd254b --- lib/prism/lex_compat.rb | 1 + lib/prism/translation/parser/lexer.rb | 1 + prism/config.yml | 2 + prism/parser.h | 7 ++ prism/prism.c | 88 +++++++++++++++---- prism/templates/src/token_type.c.erb | 2 + test/prism/errors/def_endless_do.txt | 5 +- .../4.0/endless_methods_command_call.txt | 3 + test/prism/fixtures/blocks.txt | 8 ++ test/prism/fixtures/endless_methods.txt | 4 + 10 files changed, 104 insertions(+), 17 deletions(-) diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 99d8daacdd26cf..0bc56ec592ad85 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -134,6 +134,7 @@ def deconstruct_keys(keys) # :nodoc: KEYWORD_DEF: :on_kw, KEYWORD_DEFINED: :on_kw, KEYWORD_DO: :on_kw, + KEYWORD_DO_BLOCK: :on_kw, KEYWORD_DO_LOOP: :on_kw, KEYWORD_ELSE: :on_kw, KEYWORD_ELSIF: :on_kw, diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index 8e18a3cd1e6454..e82042867f074f 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -87,6 +87,7 @@ class Lexer # :nodoc: KEYWORD_DEF: :kDEF, KEYWORD_DEFINED: :kDEFINED, KEYWORD_DO: :kDO, + KEYWORD_DO_BLOCK: :kDO_BLOCK, KEYWORD_DO_LOOP: :kDO_COND, KEYWORD_END: :kEND, KEYWORD_END_UPCASE: :klEND, diff --git a/prism/config.yml b/prism/config.yml index d8a10bc11310b7..c82c239de64c87 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -494,6 +494,8 @@ tokens: comment: "def" - name: KEYWORD_DEFINED comment: "defined?" + - name: KEYWORD_DO_BLOCK + comment: "do keyword for a block attached to a command" - name: KEYWORD_DO_LOOP comment: "do keyword for a predicate in a while, until, or for loop" - name: KEYWORD_END_UPCASE diff --git a/prism/parser.h b/prism/parser.h index c76fba58cfd63f..ed4871197c6f02 100644 --- a/prism/parser.h +++ b/prism/parser.h @@ -885,6 +885,13 @@ struct pm_parser { /** Whether or not we're at the beginning of a command. */ bool command_start; + /** + * Whether or not we're currently parsing the body of an endless method + * definition. In this context, PM_TOKEN_KEYWORD_DO_BLOCK should not be + * consumed by commands (it should bubble up to the outer context). + */ + bool in_endless_def_body; + /** Whether or not we're currently recovering from a syntax error. */ bool recovering; diff --git a/prism/prism.c b/prism/prism.c index 6f21c97bc3b48f..efe21c5e7e5725 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8330,9 +8330,15 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) { switch (width) { case 2: if (lex_keyword(parser, current_start, "do", width, PM_LEX_STATE_BEG, PM_TOKEN_KEYWORD_DO, PM_TOKEN_EOF) != PM_TOKEN_EOF) { + if (parser->enclosure_nesting == parser->lambda_enclosure_nesting) { + return PM_TOKEN_KEYWORD_DO; + } if (pm_do_loop_stack_p(parser)) { return PM_TOKEN_KEYWORD_DO_LOOP; } + if (!pm_accepts_block_stack_p(parser)) { + return PM_TOKEN_KEYWORD_DO_BLOCK; + } return PM_TOKEN_KEYWORD_DO; } @@ -12497,6 +12503,7 @@ token_begins_expression_p(pm_token_type_t type) { case PM_TOKEN_EOF: case PM_TOKEN_LAMBDA_BEGIN: case PM_TOKEN_KEYWORD_DO: + case PM_TOKEN_KEYWORD_DO_BLOCK: case PM_TOKEN_KEYWORD_DO_LOOP: case PM_TOKEN_KEYWORD_END: case PM_TOKEN_KEYWORD_ELSE: @@ -14825,6 +14832,27 @@ parse_block(pm_parser_t *parser, uint16_t depth) { return pm_block_node_create(parser, &locals, &opening, parameters, statements, &parser->previous); } +/** + * Attach a do-block (PM_TOKEN_KEYWORD_DO_BLOCK) to a command-style call node. + * The current token must be PM_TOKEN_KEYWORD_DO_BLOCK when this is called. + */ +static void +parse_command_do_block(pm_parser_t *parser, pm_call_node_t *call, uint16_t depth) { + parser_lex(parser); + pm_block_node_t *block = parse_block(parser, (uint16_t) (depth + 1)); + + if (call->block != NULL) { + pm_parser_err_node(parser, UP(block), PM_ERR_ARGUMENT_BLOCK_MULTI); + if (call->arguments == NULL) { + call->arguments = pm_arguments_node_create(parser); + } + pm_arguments_node_arguments_append(parser->arena, call->arguments, call->block); + } + + call->block = UP(block); + PM_NODE_LENGTH_SET_NODE(call, block); +} + /** * Parse a list of arguments and their surrounding parentheses if they are * present. It returns true if it found any pieces of arguments (parentheses, @@ -14833,6 +14861,7 @@ parse_block(pm_parser_t *parser, uint16_t depth) { static bool parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_block, bool accepts_command_call, uint16_t depth) { bool found = false; + bool parsed_command_args = false; if (accept1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { found |= true; @@ -14855,6 +14884,7 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } } else if (accepts_command_call && (token_begins_expression_p(parser->current.type) || match3(parser, PM_TOKEN_USTAR, PM_TOKEN_USTAR_STAR, PM_TOKEN_UAMPERSAND)) && !match1(parser, PM_TOKEN_BRACE_LEFT)) { found |= true; + parsed_command_args = true; pm_accepts_block_stack_push(parser, false); // If we get here, then the subsequent token cannot be used as an infix @@ -14885,6 +14915,9 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } else if (pm_accepts_block_stack_p(parser) && accept1(parser, PM_TOKEN_KEYWORD_DO)) { found |= true; block = parse_block(parser, (uint16_t) (depth + 1)); + } else if (parsed_command_args && pm_accepts_block_stack_p(parser) && !parser->in_endless_def_body && accept1(parser, PM_TOKEN_KEYWORD_DO_BLOCK)) { + found |= true; + block = parse_block(parser, (uint16_t) (depth + 1)); } if (block != NULL) { @@ -15300,7 +15333,7 @@ parse_conditional(pm_parser_t *parser, pm_context_t context, size_t opening_newl #define PM_CASE_KEYWORD PM_TOKEN_KEYWORD___ENCODING__: case PM_TOKEN_KEYWORD___FILE__: case PM_TOKEN_KEYWORD___LINE__: \ case PM_TOKEN_KEYWORD_ALIAS: case PM_TOKEN_KEYWORD_AND: case PM_TOKEN_KEYWORD_BEGIN: case PM_TOKEN_KEYWORD_BEGIN_UPCASE: \ case PM_TOKEN_KEYWORD_BREAK: case PM_TOKEN_KEYWORD_CASE: case PM_TOKEN_KEYWORD_CLASS: case PM_TOKEN_KEYWORD_DEF: \ - case PM_TOKEN_KEYWORD_DEFINED: case PM_TOKEN_KEYWORD_DO: case PM_TOKEN_KEYWORD_DO_LOOP: case PM_TOKEN_KEYWORD_ELSE: \ + case PM_TOKEN_KEYWORD_DEFINED: case PM_TOKEN_KEYWORD_DO: case PM_TOKEN_KEYWORD_DO_BLOCK: case PM_TOKEN_KEYWORD_DO_LOOP: case PM_TOKEN_KEYWORD_ELSE: \ case PM_TOKEN_KEYWORD_ELSIF: case PM_TOKEN_KEYWORD_END: case PM_TOKEN_KEYWORD_END_UPCASE: case PM_TOKEN_KEYWORD_ENSURE: \ case PM_TOKEN_KEYWORD_FALSE: case PM_TOKEN_KEYWORD_FOR: case PM_TOKEN_KEYWORD_IF: case PM_TOKEN_KEYWORD_IN: \ case PM_TOKEN_KEYWORD_MODULE: case PM_TOKEN_KEYWORD_NEXT: case PM_TOKEN_KEYWORD_NIL: case PM_TOKEN_KEYWORD_NOT: \ @@ -17486,7 +17519,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b element = UP(pm_keyword_hash_node_create(parser)); pm_static_literals_t hash_keys = { 0 }; - if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_EOF, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) { + if (!match8(parser, PM_TOKEN_EOF, PM_TOKEN_NEWLINE, PM_TOKEN_SEMICOLON, PM_TOKEN_KEYWORD_DO_BLOCK, PM_TOKEN_BRACE_RIGHT, PM_TOKEN_BRACKET_RIGHT, PM_TOKEN_KEYWORD_DO, PM_TOKEN_PARENTHESIS_RIGHT)) { parse_assocs(parser, &hash_keys, element, (uint16_t) (depth + 1)); } @@ -18895,20 +18928,30 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b allow_command_call = binding_power == PM_BINDING_POWER_ASSIGNMENT || binding_power < PM_BINDING_POWER_COMPOSITION; } + // Inside a def body, we push true onto the + // accepts_block_stack so that `do` is lexed as + // PM_TOKEN_KEYWORD_DO (which can only start a block for + // primary-level constructs, not commands). During command + // argument parsing, the stack is pushed to false, causing + // `do` to be lexed as PM_TOKEN_KEYWORD_DO_BLOCK, which + // is not consumed inside the endless def body and instead + // left for the outer context. + pm_accepts_block_stack_push(parser, true); + bool previous_in_endless_def_body = parser->in_endless_def_body; + parser->in_endless_def_body = true; pm_node_t *statement = parse_expression(parser, PM_BINDING_POWER_DEFINED + 1, allow_command_call, false, PM_ERR_DEF_ENDLESS, (uint16_t) (depth + 1)); + parser->in_endless_def_body = previous_in_endless_def_body; + pm_accepts_block_stack_pop(parser); - // In an endless method definition, the body is not allowed to - // be a command with a do..end block. - if (PM_NODE_TYPE_P(statement, PM_CALL_NODE)) { - pm_call_node_t *call = (pm_call_node_t *) statement; - - if (call->arguments != NULL && call->block != NULL && PM_NODE_TYPE_P(call->block, PM_BLOCK_NODE)) { - pm_block_node_t *block = (pm_block_node_t *) call->block; - - if (parser->start[block->opening_loc.start] != '{') { - pm_parser_err_node(parser, call->block, PM_ERR_DEF_ENDLESS_DO_BLOCK); - } - } + // If an unconsumed PM_TOKEN_KEYWORD_DO follows the body, + // it is an error (e.g., `def f = 1 do end`). + // PM_TOKEN_KEYWORD_DO_BLOCK is intentionally not caught + // here — it should bubble up to the outer context (e.g., + // `private def f = puts "Hello" do end` where the block + // attaches to `private`). + if (accept1(parser, PM_TOKEN_KEYWORD_DO)) { + pm_block_node_t *block = parse_block(parser, (uint16_t) (depth + 1)); + pm_parser_err_node(parser, UP(block), PM_ERR_DEF_ENDLESS_DO_BLOCK); } if (accept1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { @@ -20066,9 +20109,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b opening = parser->previous; if (!match3(parser, PM_TOKEN_KEYWORD_END, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { - pm_accepts_block_stack_push(parser, true); body = UP(parse_statements(parser, PM_CONTEXT_LAMBDA_DO_END, (uint16_t) (depth + 1))); - pm_accepts_block_stack_pop(parser); } if (match2(parser, PM_TOKEN_KEYWORD_RESCUE, PM_TOKEN_KEYWORD_ENSURE)) { @@ -21518,6 +21559,14 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc } break; case PM_CALL_NODE: + // A do-block can attach to a command-style call at the + // primary level. Inside an endless def body, DO_BLOCK must + // not be consumed so it can bubble up to the outer context + // (e.g., `private` in `private def f = bar baz do end`). + if (match1(parser, PM_TOKEN_KEYWORD_DO_BLOCK) && !parser->in_endless_def_body && pm_accepts_block_stack_p(parser) && pm_call_node_command_p((pm_call_node_t *) node)) { + parse_command_do_block(parser, (pm_call_node_t *) node, depth); + } + // If we have a call node, then we need to check if it looks like a // method call without parentheses that contains arguments. If it // does, then it has different rules for parsing infix operators, @@ -21573,6 +21622,13 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc } break; case PM_CALL_NODE: + // A do-block can attach to a command-style call + // produced by infix operators (e.g., dot-calls like + // `obj.method args do end`). + if (match1(parser, PM_TOKEN_KEYWORD_DO_BLOCK) && !parser->in_endless_def_body && pm_accepts_block_stack_p(parser) && pm_call_node_command_p((pm_call_node_t *) node)) { + parse_command_do_block(parser, (pm_call_node_t *) node, depth); + } + // These expressions are also statements, by virtue of the // right-hand side of the expression (i.e., the last argument to // the call node) being an implicit array. diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 5c6f2713100f91..94e41ec4ba1a2b 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -167,6 +167,8 @@ pm_token_type_human(pm_token_type_t token_type) { return "'defined?'"; case PM_TOKEN_KEYWORD_DO: return "'do'"; + case PM_TOKEN_KEYWORD_DO_BLOCK: + return "'do'"; case PM_TOKEN_KEYWORD_DO_LOOP: return "'do'"; case PM_TOKEN_KEYWORD_ELSE: diff --git a/test/prism/errors/def_endless_do.txt b/test/prism/errors/def_endless_do.txt index 4d786638a685b9..d66b7086da2d3f 100644 --- a/test/prism/errors/def_endless_do.txt +++ b/test/prism/errors/def_endless_do.txt @@ -1,3 +1,6 @@ def a = a b do 1 end - ^~~~~~~~ unexpected `do` for block in an endless method definition + ^~ unexpected 'do', expecting end-of-input + ^~ unexpected 'do', ignoring it + ^~~ unexpected 'end', expecting end-of-input + ^~~ unexpected 'end', ignoring it diff --git a/test/prism/fixtures/4.0/endless_methods_command_call.txt b/test/prism/fixtures/4.0/endless_methods_command_call.txt index 91a9d156d5538b..146a6ee579d3c5 100644 --- a/test/prism/fixtures/4.0/endless_methods_command_call.txt +++ b/test/prism/fixtures/4.0/endless_methods_command_call.txt @@ -6,3 +6,6 @@ private def foo(x) = puts x private def obj.foo = puts "Hello" private def obj.foo() = puts "Hello" private def obj.foo(x) = puts x + +private def foo = bar baz +private def foo = bar baz do expr end diff --git a/test/prism/fixtures/blocks.txt b/test/prism/fixtures/blocks.txt index e33d95c150b375..51ec84950c36e1 100644 --- a/test/prism/fixtures/blocks.txt +++ b/test/prism/fixtures/blocks.txt @@ -52,3 +52,11 @@ foo lambda { | } foo do |bar,| end + +foo bar baz, qux do end + +foo.bar baz do end + +foo.bar baz do end.qux quux do end + +foo bar, baz do |x| x end diff --git a/test/prism/fixtures/endless_methods.txt b/test/prism/fixtures/endless_methods.txt index 7eb3bf431897b1..6e0488a5ee9d66 100644 --- a/test/prism/fixtures/endless_methods.txt +++ b/test/prism/fixtures/endless_methods.txt @@ -5,3 +5,7 @@ def bar = A "" def method = 1 + 2 + 3 x = def f = p 1 + +def foo = bar baz + +def foo = bar(baz) From 6533b101e3d19a217d8ae2d994bbcec5c4c45307 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 6 Mar 2026 15:57:13 -0500 Subject: [PATCH 35/87] [ruby/prism] Fix precedence of infix operators after command https://github.com/ruby/prism/commit/35470bb90d --- prism/prism.c | 17 ++++++++++++++++- test/prism/errors/command_call_in_2.txt | 4 ++++ test/prism/errors/command_call_in_3.txt | 4 ++++ test/prism/errors/command_call_in_4.txt | 4 ++++ test/prism/errors/command_call_in_5.txt | 4 ++++ test/prism/errors/command_call_in_6.txt | 4 ++++ test/prism/errors/command_call_in_7.txt | 4 ++++ test/prism/errors_test.rb | 4 ---- 8 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 test/prism/errors/command_call_in_2.txt create mode 100644 test/prism/errors/command_call_in_3.txt create mode 100644 test/prism/errors/command_call_in_4.txt create mode 100644 test/prism/errors/command_call_in_5.txt create mode 100644 test/prism/errors/command_call_in_6.txt create mode 100644 test/prism/errors/command_call_in_7.txt diff --git a/prism/prism.c b/prism/prism.c index efe21c5e7e5725..2e2d6bcbd4b3cf 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21621,7 +21621,7 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc return node; } break; - case PM_CALL_NODE: + case PM_CALL_NODE: { // A do-block can attach to a command-style call // produced by infix operators (e.g., dot-calls like // `obj.method args do end`). @@ -21635,7 +21635,22 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc if (PM_NODE_FLAG_P(node, PM_CALL_NODE_FLAGS_IMPLICIT_ARRAY) && pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { return node; } + + // Command-style calls (calls with arguments but without + // parentheses) only accept composition (and/or) and modifier + // (if/unless/etc.) operators. We need to exclude operator calls + // (e.g., a + b) which also satisfy pm_call_node_command_p but + // are not commands. + const pm_call_node_t *cast = (const pm_call_node_t *) node; + if ( + (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_COMPOSITION) && + (cast->receiver == NULL || cast->call_operator_loc.length > 0) && + pm_call_node_command_p(cast) + ) { + return node; + } break; + } case PM_RESCUE_MODIFIER_NODE: // A rescue modifier whose handler is a one-liner pattern match // (=> or in) produces a statement. That means it cannot be diff --git a/test/prism/errors/command_call_in_2.txt b/test/prism/errors/command_call_in_2.txt new file mode 100644 index 00000000000000..6676b1acba7968 --- /dev/null +++ b/test/prism/errors/command_call_in_2.txt @@ -0,0 +1,4 @@ +a.b x in pattern + ^~ unexpected 'in', expecting end-of-input + ^~ unexpected 'in', ignoring it + diff --git a/test/prism/errors/command_call_in_3.txt b/test/prism/errors/command_call_in_3.txt new file mode 100644 index 00000000000000..6fe026d7d36e1b --- /dev/null +++ b/test/prism/errors/command_call_in_3.txt @@ -0,0 +1,4 @@ +a.b x: in pattern + ^~ unexpected 'in', expecting end-of-input + ^~ unexpected 'in', ignoring it + diff --git a/test/prism/errors/command_call_in_4.txt b/test/prism/errors/command_call_in_4.txt new file mode 100644 index 00000000000000..045afe6498696c --- /dev/null +++ b/test/prism/errors/command_call_in_4.txt @@ -0,0 +1,4 @@ +a.b &x in pattern + ^~ unexpected 'in', expecting end-of-input + ^~ unexpected 'in', ignoring it + diff --git a/test/prism/errors/command_call_in_5.txt b/test/prism/errors/command_call_in_5.txt new file mode 100644 index 00000000000000..be07287f81145b --- /dev/null +++ b/test/prism/errors/command_call_in_5.txt @@ -0,0 +1,4 @@ +a.b *x => pattern + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + diff --git a/test/prism/errors/command_call_in_6.txt b/test/prism/errors/command_call_in_6.txt new file mode 100644 index 00000000000000..470f323872df0b --- /dev/null +++ b/test/prism/errors/command_call_in_6.txt @@ -0,0 +1,4 @@ +a.b x: => pattern + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + diff --git a/test/prism/errors/command_call_in_7.txt b/test/prism/errors/command_call_in_7.txt new file mode 100644 index 00000000000000..a8bea912b5d1ce --- /dev/null +++ b/test/prism/errors/command_call_in_7.txt @@ -0,0 +1,4 @@ +a.b &x => pattern + ^~ unexpected '=>', expecting end-of-input + ^~ unexpected '=>', ignoring it + diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 898f4afb45f20f..c3362eaaf56e5c 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -60,10 +60,6 @@ def test_unterminated_empty_string_closing assert_nil statement.closing end - def test_invalid_message_name - assert_equal :"", Prism.parse_statement("+.@foo,+=foo").write_name - end - def test_regexp_encoding_option_mismatch_error # UTF-8 char with ASCII-8BIT modifier result = Prism.parse('/Ȃ/n') From fd9448bc87a51482564ac997e0c1c6e5f36e7927 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 6 Mar 2026 16:01:02 -0500 Subject: [PATCH 36/87] [ruby/prism] Fix not without parentheses binding power https://github.com/ruby/prism/commit/7d21e564ac --- prism/prism.c | 10 ++++++---- test/prism/errors/not_without_parens_assignment.txt | 4 ++++ test/prism/errors/not_without_parens_call.txt | 7 +++++++ test/prism/errors/not_without_parens_command.txt | 4 ++++ test/prism/errors/not_without_parens_command_call.txt | 4 ++++ test/prism/errors/not_without_parens_return.txt | 4 ++++ 6 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 test/prism/errors/not_without_parens_assignment.txt create mode 100644 test/prism/errors/not_without_parens_call.txt create mode 100644 test/prism/errors/not_without_parens_command.txt create mode 100644 test/prism/errors/not_without_parens_command_call.txt create mode 100644 test/prism/errors/not_without_parens_return.txt diff --git a/prism/prism.c b/prism/prism.c index 2e2d6bcbd4b3cf..18aa841ec9bebc 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -19195,10 +19195,12 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_arguments_t arguments = { 0 }; pm_node_t *receiver = NULL; - // If we do not accept a command call, then we also do not accept a - // not without parentheses. In this case we need to reject this - // syntax. - if (!accepts_command_call && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { + // The `not` keyword without parentheses is only valid in contexts + // where it would be parsed as an expression (i.e., at or below + // the `not` binding power level). In other contexts (e.g., method + // arguments, array elements, assignment right-hand sides), + // parentheses are required: `not(x)`. + if (binding_power > PM_BINDING_POWER_NOT && !match1(parser, PM_TOKEN_PARENTHESIS_LEFT)) { if (match1(parser, PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES)) { pm_parser_err(parser, PM_TOKEN_END(parser, &parser->previous), 1, PM_ERR_EXPECT_LPAREN_AFTER_NOT_LPAREN); } else { diff --git a/test/prism/errors/not_without_parens_assignment.txt b/test/prism/errors/not_without_parens_assignment.txt new file mode 100644 index 00000000000000..32d58efedf0642 --- /dev/null +++ b/test/prism/errors/not_without_parens_assignment.txt @@ -0,0 +1,4 @@ +x = not y + ^ expected a `(` after `not` + ^ unexpected local variable or method, expecting end-of-input + diff --git a/test/prism/errors/not_without_parens_call.txt b/test/prism/errors/not_without_parens_call.txt new file mode 100644 index 00000000000000..a77819340090ac --- /dev/null +++ b/test/prism/errors/not_without_parens_call.txt @@ -0,0 +1,7 @@ +foo(not y) + ^ expected a `(` after `not` + ^ unexpected local variable or method; expected a `)` to close the arguments + ^ unexpected local variable or method, expecting end-of-input + ^ unexpected ')', expecting end-of-input + ^ unexpected ')', ignoring it + diff --git a/test/prism/errors/not_without_parens_command.txt b/test/prism/errors/not_without_parens_command.txt new file mode 100644 index 00000000000000..957a06f8f1b92e --- /dev/null +++ b/test/prism/errors/not_without_parens_command.txt @@ -0,0 +1,4 @@ +foo not y + ^ expected a `(` after `not` + ^ unexpected local variable or method, expecting end-of-input + diff --git a/test/prism/errors/not_without_parens_command_call.txt b/test/prism/errors/not_without_parens_command_call.txt new file mode 100644 index 00000000000000..564833c7ded6a8 --- /dev/null +++ b/test/prism/errors/not_without_parens_command_call.txt @@ -0,0 +1,4 @@ +a.b not y + ^ expected a `(` after `not` + ^ unexpected local variable or method, expecting end-of-input + diff --git a/test/prism/errors/not_without_parens_return.txt b/test/prism/errors/not_without_parens_return.txt new file mode 100644 index 00000000000000..1c7edb6ff1497d --- /dev/null +++ b/test/prism/errors/not_without_parens_return.txt @@ -0,0 +1,4 @@ +return not y + ^ expected a `(` after `not` + ^ unexpected local variable or method, expecting end-of-input + From b324803732c8568738a4484be495dcae56997086 Mon Sep 17 00:00:00 2001 From: Nery Campusano Date: Fri, 6 Mar 2026 19:54:53 -0500 Subject: [PATCH 37/87] ZJIT: Add execution counters for JIT-compiled code paths (#16315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds runtime execution tracking for ZJIT-compiled iseqs so we can identify which compiled methods are called most frequently. **Why?** 1. Determining what can be optimized based on how often it's called: knowing call frequency helps prioritize which methods are worth spending time on, and focus improvements on the hottest paths, so ZJIT work has the most impact. 2. Memory eviction: ZJIT has a fixed allocated code region size, and when it fills up it stops compiling new methods. This data could help decide which low-usage methods to drop to make room. **How?** A `HashMap>` is added to `ZJITState` mapping each compiled iseq's location string to a counter. The counter is an `IncrCounterPtr` HIR instruction inserted into the JIT entry block (bb2) during HIR construction in `compile_jit_entry_block`, before the block gets lowered to machine code by `gen_function`. The placement here matters `jit_entry_ptrs` are recorded via `pos_marker` on the `EntryPoint` instruction. By inserting the counter in the HIR it ends up after EntryPoint in the instruction stream, so it actually gets executed on every JIT call. My first attempt emitted the counter directly in `gen_function` before writing the block label, which silently placed it before EntryPoint — making it unreachable since stubs jump to the EntryPoint address, skipping the counter entirely for certain methods. Box is owned by ZJITState for the lifetime of the program so the pointer stays stable across HashMap resizes. Everything is gated behind --zjit-stats, exposed through RubyVM::ZJIT.runtime_stats, and printed as "hottest code paths." Ran zjit-stats on the lobsters [benchmark](https://github.com/ruby/ruby-bench/blob/main/benchmarks/lobsters/benchmark.rb) ```cli Top-20 hottest code paths (20.9% of total 48,150,957): []@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activesupport-8.1.1/lib/active_support/isolated_execution_state.rb:32: 996,342 ( 2.1%) attribute_types@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activemodel-8.1.1/lib/active_model/attribute_registration.rb:38: 796,188 ( 1.7%) block in redefine@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activesupport-8.1.1/lib/active_support/class_attribute.rb:15: 702,587 ( 1.5%) klass@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/reflection.rb:423: 645,927 ( 1.3%) _read_attribute@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/attribute_methods/read.rb:39: 601,522 ( 1.2%) get_or_default@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb:111: 477,348 ( 1.0%) fetch@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/type/type_map.rb:19: 469,970 ( 1.0%) fetch@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/map.rb:183: 469,970 ( 1.0%) fetch_or_store@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/concurrent-ruby-1.3.5/lib/concurrent-ruby/concurrent/map.rb:205: 469,970 ( 1.0%) extended_type_map_key@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:1173: 469,970 ( 1.0%) type_map@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/connection_adapters/abstract_adapter.rb:1179: 469,941 ( 1.0%) fetch@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/result.rb:76: 449,631 ( 0.9%) context@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activesupport-8.1.1/lib/active_support/isolated_execution_state.rb:55: 445,689 ( 0.9%) <<@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/arel/collectors/plain_string.rb:15: 428,350 ( 0.9%) foreign_key@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/reflection.rb:559: 405,255 ( 0.8%) period@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activesupport-8.1.1/lib/active_support/time_with_zone.rb:79: 400,182 ( 0.8%) klass@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/associations/association.rb:166: 346,231 ( 0.7%) loaded?@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activerecord-8.1.1/lib/active_record/associations/association.rb:82: 344,651 ( 0.7%) get_header@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/rack-2.2.19/lib/rack/request.rb:63: 341,545 ( 0.7%) cast@/Users/nerycampusano/.gem/ruby/ruby-zjit/gems/activemodel-8.1.1/lib/active_model/type/value.rb:58: 326,008 ( 0.7%) ``` --- zjit.rb | 1 + zjit/src/hir.rs | 12 ++++++++++++ zjit/src/state.rs | 9 +++++++++ zjit/src/stats.rs | 11 +++++++++++ 4 files changed, 33 insertions(+) diff --git a/zjit.rb b/zjit.rb index f2cd5330f414ce..82630612749f44 100644 --- a/zjit.rb +++ b/zjit.rb @@ -184,6 +184,7 @@ def stats_string # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'iseq_calls_count_', prompt: 'most called JIT functions', buf:, stats:, limit: 20) # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated C methods', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 65c49444243145..200502af52f880 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4446,6 +4446,15 @@ impl Function { self.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); } + fn count_iseq_calls(&mut self, block: BlockId) { + let iseq_name = iseq_get_location(self.iseq, 0); + let access_counter_ptrs = crate::state::ZJITState::get_iseq_calls_count_pointers(); + let counter_ptr = access_counter_ptrs.entry(iseq_name.to_string()).or_insert_with(|| Box::new(0)); + let counter_ptr: &mut u64 = counter_ptr.as_mut(); + + self.push_insn(block, Insn::IncrCounterPtr { counter_ptr }); + } + fn count_not_annotated_cfunc(&mut self, block: BlockId, cme: *const rb_callable_method_entry_t) { let owner = unsafe { (*cme).owner }; let called_id = unsafe { (*cme).called_id }; @@ -8050,6 +8059,9 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc // Prepare entry_state with basic block params let (self_param, entry_state) = compile_jit_entry_state(fun, jit_entry_block, jit_entry_idx); + if get_option!(stats) { + fun.count_iseq_calls(jit_entry_block); + } // Jump to target_block fun.push_insn(jit_entry_block, Insn::Jump(BranchEdge { target: target_block, args: entry_state.as_args(self_param) })); } diff --git a/zjit/src/state.rs b/zjit/src/state.rs index a7851b2607132a..df8239699ff5e2 100644 --- a/zjit/src/state.rs +++ b/zjit/src/state.rs @@ -62,6 +62,9 @@ pub struct ZJITState { /// Counter pointers for all calls to any kind of C function from JIT code ccall_counter_pointers: HashMap>, + /// Counter pointers for access counts of ISEQs accessed by JIT code + iseq_calls_count_pointers: HashMap>, + /// Locations of side exists within generated code exit_locations: Option, } @@ -139,6 +142,7 @@ impl ZJITState { full_frame_cfunc_counter_pointers: HashMap::new(), not_annotated_frame_cfunc_counter_pointers: HashMap::new(), ccall_counter_pointers: HashMap::new(), + iseq_calls_count_pointers: HashMap::new(), exit_locations, }; unsafe { ZJIT_STATE = Enabled(zjit_state); } @@ -224,6 +228,11 @@ impl ZJITState { &mut ZJITState::get_instance().ccall_counter_pointers } + /// Get a mutable reference to iseq access count pointers + pub fn get_iseq_calls_count_pointers() -> &'static mut HashMap> { + &mut ZJITState::get_instance().iseq_calls_count_pointers + } + /// Was --zjit-save-compiled-iseqs specified? pub fn should_log_compiled_iseqs() -> bool { get_option!(log_compiled_iseqs).is_some() diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 9d832420a6e776..db204f032f9340 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -772,6 +772,10 @@ pub extern "C" fn rb_zjit_reset_stats_bang(_ec: EcPtr, _self: VALUE) -> VALUE { ZJITState::get_ccall_counter_pointers().iter_mut() .for_each(|b| { **(b.1) = 0; }); + // Reset iseq call counters + ZJITState::get_iseq_calls_count_pointers().iter_mut() + .for_each(|b| { **(b.1) = 0; }); + Qnil } @@ -937,6 +941,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> set_stat_usize!(hash, &key_string, **counter); } + // Set iseq access counters + let iseq_access_counts = ZJITState::get_iseq_calls_count_pointers(); + for (iseq_name, counter) in iseq_access_counts.iter() { + let key_string = format!("iseq_calls_count_{iseq_name}"); + set_stat_usize!(hash, &key_string, **counter); + } + hash } From 55694ad7efc3f8dc6d5c7aefa60ded4c303ed6cf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 10:39:46 +0900 Subject: [PATCH 38/87] [Bug #21945] Correctly handle `and?` and similar --- parse.y | 3 +++ test/ripper/test_lexer.rb | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/parse.y b/parse.y index b91b141cc65375..bcff7918bfa4c3 100644 --- a/parse.y +++ b/parse.y @@ -6978,6 +6978,9 @@ peek_word_at(struct parser_params *p, const char *str, size_t len, int at) if (lex_eol_ptr_n_p(p, ptr, len-1)) return false; if (memcmp(ptr, str, len)) return false; if (lex_eol_ptr_n_p(p, ptr, len)) return true; + switch (ptr[len]) { + case '!': case '?': return false; + } return !is_identchar(p, ptr+len, p->lex.pend, p->enc); } diff --git a/test/ripper/test_lexer.rb b/test/ripper/test_lexer.rb index 7a2c22ff2d1dcb..4bc6fd7ced05df 100644 --- a/test/ripper/test_lexer.rb +++ b/test/ripper/test_lexer.rb @@ -586,6 +586,58 @@ def test_spaces_at_eof assert_lexer(expected, code) end + def test_fluent_and + code = "foo\n" "and" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)], + [[2, 0], :on_kw, "and", state(:EXPR_BEG)], + ] + assert_lexer(expected, code) + + code = "foo\n" "and?" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_ident, "and?", state(:EXPR_CMDARG)], + ] + assert_lexer(expected, code) + + code = "foo\n" "and!" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_ident, "and!", state(:EXPR_CMDARG)], + ] + assert_lexer(expected, code) + end + + def test_fluent_or + code = "foo\n" "or" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_ignored_nl, "\n", state(:EXPR_CMDARG)], + [[2, 0], :on_kw, "or", state(:EXPR_BEG)], + ] + assert_lexer(expected, code) + + code = "foo\n" "or?" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_ident, "or?", state(:EXPR_CMDARG)], + ] + assert_lexer(expected, code) + + code = "foo\n" "or!" + expected = [ + [[1, 0], :on_ident, "foo", state(:EXPR_CMDARG)], + [[1, 3], :on_nl, "\n", state(:EXPR_BEG)], + [[2, 0], :on_ident, "or!", state(:EXPR_CMDARG)], + ] + assert_lexer(expected, code) + end + def assert_lexer(expected, code) assert_equal(code, Ripper.tokenize(code).join("")) assert_equal(expected, result = Ripper.lex(code), From 38c9f14b18942d03594781bbc3d5498397b4d03c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 13:04:37 +0900 Subject: [PATCH 39/87] vcs.rb: Extract `parse_iso_date` method --- tool/lib/vcs.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index ce545ec368eef9..8b06eb03645898 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -169,19 +169,7 @@ def get_revisions(path) ) last or raise VCS::NotFoundError, "last revision not found" changed or raise VCS::NotFoundError, "changed revision not found" - if modified - /\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ modified or - raise "unknown time format - #{modified}" - match = $~[1..6].map { |x| x.to_i } - off = $7 ? "#{$7}:#{$8}" : "+00:00" - match << off - begin - modified = Time.new(*match) - rescue ArgumentError - modified = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60 - end - modified = modified.getlocal(@zone) - end + modified &&= parse_iso_date(modified) return last, changed, modified, *rest end @@ -210,6 +198,20 @@ def relative_to(path) end end + def parse_iso_date(date) + /\A(\d+)-(\d+)-(\d+)\D(\d+):(\d+):(\d+(?:\.\d+)?)\s*(?:Z|([-+]\d\d)(\d\d))\z/ =~ date or + raise "unknown time format - #{date}" + match = $~[1..6].map { |x| x.to_i } + off = $7 ? "#{$7}:#{$8}" : "+00:00" + match << off + begin + date = Time.new(*match) + rescue ArgumentError + date = Time.utc(*$~[1..6]) + $7.to_i * 3600 + $8.to_i * 60 + end + date.getlocal(@zone) + end + def after_export(dir) FileUtils.rm_rf(Dir.glob("#{dir}/.git*")) FileUtils.rm_rf(Dir.glob("#{dir}/.mailmap")) From 9fd8dd4af94bc30170704bc7cbf635bfa99e1209 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 13:04:51 +0900 Subject: [PATCH 40/87] vcs.rb: Make `relative_to` accept the base directory name optionally --- tool/lib/vcs.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 8b06eb03645898..1f55d63c181f97 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -178,9 +178,9 @@ def modified(path) modified end - def relative_to(path) + def relative_to(path, srcdir = @srcdir) if path - srcdir = File.realpath(@srcdir) + srcdir = File.realpath(srcdir || @srcdir) path = File.realdirpath(path) list1 = srcdir.split(%r{/}) list2 = path.split(%r{/}) From f3e1dfc8f61a7b0f2053d259cf7e037020014470 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 13:05:47 +0900 Subject: [PATCH 41/87] vcs.rb: Add `VCS::GIT#author_date` method Returns the author date of the latest commit for the path. --- tool/lib/vcs.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tool/lib/vcs.rb b/tool/lib/vcs.rb index 1f55d63c181f97..d6374f9de05946 100644 --- a/tool/lib/vcs.rb +++ b/tool/lib/vcs.rb @@ -364,6 +364,11 @@ def _get_revisions(path, srcdir = nil) [last, changed, modified, branch, title] end + def author_date(path, srcdir = @srcdir) + log = cmd_read_at(srcdir, [[COMMAND, 'log', '-n1', '--pretty=%at', path]]) + Time.at(log.to_i, in: @zone) + end + def self.revision_name(rev) short_revision(rev) end From 12038f1997d40805d092340994c128d63e25a7b6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 13:12:15 +0900 Subject: [PATCH 42/87] [DOC] Update the date in man pages by the author date Prefer the date authored the contents over the merged date for the embedded dates. --- common.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.mk b/common.mk index 963af3987b55ef..349d2a87e30906 100644 --- a/common.mk +++ b/common.mk @@ -1900,7 +1900,7 @@ sudo-precheck: PHONY update-man-date: PHONY $(Q) $(BASERUBY) -I"$(tooldir)/lib" -rvcs -i -p \ -e 'BEGIN{@vcs=VCS.detect(ARGV.shift)}' \ - -e '$$_.sub!(/^(\.Dd ).*/){$$1+@vcs.modified(ARGF.path).strftime("%B %d, %Y")}' \ + -e '$$_.sub!(/^(\.Dd ).*/){$$1+@vcs.author_date(@vcs.relative_to(ARGF.path)).strftime("%B %d, %Y")}' \ "$(srcdir)" "$(srcdir)"/man/*.1 .PHONY: ChangeLog From 55df8dc063df1c749dbe07f78158f85a0ae47a99 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 7 Mar 2026 13:43:19 +0900 Subject: [PATCH 43/87] [DOC] Update the date in man pages if changed --- .github/workflows/check_misc.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 32cbcb53c8a7cf..a75b564654856e 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -52,6 +52,16 @@ 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 date in man pages is up-to-date + run: | + git fetch origin --depth=1 "${GITHUB_OLD_SHA}" + git diff --exit-code --name-only "${GITHUB_OLD_SHA}" HEAD -- man || + make V=1 GIT=git BASERUBY=ruby update-man-date + git diff --color --no-ext-diff --ignore-submodules --exit-code -- man + env: + GITHUB_OLD_SHA: ${{ github.event.pull_request.base.sha }} + if: ${{ startsWith(github.event_name, 'pull') }} + - name: Check for bash specific substitution in configure.ac run: | git grep -n '\${[A-Za-z_0-9]*/' -- configure.ac && exit 1 || : From 66e61d0cffca501f335175c6ca4948d535a0e86d Mon Sep 17 00:00:00 2001 From: git Date: Sat, 7 Mar 2026 07:00:25 +0000 Subject: [PATCH 44/87] [DOC] Update bundled gems list at 55df8dc063df1c749dbe07f78158f8 --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 79b5cc9ff87330..624440abc91f20 100644 --- a/NEWS.md +++ b/NEWS.md @@ -65,7 +65,7 @@ releases. * RubyGems 4.1.0.dev * bundler 4.1.0.dev * json 2.19.0 - * 2.18.0 to [v2.18.1][json-v2.18.1] + * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0] * openssl 4.0.1 * 4.0.0 to [v4.0.1][openssl-v4.0.1] * prism 1.9.0 @@ -134,6 +134,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21785]: https://bugs.ruby-lang.org/issues/21785 [json-v2.18.1]: https://github.com/ruby/json/releases/tag/v2.18.1 +[json-v2.19.0]: https://github.com/ruby/json/releases/tag/v2.19.0 [openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1 [prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0 [resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1 From 4068ff3bdfa34bbe9b2b45b055bac2df32063f82 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 8 Mar 2026 09:43:26 +0100 Subject: [PATCH 45/87] [ruby/json] Add missing GC_GUARD in `fbuffer_append_str` Ref: https://github.com/ruby/json/commit/fff25c9f4b9c `StringValuePtr` use `volatile` so the compiler is less likely to re-use the register. But regardless, we should GC_GUARD `str` as we no longer reference it after `GETMEM`. https://github.com/ruby/json/commit/13689c2699 --- ext/json/fbuffer/fbuffer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/json/fbuffer/fbuffer.h b/ext/json/fbuffer/fbuffer.h index d5fd5ea26adccc..9660e70dc76529 100644 --- a/ext/json/fbuffer/fbuffer.h +++ b/ext/json/fbuffer/fbuffer.h @@ -166,6 +166,7 @@ static void fbuffer_append_str(FBuffer *fb, VALUE str) RSTRING_GETMEM(str, ptr, len); fbuffer_append(fb, ptr, len); + RB_GC_GUARD(str); } static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) @@ -182,6 +183,7 @@ static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) fbuffer_append_reserved(fb, ptr, len); repeat--; } + RB_GC_GUARD(str); } static inline void fbuffer_append_char(FBuffer *fb, char newchr) From c7e7d399ed523e4f51d1848a55501fae798e2ccf Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 8 Mar 2026 10:13:53 +0100 Subject: [PATCH 46/87] [ruby/json] Release 2.19.1 https://github.com/ruby/json/commit/4a42a04280 --- ext/json/lib/json/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb index 3f73c0d97dcc6c..aa56d3a4bf4017 100644 --- a/ext/json/lib/json/version.rb +++ b/ext/json/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.19.0' + VERSION = '2.19.1' end From 47103734619c0b086655c1c2575089ab892b30fe Mon Sep 17 00:00:00 2001 From: git Date: Sun, 8 Mar 2026 09:16:26 +0000 Subject: [PATCH 47/87] Update default gems list at c7e7d399ed523e4f51d1848a55501f [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 624440abc91f20..1dea7f4cdc1950 100644 --- a/NEWS.md +++ b/NEWS.md @@ -64,7 +64,7 @@ releases. * RubyGems 4.1.0.dev * bundler 4.1.0.dev -* json 2.19.0 +* json 2.19.1 * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0] * openssl 4.0.1 * 4.0.0 to [v4.0.1][openssl-v4.0.1] From eef66208b9fc9157aeede972eca6463da1dfb947 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Sun, 8 Mar 2026 15:35:29 +0100 Subject: [PATCH 48/87] [ruby/prism] Fix link to node interface Closes https://github.com/ruby/prism/pull/3974 https://github.com/ruby/prism/commit/94646f5329 --- prism/templates/lib/prism/node.rb.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 817b59b477b529..fb13051aba0b3b 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -457,7 +457,7 @@ module Prism # ---------------------------------------------------------------------------------- # :section: Node Interface # These methods are present on all subclasses of Node. - # Read the [node interface docs](rdoc-ref:Node@node-interface) for more information. + # Read the [node interface docs](Node.html#node-interface) for more information. # ---------------------------------------------------------------------------------- # See Node.accept. From 638cdd6b60c31c3c97617557e30dc3601c115d58 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 6 Mar 2026 20:41:27 -0500 Subject: [PATCH 49/87] [ruby/prism] Copy and embed for serialization format https://github.com/ruby/prism/commit/0e7fb6b868 --- prism/templates/lib/prism/serialize.rb.erb | 38 ++++----------- prism/templates/src/serialize.c.erb | 54 ++++------------------ 2 files changed, 20 insertions(+), 72 deletions(-) diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index c336662f2c54a1..4e61c89a8906e9 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -46,7 +46,7 @@ module Prism cpool_base = loader.load_uint32 cpool_size = loader.load_varuint - constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) + constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size) node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) @@ -171,7 +171,7 @@ module Prism cpool_base = loader.load_uint32 cpool_size = loader.load_varuint - constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) + constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size) node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) @@ -202,14 +202,12 @@ module Prism class ConstantPool # :nodoc: attr_reader :size #: Integer - # @rbs @input: String # @rbs @serialized: String # @rbs @base: Integer # @rbs @pool: Array[Symbol?] - #: (String input, String serialized, Integer base, Integer size) -> void - def initialize(input, serialized, base, size) - @input = input + #: (String serialized, Integer base, Integer size) -> void + def initialize(serialized, base, size) @serialized = serialized @base = base @size = size @@ -224,11 +222,7 @@ module Prism start = @serialized.unpack1("L", offset: offset) #: Integer length = @serialized.unpack1("L", offset: offset + 4) #: Integer - if start.nobits?(1 << 31) - (@input.byteslice(start, length) or raise).force_encoding(encoding).to_sym - else - (@serialized.byteslice(start & ((1 << 31) - 1), length) or raise).force_encoding(encoding).to_sym - end + (@serialized.byteslice(start, length) or raise).force_encoding(encoding).to_sym end end end @@ -289,8 +283,8 @@ module Prism trailer = 0 constant_pool.size.times do |index| - start, length = (io.read(8) or raise).unpack("L2") #: [Integer, Integer] - trailer += length if start.anybits?(1 << 31) + length = (io.read(8) or raise).unpack1("L", offset: 4) #: Integer + trailer += length end io.read(trailer) @@ -388,7 +382,7 @@ module Prism error = ParseError.new( DIAGNOSTIC_TYPES.fetch(load_varuint), - load_embedded_string(encoding), + load_string(encoding), load_location_object(freeze), load_error_level ) @@ -422,7 +416,7 @@ module Prism warning = ParseWarning.new( DIAGNOSTIC_TYPES.fetch(load_varuint), - load_embedded_string(encoding), + load_string(encoding), load_location_object(freeze), load_warning_level ) @@ -507,21 +501,9 @@ module Prism end end - #: (Encoding encoding) -> String - def load_embedded_string(encoding) - (io.read(load_varuint) or raise).force_encoding(encoding).freeze - end - #: (Encoding encoding) -> String def load_string(encoding) - case (type = io.getbyte) - when 1 - (input.byteslice(load_varuint, load_varuint) or raise).force_encoding(encoding).freeze - when 2 - load_embedded_string(encoding) - else - raise "Unknown serialized string type: #{type}" - end + (io.read(load_varuint) or raise).force_encoding(encoding).freeze end #: (bool freeze) -> Location diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index e58bb81f0ac89a..4fe0cb88c1e255 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -26,28 +26,10 @@ pm_serialize_location(const pm_location_t *location, pm_buffer_t *buffer) { } static void -pm_serialize_string(const pm_parser_t *parser, const pm_string_t *string, pm_buffer_t *buffer) { - switch (string->type) { - case PM_STRING_SHARED: { - pm_buffer_append_byte(buffer, 1); - pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(pm_string_source(string) - parser->start)); - pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_string_length(string))); - break; - } - case PM_STRING_OWNED: - case PM_STRING_CONSTANT: { - uint32_t length = pm_sizet_to_u32(pm_string_length(string)); - pm_buffer_append_byte(buffer, 2); - pm_buffer_append_varuint(buffer, length); - pm_buffer_append_bytes(buffer, pm_string_source(string), length); - break; - } -#ifdef PRISM_HAS_MMAP - case PM_STRING_MAPPED: - assert(false && "Cannot serialize mapped strings."); - break; -#endif - } +pm_serialize_string(const pm_string_t *string, pm_buffer_t *buffer) { + uint32_t length = pm_sizet_to_u32(pm_string_length(string)); + pm_buffer_append_varuint(buffer, length); + pm_buffer_append_bytes(buffer, pm_string_source(string), length); } static void @@ -102,7 +84,7 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) { pm_serialize_node(parser, (pm_node_t *)((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); } <%- when Prism::Template::StringField -%> - pm_serialize_string(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); + pm_serialize_string(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer); <%- when Prism::Template::NodeListField -%> uint32_t <%= field.name %>_size = pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.size); pm_buffer_append_varuint(buffer, <%= field.name %>_size); @@ -304,28 +286,12 @@ pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) pm_constant_t *constant = &parser->constant_pool.constants[bucket->id - 1]; size_t buffer_offset = offset + ((((size_t)bucket->id) - 1) * 8); - if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED || bucket->type == PM_CONSTANT_POOL_BUCKET_CONSTANT) { - // Since this is an owned or constant constant, we are going to - // write its contents into the buffer after the constant pool. - // So effectively in place of the source offset, we have a - // buffer offset. We will add a leading 1 to indicate that this - // is a buffer offset. - uint32_t content_offset = pm_sizet_to_u32(buffer->length); - uint32_t owned_mask = 1U << 31; - - assert(content_offset < owned_mask); - content_offset |= owned_mask; - - memcpy(buffer->value + buffer_offset, &content_offset, 4); - pm_buffer_append_bytes(buffer, constant->start, constant->length); - } else { - // Since this is a shared constant, we are going to write its - // source offset directly into the buffer. - uint32_t source_offset = pm_ptrdifft_to_u32(constant->start - parser->start); - memcpy(buffer->value + buffer_offset, &source_offset, 4); - } + // Write the constant contents into the buffer after the constant + // pool. In place of the source offset, we store a buffer offset. + uint32_t content_offset = pm_sizet_to_u32(buffer->length); + memcpy(buffer->value + buffer_offset, &content_offset, 4); + pm_buffer_append_bytes(buffer, constant->start, constant->length); - // Now we can write the length of the constant into the buffer. uint32_t constant_length = pm_sizet_to_u32(constant->length); memcpy(buffer->value + buffer_offset + 4, &constant_length, 4); } From 8ca12b54ebe1cbd170108d80c700eea536b6714a Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Sun, 8 Mar 2026 12:32:39 -0700 Subject: [PATCH 50/87] Fix M:N threads under OpenBSD OpenBSD requires MAP_STACK for memory regions used as thread stacks. However it seems to error with "Invalid argument" unless the permissions include both PROT_READ | PROT_WRITE. We should be able to satisft this by re-mmapping over our reserved stack region to get the MAP_STACK flag. As a (very minor) bonus, this applies MAP_STACK only to the machine stack region, not the VM region. Co-authored-by: Jeremy Evans --- thread_pthread_mn.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/thread_pthread_mn.c b/thread_pthread_mn.c index a17d5597d39028..d2ae591515d313 100644 --- a/thread_pthread_mn.c +++ b/thread_pthread_mn.c @@ -189,12 +189,7 @@ nt_thread_stack_size(void) static struct nt_stack_chunk_header * nt_alloc_thread_stack_chunk(void) { - int mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE; -#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) - mmap_flags |= MAP_STACK; -#endif - - const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, mmap_flags, -1, 0); + const char *m = (void *)mmap(NULL, MSTACK_CHUNK_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (m == MAP_FAILED) { return NULL; } @@ -318,9 +313,15 @@ nt_alloc_stack(rb_vm_t *vm, void **vm_stack, void **machine_stack) char *stack_start = nt_stack_chunk_get_stack_start(ch, idx); size_t vm_stack_size = vm->default_params.thread_vm_stack_size; size_t mstack_size = nt_thread_stack_size() - vm_stack_size - MSTACK_PAGE_SIZE; + char *mstack_start = stack_start + vm_stack_size + MSTACK_PAGE_SIZE; + + int mstack_flags = MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE; +#if defined(MAP_STACK) && !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) + mstack_flags |= MAP_STACK; +#endif if (mprotect(stack_start, vm_stack_size, PROT_READ | PROT_WRITE) != 0 || - mprotect(stack_start + vm_stack_size + MSTACK_PAGE_SIZE, mstack_size, PROT_READ | PROT_WRITE) != 0) { + mmap(mstack_start, mstack_size, PROT_READ | PROT_WRITE, mstack_flags, -1, 0) == MAP_FAILED) { err = errno; } else { From 3a05ad81dd68ed581b0d163392823c71a2212c71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:18:28 +0000 Subject: [PATCH 51/87] Bump the github-actions group across 1 directory with 3 updates Bumps the github-actions group with 3 updates in the / directory: [ruby/setup-ruby](https://github.com/ruby/setup-ruby), [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) and [taiki-e/install-action](https://github.com/taiki-e/install-action). Updates `ruby/setup-ruby` from 1.289.0 to 1.290.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/19a43a6a2428d455dbd1b85344698725179c9d8c...6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c) Updates `zizmorcore/zizmor-action` from 0.5.0 to 0.5.1 - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d...195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f) Updates `taiki-e/install-action` from 2.68.19 to 2.68.25 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/385db9cc6bf65d19775b02084a4b698eaca9a4f2...a37010ded18ff788be4440302bd6830b1ae50d8b) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-version: 1.290.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: taiki-e/install-action dependency-version: 2.68.25 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 2 +- .github/workflows/check_sast.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 4 ++-- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index ecaca98b8b6b10..769d06db204527 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -73,7 +73,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 1eb931e82ee4fe..9cb8cf0f2e1ad4 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 223b30e7e86042..9ac48df2406118 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 7254aa669e0a29..4a0dce7847673d 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -38,7 +38,7 @@ jobs: with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: 4.0 diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 29ddfa80d697f3..63ee1909bbc06c 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -42,7 +42,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index a75b564654856e..a91b6a6f6e89ac 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -23,7 +23,7 @@ jobs: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: head diff --git a/.github/workflows/check_sast.yml b/.github/workflows/check_sast.yml index b02f6fb76ad047..6e2b9099e87570 100644 --- a/.github/workflows/check_sast.yml +++ b/.github/workflows/check_sast.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false - name: Run zizmor - uses: zizmorcore/zizmor-action@0dce2577a4760a2749d8cfb7a84b7d5585ebcb7d # v0.5.0 + uses: zizmorcore/zizmor-action@195d10ad90f31d8cd6ea1efd6ecc12969ddbe73f # v0.5.1 continue-on-error: true analyze: diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index 435efaea0b80af..997e3e8617b7ec 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -62,7 +62,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index ec34cc742c2f4c..77d7a8ea07208a 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -59,7 +59,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 23c6689042e7f1..71eca66d83efe1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: 3.3.4 diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 0314269a32594c..f944e92a2ae199 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -49,7 +49,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 3ffe0cc1a5a1f2..f09c8ce88f153f 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d0491f4482d278..ac9f8187efa771 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,7 +70,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index 0196fa51cf5616..a25348a56407c5 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -99,7 +99,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ae01cf3ebb5d56..15437089f200c0 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 0f40de798ceb3d..4e2ccd980831e4 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -133,7 +133,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 12b4fc08412e31..32279d9f053ae7 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 + - uses: taiki-e/install-action@a37010ded18ff788be4440302bd6830b1ae50d8b # v2.68.25 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 20e6b456756c2e..7a42d0efa1499d 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,12 +114,12 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 + - uses: ruby/setup-ruby@6ca151fd1bfcfd6fe0c4eb6837eb0584d0134a0c # v1.290.0 with: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 + - uses: taiki-e/install-action@a37010ded18ff788be4440302bd6830b1ae50d8b # v2.68.25 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} From f3d6967cf530034c7cd7e6f0d2e6955cb4c8b24d Mon Sep 17 00:00:00 2001 From: git Date: Mon, 9 Mar 2026 02:49:08 +0000 Subject: [PATCH 52/87] [DOC] Update bundled gems list at 3a05ad81dd68ed581b0d163392823c --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 1dea7f4cdc1950..273074dc89d99c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -65,7 +65,7 @@ releases. * RubyGems 4.1.0.dev * bundler 4.1.0.dev * json 2.19.1 - * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0] + * 2.18.0 to [v2.18.1][json-v2.18.1], [v2.19.0][json-v2.19.0], [v2.19.1][json-v2.19.1] * openssl 4.0.1 * 4.0.0 to [v4.0.1][openssl-v4.0.1] * prism 1.9.0 @@ -135,6 +135,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21785]: https://bugs.ruby-lang.org/issues/21785 [json-v2.18.1]: https://github.com/ruby/json/releases/tag/v2.18.1 [json-v2.19.0]: https://github.com/ruby/json/releases/tag/v2.19.0 +[json-v2.19.1]: https://github.com/ruby/json/releases/tag/v2.19.1 [openssl-v4.0.1]: https://github.com/ruby/openssl/releases/tag/v4.0.1 [prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0 [resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1 From f315d250b44e75a1a69f4a05b293dcc701377689 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 9 Mar 2026 16:47:52 +1300 Subject: [PATCH 53/87] [ruby/timeout] Compatibility with Fiber scheduler. (https://github.com/ruby/timeout/pull/97) [Bug #21947] https://github.com/ruby/timeout/commit/55d7c84b50 --- lib/timeout.rb | 28 +++++++++-------- test/test_timeout.rb | 73 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/lib/timeout.rb b/lib/timeout.rb index eb66e586df20e3..903a831d44b612 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -282,19 +282,21 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ message ||= "execution expired" if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after) - return scheduler.timeout_after(sec, klass || Error, message, &block) - end - - state = State.instance - state.ensure_timeout_thread_created - - perform = Proc.new do |exc| - request = Request.new(Thread.current, sec, exc, message) - state.add_request(request) - begin - return yield(sec) - ensure - request.finished + perform = Proc.new do |exc| + scheduler.timeout_after(sec, exc, message, &block) + end + else + state = State.instance + state.ensure_timeout_thread_created + + perform = Proc.new do |exc| + request = Request.new(Thread.current, sec, exc, message) + state.add_request(request) + begin + return yield(sec) + ensure + request.finished + end end end diff --git a/test/test_timeout.rb b/test/test_timeout.rb index b11fc92aeab1cb..5db355a7da162d 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -459,4 +459,77 @@ def test_timeout_in_trap_handler trap(signal, original_handler) end end + + if Fiber.respond_to?(:current_scheduler) + # Stubs Fiber.current_scheduler for the duration of the block, then restores it. + def with_mock_scheduler(mock) + original = Fiber.method(:current_scheduler) + Fiber.define_singleton_method(:current_scheduler) { mock } + begin + yield + ensure + Fiber.define_singleton_method(:current_scheduler, original) + end + end + + def test_fiber_scheduler_delegates_to_timeout_after + received = nil + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + received = [sec, exc, msg] + blk.call(sec) + end + + with_mock_scheduler(mock) do + assert_equal :ok, Timeout.timeout(5) { :ok } + end + + assert_equal 5, received[0] + assert_instance_of Timeout::ExitException, received[1], "scheduler should receive an ExitException instance when no klass given" + assert_equal "execution expired", received[2] + end + + def test_fiber_scheduler_delegates_to_timeout_after_with_custom_exception + custom_error = Class.new(StandardError) + received = nil + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + received = [sec, exc, msg] + blk.call(sec) + end + + with_mock_scheduler(mock) do + assert_equal :ok, Timeout.timeout(5, custom_error, "custom message") { :ok } + end + + assert_equal [5, custom_error, "custom message"], received + end + + def test_fiber_scheduler_timeout_raises_timeout_error + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + raise exc # simulate timeout firing + end + + with_mock_scheduler(mock) do + assert_raise(Timeout::Error) do + Timeout.timeout(5) { :should_not_reach } + end + end + end + + def test_fiber_scheduler_timeout_raises_custom_error + custom_error = Class.new(StandardError) + mock = Object.new + mock.define_singleton_method(:timeout_after) do |sec, exc, msg, &blk| + raise exc, msg + end + + with_mock_scheduler(mock) do + assert_raise_with_message(custom_error, "custom message") do + Timeout.timeout(5, custom_error, "custom message") { :should_not_reach } + end + end + end + end end From 7bcb5178dd1f41e60afb8635c5193fb2457ce246 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 6 Mar 2026 11:17:32 +0900 Subject: [PATCH 54/87] Mix upper bits when `st_index_t` is shorter than `uint64_t` As well as when `uint64_t` is not available. Although we now assume that `uint64_t` is always available, even on 32-bit platforms, the size of `st_index_t` is the same as a pointer and would be shorter than 64-bit. --- random.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/random.c b/random.c index ad7cbca426361c..6f5e58989c59b1 100644 --- a/random.c +++ b/random.c @@ -1783,8 +1783,10 @@ st_index_t rb_memhash(const void *ptr, long len) { sip_uint64_t h = sip_hash13(hash_salt.key.sip, ptr, len); -#ifdef HAVE_UINT64_T +#if SIZEOF_ST_INDEX_T >= 8 return (st_index_t)h; +#elif defined HAVE_UINT64_T + return (st_index_t)((h >> 32) ^ h); #else return (st_index_t)(h.u32[0] ^ h.u32[1]); #endif From bab8120bfde6e65a9b7d4e09695aa2cdce6aa570 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Mar 2026 11:39:32 +0900 Subject: [PATCH 55/87] Separate the case `default_seed_bits` is 0 --- random.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/random.c b/random.c index 6f5e58989c59b1..b6c96f1b4d25ff 100644 --- a/random.c +++ b/random.c @@ -353,15 +353,23 @@ random_alloc(VALUE klass) static VALUE rand_init_default(const rb_random_interface_t *rng, rb_random_t *rnd) { - VALUE seed, buf0 = 0; + VALUE seed; size_t len = roomof(rng->default_seed_bits, 32); - uint32_t *buf = ALLOCV_N(uint32_t, buf0, len+1); - fill_random_seed(buf, len, true); - rng->init(rnd, buf, len); - seed = make_seed_value(buf, len); - explicit_bzero(buf, len * sizeof(*buf)); - ALLOCV_END(buf0); + if (LIKELY(len)) { + VALUE buf0 = 0; + uint32_t *buf = ALLOCV_N(uint32_t, buf0, len); + fill_random_seed(buf, len, true); + rng->init(rnd, buf, len); + seed = make_seed_value(buf, len); + explicit_bzero(buf, len * sizeof(*buf)); + ALLOCV_END(buf0); + } + else { + uint32_t minimul[1] = {0}; + rng->init(rnd, minimul, 0); + seed = INT2FIX(0); + } return seed; } From c70d88be3ab7cc49fb593797e74cd3a02ef3884b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:36 +0900 Subject: [PATCH 56/87] [ruby/rubygems] Use JSON for cargo metadata parsing https://github.com/ruby/rubygems/commit/d18e420215 Co-Authored-By: Claude Opus 4.6 --- lib/rubygems/ext/cargo_builder.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 42dca3b102546c..516459dd6007b8 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -227,10 +227,9 @@ def cargo_crate_name(cargo_dir, manifest_path, results) raise Gem::InstallError, "cargo metadata failed#{exit_reason}" end - # cargo metadata output is specified as json, but with the - # --format-version 1 option the output is compatible with YAML, so we can - # avoid the json dependency - metadata = Gem::SafeYAML.safe_load(output) + # cargo metadata output is specified as json + require "json" + metadata = JSON.parse(output) package = metadata["packages"].find {|pkg| normalize_path(pkg["manifest_path"]) == manifest_path } unless package found = metadata["packages"].map {|md| "#{md["name"]} at #{md["manifest_path"]}" } From 1b8c6c1e33a46ce0cec0f0e3c56b94fd1c4fc357 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Mar 2026 15:04:07 +0900 Subject: [PATCH 57/87] Suppress a sign-compare warning This cast is safe because `rb_absint_size` returns `nlz` within the range `0...CHAR_BIT`. --- numeric.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numeric.c b/numeric.c index 226a47f7b8f38b..287294f9b5a899 100644 --- a/numeric.c +++ b/numeric.c @@ -4332,7 +4332,7 @@ int_accurate_in_double(VALUE n) const size_t mant_size = roomof(DBL_MANT_DIG, CHAR_BIT); if (size < mant_size) return true; if (size > mant_size) return false; - if (nlz >= (CHAR_BIT * mant_size - DBL_MANT_DIG)) return true; + if ((size_t)nlz >= (CHAR_BIT * mant_size - DBL_MANT_DIG)) return true; #endif return false; } From 59f744c5b028449e97254928ead35f9602491b42 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 9 Mar 2026 15:22:14 +0900 Subject: [PATCH 58/87] Suppress format warnings Use the appropriate modifier. `size_t` is not always `unsigned long`, even if the size is the same. --- gc/default/default.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 1099d6e0dc11e5..1c91a5e0d5b0fc 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -8266,7 +8266,7 @@ rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size) } if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) { - rb_bug("buffer %p freed with old_size=%lu, but was allocated with size=%lu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); + rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); } #endif ptr = info; @@ -8377,7 +8377,7 @@ rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_si ptr = info; #if VERIFY_FREE_SIZE if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) { - rb_bug("buffer %p realloced with old_size=%lu, but was allocated with size=%lu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); + rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info)); } #endif old_size = info->size; From 910682638b8871ee913bf0e97f1ad857d682b72b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:29:53 +0900 Subject: [PATCH 59/87] [ruby/rubygems] Add YAML serializer (dump) for Gem objects Replace the simple dump_hash method with dump_obj that can serialize Gem::Specification, Version, Platform, Requirement, Dependency, Hash, Array, Time, and String objects into YAML format. This enables pure-Ruby YAML serialization without depending on Psych. The serializer handles multiline strings (block scalars), special character quoting ($, !, &, *, :, @, %), and proper indentation for nested structures. https://github.com/ruby/rubygems/commit/bffb238b35 --- lib/rubygems/yaml_serializer.rb | 100 +++++++++++++++++++++++++------- 1 file changed, 79 insertions(+), 21 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index f89004f32ad8e4..278ebd624e4ce3 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -1,32 +1,94 @@ # frozen_string_literal: true module Gem - # A stub yaml serializer that can handle only hashes and strings (as of now). module YAMLSerializer module_function - def dump(hash) - yaml = String.new("---") - yaml << dump_hash(hash) + def dump(obj) + "---#{dump_obj(obj, 0)}" end - def dump_hash(hash) - yaml = String.new("\n") - hash.each do |k, v| - yaml << k << ":" - if v.is_a?(Hash) - yaml << dump_hash(v).gsub(/^(?!$)/, " ") # indent all non-empty lines - elsif v.is_a?(Array) # Expected to be array of strings - if v.empty? - yaml << " []\n" - else - yaml << "\n- " << v.map {|s| s.to_s.gsub(/\s+/, " ").inspect }.join("\n- ") << "\n" + def dump_obj(obj, indent, quote: false) + case obj + when Gem::Specification + parts = [" !ruby/object:Gem::Specification\n"] + parts << "#{" " * indent}name:#{dump_obj(obj.name, indent + 2)}" + parts << "#{" " * indent}version:#{dump_obj(obj.version, indent + 2)}" + parts << "#{" " * indent}platform: #{obj.platform}\n" + if obj.platform.to_s != obj.original_platform.to_s + parts << "#{" " * indent}original_platform: #{obj.original_platform}\n" + end + + attributes = Gem::Specification.attribute_names.map(&:to_s).sort - %w[name version platform] + attributes.each do |name| + val = obj.instance_variable_get("@#{name}") + next if val.nil? + parts << "#{" " * indent}#{name}:#{dump_obj(val, indent + 2)}" + end + res = parts.join + res << "\n" unless res.end_with?("\n") + res + when Gem::Version + " !ruby/object:Gem::Version\n#{" " * indent}version: #{dump_obj(obj.version.to_s, indent + 2).lstrip}" + when Gem::Platform + " !ruby/object:Gem::Platform\n#{" " * indent}cpu: #{obj.cpu.inspect}\n#{" " * indent}os: #{obj.os.inspect}\n#{" " * indent}version: #{obj.version.inspect}\n" + when Gem::Requirement + " !ruby/object:Gem::Requirement\n#{" " * indent}requirements:#{dump_obj(obj.requirements, indent + 2)}" + when Gem::Dependency + [ + " !ruby/object:Gem::Dependency\n", + "#{" " * indent}name: #{dump_obj(obj.name, indent + 2).lstrip}", + "#{" " * indent}requirement:#{dump_obj(obj.requirement, indent + 2)}", + "#{" " * indent}type: #{dump_obj(obj.type, indent + 2).lstrip}", + "#{" " * indent}prerelease: #{dump_obj(obj.prerelease?, indent + 2).lstrip}", + "#{" " * indent}version_requirements:#{dump_obj(obj.requirement, indent + 2)}", + ].join + when Hash + if obj.empty? + " {}\n" + else + parts = ["\n"] + obj.each do |k, v| + is_symbol = k.is_a?(Symbol) || (k.is_a?(String) && k.start_with?(":")) + key_str = k.is_a?(Symbol) ? k.inspect : k.to_s + parts << "#{" " * indent}#{key_str}:#{dump_obj(v, indent + 2, quote: is_symbol)}" end + parts.join + end + when Array + if obj.empty? + " []\n" else - yaml << " " << v.to_s.gsub(/\s+/, " ").inspect << "\n" + parts = ["\n"] + obj.each do |v| + parts << "#{" " * indent}-#{dump_obj(v, indent + 2)}" + end + parts.join end + when Time + " #{obj.utc.strftime("%Y-%m-%d %H:%M:%S.%N Z")}\n" + when String + if obj.include?("\n") + parts = [obj.end_with?("\n") ? " |\n" : " |-\n"] + obj.each_line do |line| + parts << "#{" " * (indent + 2)}#{line}" + end + res = parts.join + res << "\n" unless res.end_with?("\n") + res + elsif quote || obj.empty? || obj =~ /^[!*&:@%$]/ || obj =~ /^-?\d+(\.\d+)?$/ || obj =~ /^[<>=-]/ || + obj == "true" || obj == "false" || obj == "nil" || + obj.include?(":") || obj.include?("#") || obj.include?("[") || obj.include?("]") || + obj.include?("{") || obj.include?("}") || obj.include?(",") + " #{obj.to_s.inspect}\n" + else + " #{obj}\n" + end + when Numeric, Symbol, TrueClass, FalseClass, nil + " #{obj.inspect}\n" + else + " #{obj.to_s.inspect}\n" end - yaml end ARRAY_REGEX = / @@ -90,9 +152,5 @@ def strip_comment(val) val end end - - class << self - private :dump_hash - end end end From 91110cef3bdeacaeccd9e5f74eae95bcaaaf8a99 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:31:17 +0900 Subject: [PATCH 60/87] [ruby/rubygems] Add full YAML parser with recursive descent Replace the simple regex-based load method with a full recursive descent parser (parse_any) that handles nested hashes, arrays, block scalars (| and |-), YAML anchors (&anchor) and aliases (*alias), !ruby/object: type tags, flow notation ({} and []), and non-specific tag stripping (! prefix). Add helper methods: parse_block_scalar for multiline strings, build_permitted_tags for security validation, unquote_simple for type coercion (booleans, integers, timestamps, quoted strings), and improve strip_comment to handle # inside quoted strings. The parser returns raw Ruby data structures (Hash, Array, String) with :tag metadata for typed objects, without yet reconstructing Gem-specific objects. https://github.com/ruby/rubygems/commit/053b576c20 --- lib/rubygems/yaml_serializer.rb | 358 ++++++++++++++++++++++++++++---- 1 file changed, 313 insertions(+), 45 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 278ebd624e4ce3..626bd264ed02b3 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -91,63 +91,331 @@ def dump_obj(obj, indent, quote: false) end end - ARRAY_REGEX = / - ^ - (?:[ ]*-[ ]) # '- ' before array items - (['"]?) # optional opening quote - (.*) # value - \1 # matching closing quote - $ - /xo - - HASH_REGEX = / - ^ - ([ ]*) # indentations - ([^#]+) # key excludes comment char '#' - (?::(?=(?:\s|$))) # : (without the lookahead the #key includes this when : is present in value) - [ ]? - (['"]?) # optional opening quote - (.*) # value - \3 # matching closing quote - $ - /xo - - def load(str) - res = {} - stack = [res] - last_hash = nil - last_empty_key = nil - str.split(/\r?\n/) do |line| - if match = HASH_REGEX.match(line) - indent, key, quote, val = match.captures - val = strip_comment(val) + def load(str, permitted_classes: [], permitted_symbols: [], aliases: true) + return {} if str.nil? || str.empty? + lines = str.split(/\r?\n/) + if lines[0]&.start_with?("---") + if lines[0].strip == "---" + lines.shift + else + lines[0] = lines[0].sub(/^---\s*/, "") + end + end + + permitted_tags = build_permitted_tags(permitted_classes) + anchors = {} + data = nil + while lines.any? + before_count = lines.size + parsed = parse_any(lines, -1, permitted_tags, aliases, anchors) + if lines.size == before_count && lines.any? + lines.shift + end + + if data.is_a?(Hash) && parsed.is_a?(Hash) + data.merge!(parsed) + elsif data.nil? + data = parsed + end + end + + return {} if data.nil? + data + end + + def parse_any(lines, base_indent, permitted_tags, aliases, anchors) + while lines.any? && (lines[0].strip.empty? || lines[0].lstrip.start_with?("#")) + lines.shift + end + return nil if lines.empty? + + indent = lines[0][/^ */].size + return nil if indent < base_indent - depth = indent.size / 2 - if quote.empty? && val.empty? - new_hash = {} - stack[depth][key] = new_hash - stack[depth + 1] = new_hash - last_empty_key = key - last_hash = stack[depth] + line = lines[0] + + # Check for alias reference (*anchor) + if line.lstrip.start_with?("*") + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + alias_name = lines.shift.lstrip[1..-1].strip + return anchors[alias_name] + end + + # Extract anchor if present (&anchor) + anchor_name = nil + if line.lstrip =~ /^&(\S+)\s+/ + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + anchor_name = $1 + line = line.sub(/&#{Regexp.escape(anchor_name)}\s+/, "") + lines[0] = line + end + + if line.lstrip.start_with?("- ") || line.lstrip == "-" + res = [] + while lines.any? && lines[0][/^ */].size == indent && (lines[0].lstrip.start_with?("- ") || lines[0].lstrip == "-") + l = lines.shift + content = l.lstrip[1..-1].strip + + # Check for anchor in array item + item_anchor = nil + if content =~ /^&(\S+)/ + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + item_anchor = $1 + content = content.sub(/^&#{Regexp.escape(item_anchor)}\s*/, "") + end + + # Check for alias in array item + if content.start_with?("*") + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + alias_name = content[1..-1].strip + res << anchors[alias_name] + elsif content.empty? + # Empty array item - check if next line is nested content or a new item + item_value = if lines.any? && lines[0][/^ */].size > indent + parse_any(lines, indent, permitted_tags, aliases, anchors) + end + anchors[item_anchor] = item_value if item_anchor + res << item_value + elsif content.start_with?("!ruby/object:") + tag = content.strip + unless permitted_tags.include?(tag) + raise ArgumentError, "Disallowed class: #{tag}" + end + nested = parse_any(lines, indent, permitted_tags, aliases, anchors) + item_value = if nested.is_a?(Hash) + nested[:tag] = tag + nested + else + { :tag => tag, "value" => nested } + end + anchors[item_anchor] = item_value if item_anchor + res << item_value + elsif content.start_with?("-") + lines.unshift(" " * (indent + 2) + content) + item_value = parse_any(lines, indent, permitted_tags, aliases, anchors) + anchors[item_anchor] = item_value if item_anchor + res << item_value + elsif content =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !content.start_with?("!ruby/object:") + lines.unshift(" " * (indent + 2) + content) + item_value = parse_any(lines, indent, permitted_tags, aliases, anchors) + anchors[item_anchor] = item_value if item_anchor + res << item_value + elsif content.start_with?("|") + modifier = content[1..-1].to_s.strip + item_value = parse_block_scalar(lines, indent, modifier) + anchors[item_anchor] = item_value if item_anchor + res << item_value else - val = [] if val == "[]" # empty array - stack[depth][key] = val + str = unquote_simple(content) + while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent + str << " " << lines.shift.strip + end + anchors[item_anchor] = str if item_anchor + res << str end - elsif match = ARRAY_REGEX.match(line) - _, val = match.captures + end + result = res + elsif line.lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !line.lstrip.start_with?("!ruby/object:") + res = Hash.new + while lines.any? && lines[0][/^ */].size == indent && lines[0].lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !lines[0].lstrip.start_with?("!ruby/object:") + l = lines.shift + l.lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ + key = $1.strip + val = $2.to_s.strip val = strip_comment(val) - last_hash[last_empty_key] = [] unless last_hash[last_empty_key].is_a?(Array) + # Check for anchor in value + val_anchor = nil + if val =~ /^&(\S+)\s+/ + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + val_anchor = $1 + val = val.sub(/^&#{Regexp.escape(val_anchor)}\s+/, "") + end + + # Check for alias in value + if val.start_with?("*") + unless aliases + raise ArgumentError, "YAML aliases are not allowed" + end + alias_name = val[1..-1].strip + res[key] = anchors[alias_name] + elsif val.start_with?("!ruby/object:") + tag = val.strip + unless permitted_tags.include?(tag) + raise ArgumentError, "Disallowed class: #{tag}" + end + nested = parse_any(lines, indent, permitted_tags, aliases, anchors) + value = if nested.is_a?(Hash) + nested[:tag] = tag + nested + else + { :tag => tag, "value" => nested } + end + anchors[val_anchor] = value if val_anchor + res[key] = value + elsif val.empty? + value = if lines.any? && (lines[0].lstrip.start_with?("- ") || lines[0].lstrip == "-") && lines[0][/^ */].size == indent + parse_any(lines, indent, permitted_tags, aliases, anchors) + else + parse_any(lines, indent + 1, permitted_tags, aliases, anchors) + end + anchors[val_anchor] = value if val_anchor + res[key] = value + elsif val == "[]" + value = [] + anchors[val_anchor] = value if val_anchor + res[key] = value + elsif val == "{}" + value = {} + anchors[val_anchor] = value if val_anchor + res[key] = value + elsif val.start_with?("|") + modifier = val[1..-1].to_s.strip + value = parse_block_scalar(lines, indent, modifier) + anchors[val_anchor] = value if val_anchor + res[key] = value + else + str = unquote_simple(val) + while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent + str << " " << lines.shift.strip + end + anchors[val_anchor] = str if val_anchor + res[key] = str + end + end + result = res + elsif line.lstrip.start_with?("!ruby/object:") + tag = lines.shift.lstrip.strip + unless permitted_tags.include?(tag) + raise ArgumentError, "Disallowed class: #{tag}" + end + nested = parse_any(lines, indent, permitted_tags, aliases, anchors) + if nested.is_a?(Hash) + nested[:tag] = tag + result = nested + else + result = { :tag => tag, "value" => nested } + end + elsif line.lstrip.start_with?("|") + modifier = line.lstrip[1..-1].to_s.strip + lines.shift + result = parse_block_scalar(lines, indent, modifier) + else + str = unquote_simple(lines.shift.strip) + while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent + str << " " << lines.shift.strip + end + result = str + end + + # Store anchor if present + anchors[anchor_name] = result if anchor_name + result + end - last_hash[last_empty_key].push(val) + def parse_block_scalar(lines, base_indent, modifier) + parts = [] + block_indent = nil + while lines.any? + if lines[0].strip.empty? + parts << "\n" + lines.shift + else + line_indent = lines[0][/^ */].size + break if line_indent <= base_indent + block_indent ||= line_indent + l = lines.shift + parts << l[block_indent..-1].to_s << "\n" end end + res = parts.join + res.chomp! if modifier == "-" && res.end_with?("\n") res end + def build_permitted_tags(permitted_classes) + Array(permitted_classes).map do |klass| + name = klass.is_a?(Module) ? klass.name : klass.to_s + "!ruby/object:#{name}" + end + end + def strip_comment(val) - if val.include?("#") && !val.start_with?("#") - val.split("#", 2).first.strip + return val unless val.include?("#") + return val if val.lstrip.start_with?("#") + + in_single = false + in_double = false + escape = false + + val.each_char.with_index do |ch, i| + if escape + escape = false + next + end + + if in_single + in_single = false if ch == "'" + elsif in_double + if ch == "\\" + escape = true + elsif ch == '"' + in_double = false + end + else + case ch + when "'" + in_single = true + when '"' + in_double = true + when "#" + return val[0...i].rstrip + end + end + end + + val + end + + def unquote_simple(val) + # Strip YAML non-specific tag (! prefix), e.g. ! '>=' -> '>=' + val = val.sub(/^! /, "") if val.start_with?("! ") + + if val =~ /^"(.*)"$/ + $1.gsub(/\\"/, '"').gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\t/, "\t").gsub(/\\\\/, "\\") + elsif val =~ /^'(.*)'$/ + $1.gsub(/''/, "'") + elsif val == "true" + true + elsif val == "false" + false + elsif val == "nil" + nil + elsif val == "{}" + {} + elsif val =~ /^\[(.*)\]$/ + inner = $1.strip + return [] if inner.empty? + inner.split(/\s*,\s*/).reject(&:empty?).map {|element| unquote_simple(element) } + elsif /^\d{4}-\d{2}-\d{2}/.match?(val) + require "time" + begin + Time.parse(val) + rescue ArgumentError + val + end + elsif /^-?\d+$/.match?(val) + val.to_i else val end From 45cb5d003064a2333c18e4ca7cc358f01a0ce2ad Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:31:31 +0900 Subject: [PATCH 61/87] [ruby/rubygems] Add Gem object reconstruction from parsed YAML Add convert_to_spec and convert_any methods that transform parsed YAML data structures (with :tag metadata) back into Gem::Specification, Version, Platform, Requirement, and Dependency objects. Wire these into the load method so it returns fully reconstructed Gem objects. convert_to_spec normalizes specification_version to Integer, rdoc_options to Array of Strings, and other array fields (files, test_files, executables, requirements, extra_rdoc_files) to proper arrays. convert_any handles Gem::Version::Requirement (legacy) and validates requirement operators and dependency type symbols against permitted lists. https://github.com/ruby/rubygems/commit/fe1a29ef2d --- lib/rubygems/yaml_serializer.rb | 184 +++++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 626bd264ed02b3..8f321e0e0573cb 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -80,7 +80,7 @@ def dump_obj(obj, indent, quote: false) obj == "true" || obj == "false" || obj == "nil" || obj.include?(":") || obj.include?("#") || obj.include?("[") || obj.include?("]") || obj.include?("{") || obj.include?("}") || obj.include?(",") - " #{obj.to_s.inspect}\n" + " #{obj.to_s.inspect}\n" else " #{obj}\n" end @@ -120,7 +120,12 @@ def load(str, permitted_classes: [], permitted_symbols: [], aliases: true) end return {} if data.nil? - data + + if data.is_a?(Hash) && (data[:tag] == "!ruby/object:Gem::Specification" || data["tag"] == "!ruby/object:Gem::Specification") + convert_to_spec(data, permitted_symbols) + else + convert_any(data, permitted_symbols) + end end def parse_any(lines, base_indent, permitted_tags, aliases, anchors) @@ -350,6 +355,181 @@ def build_permitted_tags(permitted_classes) end end + def convert_to_spec(hash, permitted_symbols) + spec = Gem::Specification.allocate + return spec unless hash.is_a?(Hash) + + converted_hash = {} + hash.each {|k, v| converted_hash[k] = convert_any(v, permitted_symbols) } + + # Ensure specification_version is an Integer if it's a valid numeric string + if converted_hash["specification_version"] && !converted_hash["specification_version"].is_a?(Integer) + val = converted_hash["specification_version"] + if val.is_a?(String) && /\A\d+\z/.match?(val) + converted_hash["specification_version"] = val.to_i + end + end + + # Debug: log rdoc_options that contain non-string elements + if converted_hash["rdoc_options"] && converted_hash["name"] + rdoc_opts = converted_hash["rdoc_options"] + has_non_string = case rdoc_opts + when Array then rdoc_opts.any? {|o| !o.is_a?(String) } + when Hash then true + else true + end + if has_non_string + warn "[DEBUG rdoc_options] gem=#{converted_hash["name"]} class=#{rdoc_opts.class} value=#{rdoc_opts.inspect}" + end + end + + # Ensure rdoc_options is an Array of Strings + if converted_hash["rdoc_options"].is_a?(Hash) + converted_hash["rdoc_options"] = converted_hash["rdoc_options"].values.flatten.compact.map(&:to_s) + elsif converted_hash["rdoc_options"].is_a?(Array) + converted_hash["rdoc_options"] = converted_hash["rdoc_options"].flat_map do |opt| + if opt.is_a?(Hash) + opt.flat_map {|k, v| [k.to_s, v.to_s] } + elsif opt.is_a?(String) + opt + else + opt.to_s + end + end + end + + # Ensure other array fields are properly typed + ["files", "test_files", "executables", "requirements", "extra_rdoc_files"].each do |field| + if converted_hash[field].is_a?(Hash) + converted_hash[field] = converted_hash[field].values.flatten.compact + elsif !converted_hash[field].is_a?(Array) && converted_hash[field] + converted_hash[field] = [converted_hash[field]].flatten.compact + end + end + + spec.yaml_initialize("!ruby/object:Gem::Specification", converted_hash) + spec + end + + def convert_any(obj, permitted_symbols) + if obj.is_a?(Hash) + if obj[:tag] == "!ruby/object:Gem::Version" + ver = obj["version"] || obj["value"] + Gem::Version.new(ver.to_s) + elsif obj[:tag] == "!ruby/object:Gem::Platform" + if obj["value"] + Gem::Platform.new(obj["value"]) + else + Gem::Platform.new([obj["cpu"], obj["os"], obj["version"]]) + end + elsif ["!ruby/object:Gem::Requirement", "!ruby/object:Gem::Version::Requirement"].include?(obj[:tag]) + r = Gem::Requirement.allocate + raw_reqs = obj["requirements"] || obj["value"] + reqs = convert_any(raw_reqs, permitted_symbols) + # Ensure reqs is an array (never nil or Hash) + reqs = [] unless reqs.is_a?(Array) + if reqs.is_a?(Array) && !reqs.empty? + safe_reqs = [] + reqs.each do |item| + if item.is_a?(Array) && item.size == 2 + op = item[0].to_s + ver = item[1] + # Validate that op is a valid requirement operator + if ["=", "!=", ">", "<", ">=", "<=", "~>"].include?(op) + version_obj = if ver.is_a?(Gem::Version) + ver + else + Gem::Version.new(ver.to_s) + end + safe_reqs << [op, version_obj] + end + elsif item.is_a?(String) + # Try to validate the requirement string + parsed = Gem::Requirement.parse(item) + safe_reqs << parsed + end + rescue Gem::Requirement::BadRequirementError, Gem::Version::BadVersionError + # Skip malformed items silently + end + reqs = safe_reqs unless safe_reqs.empty? + end + r.instance_variable_set(:@requirements, reqs) + r + elsif obj[:tag] == "!ruby/object:Gem::Dependency" + d = Gem::Dependency.allocate + d.instance_variable_set(:@name, obj["name"]) + + # Ensure requirement is properly formed + requirement = begin + converted_req = convert_any(obj["requirement"], permitted_symbols) + # Validate that the requirement has valid requirements + if converted_req.is_a?(Gem::Requirement) + # Check if the requirement has any invalid items + reqs = converted_req.instance_variable_get(:@requirements) + if reqs&.is_a?(Array) + # Verify all requirements are valid + valid = reqs.all? do |item| + next true if item == Gem::Requirement::DefaultRequirement + if item.is_a?(Array) && item.size >= 2 + ["=", "!=", ">", "<", ">=", "<=", "~>"].include?(item[0].to_s) + else + false + end + end + valid ? converted_req : Gem::Requirement.default + else + converted_req + end + else + converted_req + end + rescue StandardError + Gem::Requirement.default + end + + d.instance_variable_set(:@requirement, requirement) + + type = obj["type"] + if type + type = type.to_s.sub(/^:/, "").to_sym + else + type = :runtime + end + if permitted_symbols.any? && !permitted_symbols.include?(type.to_s) + raise ArgumentError, "Disallowed symbol: #{type.inspect}" + end + d.instance_variable_set(:@type, type) + + d.instance_variable_set(:@prerelease, ["true", true].include?(obj["prerelease"])) + d.instance_variable_set(:@version_requirements, d.instance_variable_get(:@requirement)) + d + else + res = Hash.new + obj.each do |k, v| + next if k == :tag + key_str = k.to_s + converted_val = convert_any(v, permitted_symbols) + + # Convert Hash to Array for fields that should be arrays + if ["rdoc_options", "files", "test_files", "executables", "requirements", "extra_rdoc_files"].include?(key_str) + if converted_val.is_a?(Hash) + converted_val = converted_val.values.flatten.compact + elsif !converted_val.is_a?(Array) && converted_val + converted_val = [converted_val].flatten.compact + end + end + + res[key_str] = converted_val + end + res + end + elsif obj.is_a?(Array) + obj.map {|i| convert_any(i, permitted_symbols) } + else + obj + end + end + def strip_comment(val) return val unless val.include?("#") return val if val.lstrip.start_with?("#") From a551f4fbf50a4e063abe08ba703a9f70709fdc5f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 14:29:54 +0900 Subject: [PATCH 62/87] [ruby/rubygems] Refactor YAMLSerializer into Parser/Builder/Emitter https://github.com/ruby/rubygems/commit/bfe17c110c --- lib/rubygems/yaml_serializer.rb | 1142 +++++++++++++++++-------------- 1 file changed, 642 insertions(+), 500 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 8f321e0e0573cb..720911194e1d32 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -2,603 +2,745 @@ module Gem module YAMLSerializer - module_function + Scalar = Struct.new(:value, :tag, :anchor, keyword_init: true) - def dump(obj) - "---#{dump_obj(obj, 0)}" + Mapping = Struct.new(:pairs, :tag, :anchor, keyword_init: true) do + def initialize(pairs: [], tag: nil, anchor: nil) + super + end end - def dump_obj(obj, indent, quote: false) - case obj - when Gem::Specification - parts = [" !ruby/object:Gem::Specification\n"] - parts << "#{" " * indent}name:#{dump_obj(obj.name, indent + 2)}" - parts << "#{" " * indent}version:#{dump_obj(obj.version, indent + 2)}" - parts << "#{" " * indent}platform: #{obj.platform}\n" - if obj.platform.to_s != obj.original_platform.to_s - parts << "#{" " * indent}original_platform: #{obj.original_platform}\n" + Sequence = Struct.new(:items, :tag, :anchor, keyword_init: true) do + def initialize(items: [], tag: nil, anchor: nil) + super + end + end + + AliasRef = Struct.new(:name, keyword_init: true) + + class Parser + MAPPING_KEY_RE = /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ + + def initialize(source) + @lines = source.split(/\r?\n/) + @anchors = {} + strip_document_prefix + end + + def parse + return nil if @lines.empty? + + root = nil + while @lines.any? + before = @lines.size + node = parse_node(-1) + @lines.shift if @lines.size == before && @lines.any? + + if root.is_a?(Mapping) && node.is_a?(Mapping) + root.pairs.concat(node.pairs) + elsif root.nil? + root = node + end end + root + end - attributes = Gem::Specification.attribute_names.map(&:to_s).sort - %w[name version platform] - attributes.each do |name| - val = obj.instance_variable_get("@#{name}") - next if val.nil? - parts << "#{" " * indent}#{name}:#{dump_obj(val, indent + 2)}" + private + + def strip_document_prefix + return if @lines.empty? + return unless @lines[0]&.start_with?("---") + + if @lines[0].strip == "---" + @lines.shift + else + @lines[0] = @lines[0].sub(/^---\s*/, "") end - res = parts.join - res << "\n" unless res.end_with?("\n") - res - when Gem::Version - " !ruby/object:Gem::Version\n#{" " * indent}version: #{dump_obj(obj.version.to_s, indent + 2).lstrip}" - when Gem::Platform - " !ruby/object:Gem::Platform\n#{" " * indent}cpu: #{obj.cpu.inspect}\n#{" " * indent}os: #{obj.os.inspect}\n#{" " * indent}version: #{obj.version.inspect}\n" - when Gem::Requirement - " !ruby/object:Gem::Requirement\n#{" " * indent}requirements:#{dump_obj(obj.requirements, indent + 2)}" - when Gem::Dependency - [ - " !ruby/object:Gem::Dependency\n", - "#{" " * indent}name: #{dump_obj(obj.name, indent + 2).lstrip}", - "#{" " * indent}requirement:#{dump_obj(obj.requirement, indent + 2)}", - "#{" " * indent}type: #{dump_obj(obj.type, indent + 2).lstrip}", - "#{" " * indent}prerelease: #{dump_obj(obj.prerelease?, indent + 2).lstrip}", - "#{" " * indent}version_requirements:#{dump_obj(obj.requirement, indent + 2)}", - ].join - when Hash - if obj.empty? - " {}\n" + end + + def parse_node(base_indent) + skip_blank_and_comments + return nil if @lines.empty? + + indent = @lines[0][/^ */].size + return nil if indent < base_indent + + line = @lines[0] + + return parse_alias_ref if line.lstrip.start_with?("*") + + anchor = consume_anchor + + if line.lstrip.start_with?("- ") || line.lstrip == "-" + parse_sequence(indent, anchor) + elsif line.lstrip =~ MAPPING_KEY_RE && !line.lstrip.start_with?("!ruby/object:") + parse_mapping(indent, anchor) + elsif line.lstrip.start_with?("!ruby/object:") + parse_tagged_node(indent, anchor) + elsif line.lstrip.start_with?("|") + modifier = line.lstrip[1..].to_s.strip + @lines.shift + register_anchor(anchor, Scalar.new(value: parse_block_scalar(indent, modifier))) else - parts = ["\n"] - obj.each do |k, v| - is_symbol = k.is_a?(Symbol) || (k.is_a?(String) && k.start_with?(":")) - key_str = k.is_a?(Symbol) ? k.inspect : k.to_s - parts << "#{" " * indent}#{key_str}:#{dump_obj(v, indent + 2, quote: is_symbol)}" - end - parts.join + parse_plain_scalar(indent, anchor) end - when Array - if obj.empty? - " []\n" + end + + def parse_sequence(indent, anchor) + items = [] + while @lines.any? && @lines[0][/^ */].size == indent && + (@lines[0].lstrip.start_with?("- ") || @lines[0].lstrip == "-") + content = @lines.shift.lstrip[1..].strip + item_anchor, content = extract_item_anchor(content) + item = parse_sequence_item(content, indent) + items << register_anchor(item_anchor, item) + end + register_anchor(anchor, Sequence.new(items: items)) + end + + def parse_sequence_item(content, indent) + if content.start_with?("*") + parse_inline_alias(content) + elsif content.empty? + @lines.any? && @lines[0][/^ */].size > indent ? parse_node(indent) : nil + elsif content.start_with?("!ruby/object:") + parse_tagged_content(content.strip, indent) + elsif content.start_with?("-") + @lines.unshift("#{" " * (indent + 2)}#{content}") + parse_node(indent) + elsif content =~ MAPPING_KEY_RE && !content.start_with?("!ruby/object:") + @lines.unshift("#{" " * (indent + 2)}#{content}") + parse_node(indent) + elsif content.start_with?("|") + Scalar.new(value: parse_block_scalar(indent, content[1..].to_s.strip)) else - parts = ["\n"] - obj.each do |v| - parts << "#{" " * indent}-#{dump_obj(v, indent + 2)}" - end - parts.join + parse_inline_scalar(content, indent) + end + end + + def parse_mapping(indent, anchor) + pairs = [] + while @lines.any? && @lines[0][/^ */].size == indent && + @lines[0].lstrip =~ MAPPING_KEY_RE && !@lines[0].lstrip.start_with?("!ruby/object:") + l = @lines.shift + l.lstrip =~ MAPPING_KEY_RE + key = $1.strip + val = strip_comment($2.to_s.strip) + + val_anchor, val = consume_value_anchor(val) + value = parse_mapping_value(val, indent) + value = register_anchor(val_anchor, value) if val_anchor + + pairs << [Scalar.new(value: key), value] end - when Time - " #{obj.utc.strftime("%Y-%m-%d %H:%M:%S.%N Z")}\n" - when String - if obj.include?("\n") - parts = [obj.end_with?("\n") ? " |\n" : " |-\n"] - obj.each_line do |line| - parts << "#{" " * (indent + 2)}#{line}" + register_anchor(anchor, Mapping.new(pairs: pairs)) + end + + def parse_mapping_value(val, indent) + if val.start_with?("*") + parse_inline_alias(val) + elsif val.start_with?("!ruby/object:") + parse_tagged_content(val.strip, indent) + elsif val.empty? + if @lines.any? && + (@lines[0].lstrip.start_with?("- ") || @lines[0].lstrip == "-") && + @lines[0][/^ */].size == indent + parse_node(indent) + else + parse_node(indent + 1) end - res = parts.join - res << "\n" unless res.end_with?("\n") - res - elsif quote || obj.empty? || obj =~ /^[!*&:@%$]/ || obj =~ /^-?\d+(\.\d+)?$/ || obj =~ /^[<>=-]/ || - obj == "true" || obj == "false" || obj == "nil" || - obj.include?(":") || obj.include?("#") || obj.include?("[") || obj.include?("]") || - obj.include?("{") || obj.include?("}") || obj.include?(",") - " #{obj.to_s.inspect}\n" + elsif val == "[]" + Sequence.new + elsif val == "{}" + Mapping.new + elsif val.start_with?("|") + Scalar.new(value: parse_block_scalar(indent, val[1..].to_s.strip)) else - " #{obj}\n" + parse_inline_scalar(val, indent) end - when Numeric, Symbol, TrueClass, FalseClass, nil - " #{obj.inspect}\n" - else - " #{obj.to_s.inspect}\n" end - end - def load(str, permitted_classes: [], permitted_symbols: [], aliases: true) - return {} if str.nil? || str.empty? - lines = str.split(/\r?\n/) - if lines[0]&.start_with?("---") - if lines[0].strip == "---" - lines.shift + def parse_tagged_node(indent, anchor) + tag = @lines.shift.lstrip.strip + nested = parse_node(indent) + apply_tag(nested, tag, anchor) + end + + def parse_tagged_content(tag, indent) + nested = parse_node(indent) + apply_tag(nested, tag, nil) + end + + def apply_tag(node, tag, anchor) + if node.is_a?(Mapping) + node.tag = tag + node.anchor = anchor + node else - lines[0] = lines[0].sub(/^---\s*/, "") + Mapping.new(pairs: [[Scalar.new(value: "value"), node]], tag: tag, anchor: anchor) end end - permitted_tags = build_permitted_tags(permitted_classes) - anchors = {} - data = nil - while lines.any? - before_count = lines.size - parsed = parse_any(lines, -1, permitted_tags, aliases, anchors) - if lines.size == before_count && lines.any? - lines.shift + def parse_block_scalar(base_indent, modifier) + parts = [] + block_indent = nil + + while @lines.any? + if @lines[0].strip.empty? + parts << "\n" + @lines.shift + else + line_indent = @lines[0][/^ */].size + break if line_indent <= base_indent + block_indent ||= line_indent + parts << @lines.shift[block_indent..].to_s << "\n" + end end - if data.is_a?(Hash) && parsed.is_a?(Hash) - data.merge!(parsed) - elsif data.nil? - data = parsed + res = parts.join + res.chomp! if modifier == "-" && res.end_with?("\n") + res + end + + def parse_plain_scalar(indent, anchor) + result = coerce(@lines.shift.strip) + return register_anchor(anchor, result) if result.is_a?(Mapping) || result.is_a?(Sequence) + + while result.is_a?(String) && @lines.any? && + !@lines[0].strip.empty? && @lines[0][/^ */].size > indent + result << " " << @lines.shift.strip end + register_anchor(anchor, Scalar.new(value: result)) end - return {} if data.nil? + def parse_inline_scalar(val, indent) + result = coerce(val) + return result if result.is_a?(Mapping) || result.is_a?(Sequence) - if data.is_a?(Hash) && (data[:tag] == "!ruby/object:Gem::Specification" || data["tag"] == "!ruby/object:Gem::Specification") - convert_to_spec(data, permitted_symbols) - else - convert_any(data, permitted_symbols) + while result.is_a?(String) && @lines.any? && + !@lines[0].strip.empty? && @lines[0][/^ */].size > indent + result << " " << @lines.shift.strip + end + Scalar.new(value: result) end - end - def parse_any(lines, base_indent, permitted_tags, aliases, anchors) - while lines.any? && (lines[0].strip.empty? || lines[0].lstrip.start_with?("#")) - lines.shift + def coerce(val) + val = val.sub(/^! /, "") if val.start_with?("! ") + + if val =~ /^"(.*)"$/ + $1.gsub(/\\"/, '"').gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\t/, "\t").gsub(/\\\\/, "\\") + elsif val =~ /^'(.*)'$/ + $1.gsub(/''/, "'") + elsif val == "true" + true + elsif val == "false" + false + elsif val == "nil" + nil + elsif val == "{}" + Mapping.new + elsif val =~ /^\[(.*)\]$/ + inner = $1.strip + return Sequence.new if inner.empty? + items = inner.split(/\s*,\s*/).reject(&:empty?).map {|e| Scalar.new(value: coerce(e)) } + Sequence.new(items: items) + elsif /^\d{4}-\d{2}-\d{2}/.match?(val) + require "time" + begin + Time.parse(val) + rescue ArgumentError + val + end + elsif /^-?\d+$/.match?(val) + val.to_i + else + val + end + end + + def parse_alias_ref + AliasRef.new(name: @lines.shift.lstrip[1..].strip) end - return nil if lines.empty? - indent = lines[0][/^ */].size - return nil if indent < base_indent + def parse_inline_alias(content) + AliasRef.new(name: content[1..].strip) + end - line = lines[0] + def consume_anchor + line = @lines[0] + return nil unless line.lstrip =~ /^&(\S+)\s+/ - # Check for alias reference (*anchor) - if line.lstrip.start_with?("*") - unless aliases - raise ArgumentError, "YAML aliases are not allowed" - end - alias_name = lines.shift.lstrip[1..-1].strip - return anchors[alias_name] + anchor = $1 + @lines[0] = line.sub(/&#{Regexp.escape(anchor)}\s+/, "") + anchor end - # Extract anchor if present (&anchor) - anchor_name = nil - if line.lstrip =~ /^&(\S+)\s+/ - unless aliases - raise ArgumentError, "YAML aliases are not allowed" - end - anchor_name = $1 - line = line.sub(/&#{Regexp.escape(anchor_name)}\s+/, "") - lines[0] = line + def extract_item_anchor(content) + return [nil, content] unless content =~ /^&(\S+)/ + + anchor = $1 + [anchor, content.sub(/^&#{Regexp.escape(anchor)}\s*/, "")] end - if line.lstrip.start_with?("- ") || line.lstrip == "-" - res = [] - while lines.any? && lines[0][/^ */].size == indent && (lines[0].lstrip.start_with?("- ") || lines[0].lstrip == "-") - l = lines.shift - content = l.lstrip[1..-1].strip + def consume_value_anchor(val) + return [nil, val] unless val =~ /^&(\S+)\s+/ - # Check for anchor in array item - item_anchor = nil - if content =~ /^&(\S+)/ - unless aliases - raise ArgumentError, "YAML aliases are not allowed" - end - item_anchor = $1 - content = content.sub(/^&#{Regexp.escape(item_anchor)}\s*/, "") - end + anchor = $1 + [anchor, val.sub(/^&#{Regexp.escape(anchor)}\s+/, "")] + end - # Check for alias in array item - if content.start_with?("*") - unless aliases - raise ArgumentError, "YAML aliases are not allowed" - end - alias_name = content[1..-1].strip - res << anchors[alias_name] - elsif content.empty? - # Empty array item - check if next line is nested content or a new item - item_value = if lines.any? && lines[0][/^ */].size > indent - parse_any(lines, indent, permitted_tags, aliases, anchors) - end - anchors[item_anchor] = item_value if item_anchor - res << item_value - elsif content.start_with?("!ruby/object:") - tag = content.strip - unless permitted_tags.include?(tag) - raise ArgumentError, "Disallowed class: #{tag}" - end - nested = parse_any(lines, indent, permitted_tags, aliases, anchors) - item_value = if nested.is_a?(Hash) - nested[:tag] = tag - nested - else - { :tag => tag, "value" => nested } - end - anchors[item_anchor] = item_value if item_anchor - res << item_value - elsif content.start_with?("-") - lines.unshift(" " * (indent + 2) + content) - item_value = parse_any(lines, indent, permitted_tags, aliases, anchors) - anchors[item_anchor] = item_value if item_anchor - res << item_value - elsif content =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !content.start_with?("!ruby/object:") - lines.unshift(" " * (indent + 2) + content) - item_value = parse_any(lines, indent, permitted_tags, aliases, anchors) - anchors[item_anchor] = item_value if item_anchor - res << item_value - elsif content.start_with?("|") - modifier = content[1..-1].to_s.strip - item_value = parse_block_scalar(lines, indent, modifier) - anchors[item_anchor] = item_value if item_anchor - res << item_value - else - str = unquote_simple(content) - while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent - str << " " << lines.shift.strip - end - anchors[item_anchor] = str if item_anchor - res << str - end + def register_anchor(name, node) + if name + @anchors[name] = node + node.anchor = name if node.respond_to?(:anchor=) end - result = res - elsif line.lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !line.lstrip.start_with?("!ruby/object:") - res = Hash.new - while lines.any? && lines[0][/^ */].size == indent && lines[0].lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ && !lines[0].lstrip.start_with?("!ruby/object:") - l = lines.shift - l.lstrip =~ /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ - key = $1.strip - val = $2.to_s.strip - val = strip_comment(val) - - # Check for anchor in value - val_anchor = nil - if val =~ /^&(\S+)\s+/ - unless aliases - raise ArgumentError, "YAML aliases are not allowed" - end - val_anchor = $1 - val = val.sub(/^&#{Regexp.escape(val_anchor)}\s+/, "") + node + end + + def skip_blank_and_comments + @lines.shift while @lines.any? && + (@lines[0].strip.empty? || @lines[0].lstrip.start_with?("#")) + end + + def strip_comment(val) + return val unless val.include?("#") + return val if val.lstrip.start_with?("#") + + in_single = false + in_double = false + escape = false + + val.each_char.with_index do |ch, i| + if escape + escape = false + next end - # Check for alias in value - if val.start_with?("*") - unless aliases - raise ArgumentError, "YAML aliases are not allowed" + if in_single + in_single = false if ch == "'" + elsif in_double + if ch == "\\" + escape = true + elsif ch == '"' + in_double = false end - alias_name = val[1..-1].strip - res[key] = anchors[alias_name] - elsif val.start_with?("!ruby/object:") - tag = val.strip - unless permitted_tags.include?(tag) - raise ArgumentError, "Disallowed class: #{tag}" - end - nested = parse_any(lines, indent, permitted_tags, aliases, anchors) - value = if nested.is_a?(Hash) - nested[:tag] = tag - nested - else - { :tag => tag, "value" => nested } - end - anchors[val_anchor] = value if val_anchor - res[key] = value - elsif val.empty? - value = if lines.any? && (lines[0].lstrip.start_with?("- ") || lines[0].lstrip == "-") && lines[0][/^ */].size == indent - parse_any(lines, indent, permitted_tags, aliases, anchors) - else - parse_any(lines, indent + 1, permitted_tags, aliases, anchors) - end - anchors[val_anchor] = value if val_anchor - res[key] = value - elsif val == "[]" - value = [] - anchors[val_anchor] = value if val_anchor - res[key] = value - elsif val == "{}" - value = {} - anchors[val_anchor] = value if val_anchor - res[key] = value - elsif val.start_with?("|") - modifier = val[1..-1].to_s.strip - value = parse_block_scalar(lines, indent, modifier) - anchors[val_anchor] = value if val_anchor - res[key] = value else - str = unquote_simple(val) - while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent - str << " " << lines.shift.strip + case ch + when "'" then in_single = true + when '"' then in_double = true + when "#" then return val[0...i].rstrip end - anchors[val_anchor] = str if val_anchor - res[key] = str end end - result = res - elsif line.lstrip.start_with?("!ruby/object:") - tag = lines.shift.lstrip.strip - unless permitted_tags.include?(tag) - raise ArgumentError, "Disallowed class: #{tag}" + + val + end + end + + class Builder + VALID_OPS = %w[= != > < >= <= ~>].freeze + ARRAY_FIELDS = %w[rdoc_options files test_files executables requirements extra_rdoc_files].freeze + + def initialize(permitted_classes: [], permitted_symbols: [], aliases: true) + @permitted_tags = Array(permitted_classes).map do |c| + "!ruby/object:#{c.is_a?(Module) ? c.name : c}" end - nested = parse_any(lines, indent, permitted_tags, aliases, anchors) - if nested.is_a?(Hash) - nested[:tag] = tag - result = nested + @permitted_symbols = permitted_symbols + @aliases = aliases + @anchor_values = {} + end + + def build(node) + return {} if node.nil? + + result = build_node(node) + + if result.is_a?(Hash) && + (result[:tag] == "!ruby/object:Gem::Specification" || + result["tag"] == "!ruby/object:Gem::Specification") + build_specification(result) else - result = { :tag => tag, "value" => nested } + result + end + end + + private + + def build_node(node) + case node + when nil then nil + when AliasRef then resolve_alias(node) + when Scalar then store_anchor(node.anchor, node.value) + when Mapping then build_mapping(node) + when Sequence then store_anchor(node.anchor, node.items.map {|item| build_node(item) }) + else node # already a Ruby object + end + end + + def resolve_alias(node) + raise ArgumentError, "YAML aliases are not allowed" unless @aliases + @anchor_values.fetch(node.name, nil) + end + + def store_anchor(name, value) + @anchor_values[name] = value if name + value + end + + def build_mapping(node) + validate_tag!(node.tag) if node.tag + check_anchor!(node) + + result = case node.tag + when "!ruby/object:Gem::Version" + build_version(node) + when "!ruby/object:Gem::Platform" + build_platform(node) + when "!ruby/object:Gem::Requirement", "!ruby/object:Gem::Version::Requirement" + build_requirement(node) + when "!ruby/object:Gem::Dependency" + build_dependency(node) + when nil + build_hash(node) + else + hash = build_hash(node) + hash[:tag] = node.tag + hash end - elsif line.lstrip.start_with?("|") - modifier = line.lstrip[1..-1].to_s.strip - lines.shift - result = parse_block_scalar(lines, indent, modifier) - else - str = unquote_simple(lines.shift.strip) - while lines.any? && !lines[0].strip.empty? && lines[0][/^ */].size > indent - str << " " << lines.shift.strip + + store_anchor(node.anchor, result) + end + + def build_hash(node) + result = {} + node.pairs.each do |key_node, value_node| + key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s + value = build_node(value_node) + + if ARRAY_FIELDS.include?(key) + value = normalize_array_field(value) + end + + result[key] = value end - result = str + result end - # Store anchor if present - anchors[anchor_name] = result if anchor_name - result - end + def build_version(node) + hash = pairs_to_hash(node) + Gem::Version.new((hash["version"] || hash["value"]).to_s) + end - def parse_block_scalar(lines, base_indent, modifier) - parts = [] - block_indent = nil - while lines.any? - if lines[0].strip.empty? - parts << "\n" - lines.shift + def build_platform(node) + hash = pairs_to_hash(node) + if hash["value"] + Gem::Platform.new(hash["value"]) else - line_indent = lines[0][/^ */].size - break if line_indent <= base_indent - block_indent ||= line_indent - l = lines.shift - parts << l[block_indent..-1].to_s << "\n" + Gem::Platform.new([hash["cpu"], hash["os"], hash["version"]]) end end - res = parts.join - res.chomp! if modifier == "-" && res.end_with?("\n") - res - end - def build_permitted_tags(permitted_classes) - Array(permitted_classes).map do |klass| - name = klass.is_a?(Module) ? klass.name : klass.to_s - "!ruby/object:#{name}" + def build_requirement(node) + r = Gem::Requirement.allocate + hash = pairs_to_hash(node) + reqs = hash["requirements"] || hash["value"] + reqs = [] unless reqs.is_a?(Array) + + if reqs.is_a?(Array) && !reqs.empty? + safe_reqs = [] + reqs.each do |item| + if item.is_a?(Array) && item.size == 2 + op = item[0].to_s + ver = item[1] + if VALID_OPS.include?(op) + version_obj = ver.is_a?(Gem::Version) ? ver : Gem::Version.new(ver.to_s) + safe_reqs << [op, version_obj] + end + elsif item.is_a?(String) + parsed = Gem::Requirement.parse(item) + safe_reqs << parsed + end + rescue Gem::Requirement::BadRequirementError, Gem::Version::BadVersionError + # Skip malformed items silently + end + reqs = safe_reqs unless safe_reqs.empty? + end + + r.instance_variable_set(:@requirements, reqs) + r end - end - def convert_to_spec(hash, permitted_symbols) - spec = Gem::Specification.allocate - return spec unless hash.is_a?(Hash) + def build_dependency(node) + hash = pairs_to_hash(node) + d = Gem::Dependency.allocate + d.instance_variable_set(:@name, hash["name"]) + + requirement = build_safe_requirement(hash["requirement"]) + d.instance_variable_set(:@requirement, requirement) - converted_hash = {} - hash.each {|k, v| converted_hash[k] = convert_any(v, permitted_symbols) } + type = hash["type"] + type = type ? type.to_s.sub(/^:/, "").to_sym : :runtime + validate_symbol!(type) + d.instance_variable_set(:@type, type) - # Ensure specification_version is an Integer if it's a valid numeric string - if converted_hash["specification_version"] && !converted_hash["specification_version"].is_a?(Integer) - val = converted_hash["specification_version"] - if val.is_a?(String) && /\A\d+\z/.match?(val) - converted_hash["specification_version"] = val.to_i + d.instance_variable_set(:@prerelease, ["true", true].include?(hash["prerelease"])) + d.instance_variable_set(:@version_requirements, d.instance_variable_get(:@requirement)) + d + end + + def build_specification(hash) + spec = Gem::Specification.allocate + + normalize_specification_version!(hash) + normalize_rdoc_options!(hash) + normalize_array_fields!(hash) + + spec.yaml_initialize("!ruby/object:Gem::Specification", hash) + spec + end + + def pairs_to_hash(node) + result = {} + node.pairs.each do |key_node, value_node| + key = key_node.is_a?(Scalar) ? key_node.value.to_s : build_node(key_node).to_s + result[key] = build_node(value_node) end + result end - # Debug: log rdoc_options that contain non-string elements - if converted_hash["rdoc_options"] && converted_hash["name"] - rdoc_opts = converted_hash["rdoc_options"] - has_non_string = case rdoc_opts - when Array then rdoc_opts.any? {|o| !o.is_a?(String) } - when Hash then true - else true + def build_safe_requirement(req_value) + return Gem::Requirement.default unless req_value + + converted = req_value + return Gem::Requirement.default unless converted.is_a?(Gem::Requirement) + + reqs = converted.instance_variable_get(:@requirements) + if reqs&.is_a?(Array) + valid = reqs.all? do |item| + next true if item == Gem::Requirement::DefaultRequirement + item.is_a?(Array) && item.size >= 2 && VALID_OPS.include?(item[0].to_s) + end + valid ? converted : Gem::Requirement.default + else + converted end - if has_non_string - warn "[DEBUG rdoc_options] gem=#{converted_hash["name"]} class=#{rdoc_opts.class} value=#{rdoc_opts.inspect}" + rescue StandardError + Gem::Requirement.default + end + + def validate_tag!(tag) + unless @permitted_tags.include?(tag) + raise ArgumentError, "Disallowed class: #{tag}" end end - # Ensure rdoc_options is an Array of Strings - if converted_hash["rdoc_options"].is_a?(Hash) - converted_hash["rdoc_options"] = converted_hash["rdoc_options"].values.flatten.compact.map(&:to_s) - elsif converted_hash["rdoc_options"].is_a?(Array) - converted_hash["rdoc_options"] = converted_hash["rdoc_options"].flat_map do |opt| - if opt.is_a?(Hash) - opt.flat_map {|k, v| [k.to_s, v.to_s] } - elsif opt.is_a?(String) - opt - else - opt.to_s - end + def validate_symbol!(sym) + if @permitted_symbols.any? && !@permitted_symbols.include?(sym.to_s) + raise ArgumentError, "Disallowed symbol: #{sym.inspect}" end end - # Ensure other array fields are properly typed - ["files", "test_files", "executables", "requirements", "extra_rdoc_files"].each do |field| - if converted_hash[field].is_a?(Hash) - converted_hash[field] = converted_hash[field].values.flatten.compact - elsif !converted_hash[field].is_a?(Array) && converted_hash[field] - converted_hash[field] = [converted_hash[field]].flatten.compact + def check_anchor!(node) + if node.anchor + raise ArgumentError, "YAML aliases are not allowed" unless @aliases end end - spec.yaml_initialize("!ruby/object:Gem::Specification", converted_hash) - spec - end + def normalize_specification_version!(hash) + val = hash["specification_version"] + return unless val && !val.is_a?(Integer) + hash["specification_version"] = val.to_i if val.is_a?(String) && /\A\d+\z/.match?(val) + end - def convert_any(obj, permitted_symbols) - if obj.is_a?(Hash) - if obj[:tag] == "!ruby/object:Gem::Version" - ver = obj["version"] || obj["value"] - Gem::Version.new(ver.to_s) - elsif obj[:tag] == "!ruby/object:Gem::Platform" - if obj["value"] - Gem::Platform.new(obj["value"]) - else - Gem::Platform.new([obj["cpu"], obj["os"], obj["version"]]) - end - elsif ["!ruby/object:Gem::Requirement", "!ruby/object:Gem::Version::Requirement"].include?(obj[:tag]) - r = Gem::Requirement.allocate - raw_reqs = obj["requirements"] || obj["value"] - reqs = convert_any(raw_reqs, permitted_symbols) - # Ensure reqs is an array (never nil or Hash) - reqs = [] unless reqs.is_a?(Array) - if reqs.is_a?(Array) && !reqs.empty? - safe_reqs = [] - reqs.each do |item| - if item.is_a?(Array) && item.size == 2 - op = item[0].to_s - ver = item[1] - # Validate that op is a valid requirement operator - if ["=", "!=", ">", "<", ">=", "<=", "~>"].include?(op) - version_obj = if ver.is_a?(Gem::Version) - ver - else - Gem::Version.new(ver.to_s) - end - safe_reqs << [op, version_obj] - end - elsif item.is_a?(String) - # Try to validate the requirement string - parsed = Gem::Requirement.parse(item) - safe_reqs << parsed - end - rescue Gem::Requirement::BadRequirementError, Gem::Version::BadVersionError - # Skip malformed items silently - end - reqs = safe_reqs unless safe_reqs.empty? - end - r.instance_variable_set(:@requirements, reqs) - r - elsif obj[:tag] == "!ruby/object:Gem::Dependency" - d = Gem::Dependency.allocate - d.instance_variable_set(:@name, obj["name"]) - - # Ensure requirement is properly formed - requirement = begin - converted_req = convert_any(obj["requirement"], permitted_symbols) - # Validate that the requirement has valid requirements - if converted_req.is_a?(Gem::Requirement) - # Check if the requirement has any invalid items - reqs = converted_req.instance_variable_get(:@requirements) - if reqs&.is_a?(Array) - # Verify all requirements are valid - valid = reqs.all? do |item| - next true if item == Gem::Requirement::DefaultRequirement - if item.is_a?(Array) && item.size >= 2 - ["=", "!=", ">", "<", ">=", "<=", "~>"].include?(item[0].to_s) - else - false - end - end - valid ? converted_req : Gem::Requirement.default - else - converted_req - end + def normalize_rdoc_options!(hash) + opts = hash["rdoc_options"] + if opts.is_a?(Hash) + hash["rdoc_options"] = opts.values.flatten.compact.map(&:to_s) + elsif opts.is_a?(Array) + hash["rdoc_options"] = opts.flat_map do |opt| + if opt.is_a?(Hash) + opt.flat_map {|k, v| [k.to_s, v.to_s] } + elsif opt.is_a?(String) + opt else - converted_req + opt.to_s end - rescue StandardError - Gem::Requirement.default end + end + end - d.instance_variable_set(:@requirement, requirement) - - type = obj["type"] - if type - type = type.to_s.sub(/^:/, "").to_sym - else - type = :runtime - end - if permitted_symbols.any? && !permitted_symbols.include?(type.to_s) - raise ArgumentError, "Disallowed symbol: #{type.inspect}" - end - d.instance_variable_set(:@type, type) + def normalize_array_fields!(hash) + ARRAY_FIELDS.each do |field| + next if field == "rdoc_options" # already handled + hash[field] = normalize_array_field(hash[field]) if hash[field] + end + end - d.instance_variable_set(:@prerelease, ["true", true].include?(obj["prerelease"])) - d.instance_variable_set(:@version_requirements, d.instance_variable_get(:@requirement)) - d + def normalize_array_field(value) + if value.is_a?(Hash) + value.values.flatten.compact + elsif !value.is_a?(Array) && value + [value].flatten.compact else - res = Hash.new - obj.each do |k, v| - next if k == :tag - key_str = k.to_s - converted_val = convert_any(v, permitted_symbols) - - # Convert Hash to Array for fields that should be arrays - if ["rdoc_options", "files", "test_files", "executables", "requirements", "extra_rdoc_files"].include?(key_str) - if converted_val.is_a?(Hash) - converted_val = converted_val.values.flatten.compact - elsif !converted_val.is_a?(Array) && converted_val - converted_val = [converted_val].flatten.compact - end - end - - res[key_str] = converted_val - end - res + value end - elsif obj.is_a?(Array) - obj.map {|i| convert_any(i, permitted_symbols) } - else - obj end end - def strip_comment(val) - return val unless val.include?("#") - return val if val.lstrip.start_with?("#") + class Emitter + def emit(obj) + "---#{emit_node(obj, 0)}" + end + + private + + def emit_node(obj, indent, quote: false) + case obj + when Gem::Specification then emit_specification(obj, indent) + when Gem::Version then emit_version(obj, indent) + when Gem::Platform then emit_platform(obj, indent) + when Gem::Requirement then emit_requirement(obj, indent) + when Gem::Dependency then emit_dependency(obj, indent) + when Hash then emit_hash(obj, indent) + when Array then emit_array(obj, indent) + when Time then emit_time(obj) + when String then emit_string(obj, indent, quote: quote) + when Numeric, Symbol, TrueClass, FalseClass, nil + " #{obj.inspect}\n" + else + " #{obj.to_s.inspect}\n" + end + end - in_single = false - in_double = false - escape = false + def emit_specification(spec, indent) + parts = [" !ruby/object:Gem::Specification\n"] + parts << "#{pad(indent)}name:#{emit_node(spec.name, indent + 2)}" + parts << "#{pad(indent)}version:#{emit_node(spec.version, indent + 2)}" + parts << "#{pad(indent)}platform: #{spec.platform}\n" + if spec.platform.to_s != spec.original_platform.to_s + parts << "#{pad(indent)}original_platform: #{spec.original_platform}\n" + end - val.each_char.with_index do |ch, i| - if escape - escape = false - next + attributes = Gem::Specification.attribute_names.map(&:to_s).sort - %w[name version platform] + attributes.each do |name| + val = spec.instance_variable_get("@#{name}") + next if val.nil? + parts << "#{pad(indent)}#{name}:#{emit_node(val, indent + 2)}" end - if in_single - in_single = false if ch == "'" - elsif in_double - if ch == "\\" - escape = true - elsif ch == '"' - in_double = false + res = parts.join + res << "\n" unless res.end_with?("\n") + res + end + + def emit_version(ver, indent) + " !ruby/object:Gem::Version\n" \ + "#{pad(indent)}version: #{emit_node(ver.version.to_s, indent + 2).lstrip}" + end + + def emit_platform(plat, indent) + " !ruby/object:Gem::Platform\n" \ + "#{pad(indent)}cpu: #{plat.cpu.inspect}\n" \ + "#{pad(indent)}os: #{plat.os.inspect}\n" \ + "#{pad(indent)}version: #{plat.version.inspect}\n" + end + + def emit_requirement(req, indent) + " !ruby/object:Gem::Requirement\n" \ + "#{pad(indent)}requirements:#{emit_node(req.requirements, indent + 2)}" + end + + def emit_dependency(dep, indent) + [ + " !ruby/object:Gem::Dependency\n", + "#{pad(indent)}name: #{emit_node(dep.name, indent + 2).lstrip}", + "#{pad(indent)}requirement:#{emit_node(dep.requirement, indent + 2)}", + "#{pad(indent)}type: #{emit_node(dep.type, indent + 2).lstrip}", + "#{pad(indent)}prerelease: #{emit_node(dep.prerelease?, indent + 2).lstrip}", + "#{pad(indent)}version_requirements:#{emit_node(dep.requirement, indent + 2)}", + ].join + end + + def emit_hash(hash, indent) + if hash.empty? + " {}\n" + else + parts = ["\n"] + hash.each do |k, v| + is_symbol = k.is_a?(Symbol) || (k.is_a?(String) && k.start_with?(":")) + key_str = k.is_a?(Symbol) ? k.inspect : k.to_s + parts << "#{pad(indent)}#{key_str}:#{emit_node(v, indent + 2, quote: is_symbol)}" end + parts.join + end + end + + def emit_array(arr, indent) + if arr.empty? + " []\n" else - case ch - when "'" - in_single = true - when '"' - in_double = true - when "#" - return val[0...i].rstrip + parts = ["\n"] + arr.each do |v| + parts << "#{pad(indent)}-#{emit_node(v, indent + 2)}" end + parts.join end end - val - end + def emit_time(time) + " #{time.utc.strftime("%Y-%m-%d %H:%M:%S.%N Z")}\n" + end - def unquote_simple(val) - # Strip YAML non-specific tag (! prefix), e.g. ! '>=' -> '>=' - val = val.sub(/^! /, "") if val.start_with?("! ") - - if val =~ /^"(.*)"$/ - $1.gsub(/\\"/, '"').gsub(/\\n/, "\n").gsub(/\\r/, "\r").gsub(/\\t/, "\t").gsub(/\\\\/, "\\") - elsif val =~ /^'(.*)'$/ - $1.gsub(/''/, "'") - elsif val == "true" - true - elsif val == "false" - false - elsif val == "nil" - nil - elsif val == "{}" - {} - elsif val =~ /^\[(.*)\]$/ - inner = $1.strip - return [] if inner.empty? - inner.split(/\s*,\s*/).reject(&:empty?).map {|element| unquote_simple(element) } - elsif /^\d{4}-\d{2}-\d{2}/.match?(val) - require "time" - begin - Time.parse(val) - rescue ArgumentError - val + def emit_string(str, indent, quote: false) + if str.include?("\n") + emit_block_scalar(str, indent) + elsif needs_quoting?(str, quote) + " #{str.to_s.inspect}\n" + else + " #{str}\n" end - elsif /^-?\d+$/.match?(val) - val.to_i - else - val end + + def emit_block_scalar(str, indent) + parts = [str.end_with?("\n") ? " |\n" : " |-\n"] + str.each_line do |line| + parts << "#{pad(indent + 2)}#{line}" + end + res = parts.join + res << "\n" unless res.end_with?("\n") + res + end + + def needs_quoting?(str, quote) + quote || str.empty? || + str =~ /^[!*&:@%$]/ || str =~ /^-?\d+(\.\d+)?$/ || str =~ /^[<>=-]/ || + str == "true" || str == "false" || str == "nil" || + str.include?(":") || str.include?("#") || str.include?("[") || str.include?("]") || + str.include?("{") || str.include?("}") || str.include?(",") + end + + def pad(indent) + " " * indent + end + end + + module_function + + def dump(obj) + Emitter.new.emit(obj) + end + + def load(str, permitted_classes: [], permitted_symbols: [], aliases: true) + return {} if str.nil? || str.empty? + + ast = Parser.new(str).parse + return {} if ast.nil? + + Builder.new( + permitted_classes: permitted_classes, + permitted_symbols: permitted_symbols, + aliases: aliases + ).build(ast) end end end From 2c9e4befbf014b3eff2e281aeb276fcf780055ca Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:11 +0900 Subject: [PATCH 63/87] [ruby/rubygems] Add use_psych config and make YAMLSerializer default YAML backend Add Gem.use_psych? and Gem.load_yaml branching so that YAMLSerializer is used by default, while Psych remains available via the use_psych config option in .gemrc or RUBYGEMS_USE_PSYCH environment variable. https://github.com/ruby/rubygems/commit/d67561aa06 Co-Authored-By: Claude Opus 4.6 --- lib/rubygems.rb | 23 +++++++++++++++++++++-- lib/rubygems/config_file.rb | 20 ++++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 490c81821dafd7..baf0599ee6bbd7 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -640,6 +640,14 @@ def self.add_to_load_path(*paths) end @yaml_loaded = false + @use_psych = nil + + ## + # Returns true if the Psych YAML parser is enabled via configuration. + + def self.use_psych? + @use_psych || false + end ## # Loads YAML, preferring Psych @@ -647,9 +655,20 @@ def self.add_to_load_path(*paths) def self.load_yaml return if @yaml_loaded - require "psych" - require_relative "rubygems/psych_tree" + @use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" || + (defined?(@configuration) && @configuration && !!@configuration[:use_psych]) + + if @use_psych + # Remove Psych stubs (defined by yaml_serializer.rb) before loading + # real Psych to avoid superclass mismatch errors + if defined?(Psych) && !defined?(Psych::VERSION) + Object.send(:remove_const, :Psych) + end + require "psych" + require_relative "rubygems/psych_tree" + end + require_relative "rubygems/yaml_serializer" require_relative "rubygems/safe_yaml" @yaml_loaded = true diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index 06ca73157a1916..bd66aa258dad22 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -49,6 +49,7 @@ class Gem::ConfigFile DEFAULT_IPV4_FALLBACK_ENABLED = false DEFAULT_INSTALL_EXTENSION_IN_LIB = false DEFAULT_GLOBAL_GEM_CACHE = false + DEFAULT_USE_PSYCH = false ## # For Ruby packagers to set configuration defaults. Set in @@ -161,6 +162,11 @@ class Gem::ConfigFile attr_accessor :global_gem_cache + ## + # Use Psych (C extension YAML parser) instead of the pure Ruby YAMLSerializer. + + attr_accessor :use_psych + ## # Path name of directory or file of openssl client certificate, used for remote https connection with client authentication @@ -199,6 +205,7 @@ def initialize(args) @install_extension_in_lib = DEFAULT_INSTALL_EXTENSION_IN_LIB @ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED @global_gem_cache = ENV["RUBYGEMS_GLOBAL_GEM_CACHE"] == "true" || DEFAULT_GLOBAL_GEM_CACHE + @use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" || DEFAULT_USE_PSYCH operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS) platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS) @@ -221,7 +228,7 @@ def initialize(args) # gemhome and gempath are not working with symbol keys if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days concurrent_downloads install_extension_in_lib ipv4_fallback_enabled - global_gem_cache sources + global_gem_cache use_psych sources disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k) k.to_sym else @@ -239,6 +246,7 @@ def initialize(args) @install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib @ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled @global_gem_cache = @hash[:global_gem_cache] if @hash.key? :global_gem_cache + @use_psych = @hash[:use_psych] if @hash.key? :use_psych @home = @hash[:gemhome] if @hash.key? :gemhome @path = @hash[:gempath] if @hash.key? :gempath @@ -378,7 +386,9 @@ def load_file(filename) begin config = self.class.load_with_rubygems_config_hash(File.read(filename)) - if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") } + has_invalid_keys = config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") } + has_invalid_values = config.values.any? {|v| v.is_a?(String) && v.gsub(%r{https?:\/\/}, "").match?(/\A\S+: /) } + if has_invalid_keys || has_invalid_values warn "Failed to load #{filename} because it doesn't contain valid YAML hash" return {} else @@ -563,7 +573,9 @@ def self.dump_with_rubygems_yaml(content) def self.load_with_rubygems_config_hash(yaml) require_relative "yaml_serializer" - content = Gem::YAMLSerializer.load(yaml) + content = Gem::YAMLSerializer.load(yaml, permitted_classes: []) + return {} unless content.is_a?(Hash) + deep_transform_config_keys!(content) end @@ -597,7 +609,7 @@ def self.deep_transform_config_keys!(config) else v end - elsif v.empty? + elsif v.respond_to?(:empty?) && v.empty? nil elsif v.is_a?(Hash) deep_transform_config_keys!(v) From 852e7cfab4b7a07300948634bb072441b6bcc1a1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:16 +0900 Subject: [PATCH 64/87] [ruby/rubygems] Use YAMLSerializer in SafeYAML with Psych fallback https://github.com/ruby/rubygems/commit/d81ae0a870 Co-Authored-By: Claude Opus 4.6 --- lib/rubygems/safe_yaml.rb | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index 6a02a482304dc0..1c15f10eb16661 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -35,11 +35,45 @@ def self.aliases_enabled? # :nodoc: end def self.safe_load(input) - ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) + if Gem.use_psych? + ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, + permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) + else + Gem::YAMLSerializer.load( + input, + permitted_classes: PERMITTED_CLASSES, + permitted_symbols: PERMITTED_SYMBOLS, + aliases: aliases_enabled? + ) + end end def self.load(input) - ::Psych.safe_load(input, permitted_classes: [::Symbol]) + if Gem.use_psych? + ::Psych.safe_load(input, permitted_classes: [::Symbol]) + else + Gem::YAMLSerializer.load( + input, + permitted_classes: [::Symbol] + ) + end + end + + def self.safe_load(input) + if Gem.use_psych? + if ::Psych.respond_to?(:unsafe_load) + ::Psych.unsafe_load(input) + else + ::Psych.load(input) + end + else + Gem::YAMLSerializer.load( + input, + permitted_classes: PERMITTED_CLASSES, + permitted_symbols: PERMITTED_SYMBOLS, + aliases: aliases_enabled? + ) + end end end end From 9e85f2c2bd182d5edcf899d62bbc786ce7f401cc Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:22 +0900 Subject: [PATCH 65/87] [ruby/rubygems] Use YAMLSerializer in Specification with Psych fallback https://github.com/ruby/rubygems/commit/b4655ddeb2 Co-Authored-By: Claude Opus 4.6 --- lib/rubygems/safe_marshal.rb | 1 + lib/rubygems/specification.rb | 36 +++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/rubygems/safe_marshal.rb b/lib/rubygems/safe_marshal.rb index b81d1a0a475a00..871f24727dcb44 100644 --- a/lib/rubygems/safe_marshal.rb +++ b/lib/rubygems/safe_marshal.rb @@ -54,6 +54,7 @@ module SafeMarshal "Gem::NameTuple" => %w[@name @version @platform], "Gem::Platform" => %w[@os @cpu @version], "Psych::PrivateType" => %w[@value @type_id], + "YAML::PrivateType" => %w[@value @type_id], }.freeze private_constant :PERMITTED_IVARS diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 64f289a7b41e50..d852332db7f92f 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -1275,7 +1275,7 @@ def self._load(str) raise unless message.include?("YAML::") unless Object.const_defined?(:YAML) - Object.const_set "YAML", Psych + Object.const_set "YAML", Module.new yaml_set = true end @@ -1284,7 +1284,7 @@ def self._load(str) YAML::Syck.const_set "DefaultKey", Class.new if message.include?("YAML::Syck::DefaultKey") && !YAML::Syck.const_defined?(:DefaultKey) elsif message.include?("YAML::PrivateType") && !YAML.const_defined?(:PrivateType) - YAML.const_set "PrivateType", Class.new + YAML.const_set "PrivateType", Class.new { attr_accessor :type_id, :value } end retry_count += 1 @@ -2455,24 +2455,28 @@ def to_spec def to_yaml(opts = {}) # :nodoc: Gem.load_yaml - # Because the user can switch the YAML engine behind our - # back, we have to check again here to make sure that our - # psych code was properly loaded, and load it if not. - unless Gem.const_defined?(:NoAliasYAMLTree) - require_relative "psych_tree" - end + if Gem.use_psych? + # Because the user can switch the YAML engine behind our + # back, we have to check again here to make sure that our + # psych code was properly loaded, and load it if not. + unless Gem.const_defined?(:NoAliasYAMLTree) + require_relative "psych_tree" + end - builder = Gem::NoAliasYAMLTree.create - builder << self - ast = builder.tree + builder = Gem::NoAliasYAMLTree.create + builder << self + ast = builder.tree - require "stringio" - io = StringIO.new - io.set_encoding Encoding::UTF_8 + require "stringio" + io = StringIO.new + io.set_encoding Encoding::UTF_8 - Psych::Visitors::Emitter.new(io).accept(ast) + Psych::Visitors::Emitter.new(io).accept(ast) - io.string.gsub(/ !!null \n/, " \n") + io.string.gsub(/ !!null \n/, " \n") + else + Gem::YAMLSerializer.dump(self) + end end ## From e954bd2b5076e1593602eca48a17e25cdb0541d3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:27 +0900 Subject: [PATCH 66/87] [ruby/rubygems] Use YAMLSerializer in Package with Psych fallback https://github.com/ruby/rubygems/commit/21c33bb482 Co-Authored-By: Claude Opus 4.6 --- lib/rubygems/package.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index e6e078dce40680..c433cf1a77983a 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -232,7 +232,11 @@ def add_checksums(tar) tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io| gzip_to io do |gz_io| - Psych.dump checksums_by_algorithm, gz_io + if Gem.use_psych? + Psych.dump checksums_by_algorithm, gz_io + else + gz_io.write Gem::YAMLSerializer.dump(checksums_by_algorithm) + end end end end From 6167a6c905b5613148549fd2023cb62dcdcff752 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:42 +0900 Subject: [PATCH 67/87] [ruby/rubygems] Use YAMLSerializer in specification_command with Psych fallback https://github.com/ruby/rubygems/commit/895c8799fc Co-Authored-By: Claude Opus 4.6 --- lib/rubygems/commands/specification_command.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems/commands/specification_command.rb b/lib/rubygems/commands/specification_command.rb index a21ed35be316a4..ec81917d2a88ff 100644 --- a/lib/rubygems/commands/specification_command.rb +++ b/lib/rubygems/commands/specification_command.rb @@ -147,7 +147,7 @@ def execute say case options[:format] when :ruby then s.to_ruby when :marshal then Marshal.dump s - else s.to_yaml + else Gem.use_psych? ? s.to_yaml : Gem::YAMLSerializer.dump(s) end say "\n" From 2781b19ca6c6d6da83f45c77f29d75ce1437ea5e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:17:48 +0900 Subject: [PATCH 68/87] [ruby/rubygems] Update test helpers for YAMLSerializer https://github.com/ruby/rubygems/commit/9d54d0f830 Co-Authored-By: Claude Opus 4.6 --- test/rubygems/helper.rb | 6 +----- test/rubygems/test_gem_commands_owner_command.rb | 4 +--- test/rubygems/test_gem_package.rb | 12 ++++++++++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index dc40f4ecb1f8ec..ec373d41e0202a 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -738,11 +738,7 @@ def write_dummy_extconf(gem_name) # Load a YAML string, the psych 3 way def load_yaml(yaml) - if Psych.respond_to?(:unsafe_load) - Psych.unsafe_load(yaml) - else - Psych.load(yaml) - end + Gem::SafeYAML.safe_load(yaml) end ## diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index 80b1497c415ae9..be4eee00e63cd0 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -55,8 +55,6 @@ def test_show_owners end def test_show_owners_dont_load_objects - pend "testing a psych-only API" unless defined?(::Psych::DisallowedClass) - response = < Date: Mon, 9 Mar 2026 12:17:53 +0900 Subject: [PATCH 69/87] [ruby/rubygems] Update bundler inline spec expectations https://github.com/ruby/rubygems/commit/825d4eba3c Co-Authored-By: Claude Opus 4.6 --- spec/bundler/runtime/inline_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index e55d029a4b689f..daf966f458c45d 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -678,8 +678,10 @@ def confirm(msg, newline = nil) expect(out).to include("Installing psych 999") expect(out).to include("Installing stringio 999") - expect(out).to include("The psych gem was resolved to 999") - expect(out).to include("The stringio gem was resolved to 999") + if Gem.respond_to?(:use_psych?) && Gem.use_psych? + expect(out).to include("The psych gem was resolved to 999") + expect(out).to include("The stringio gem was resolved to 999") + end end it "leaves a lockfile in the same directory as the inline script alone" do From 1cd2cc2cdfb8c811be8e88a33cbc3a1b6665b8d3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 10:46:07 +0900 Subject: [PATCH 70/87] [ruby/rubygems] Use Psych-specific YAML error classes https://github.com/ruby/rubygems/commit/e07e88a232 --- lib/rubygems/yaml_serializer.rb | 17 +++++++++++++---- .../rubygems/test_gem_commands_owner_command.rb | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 720911194e1d32..878c958a46bb1b 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -1,5 +1,14 @@ # frozen_string_literal: true +unless defined?(Psych) + module Psych + class SyntaxError < ::StandardError; end + class DisallowedClass < ::ArgumentError; end + class BadAlias < ::ArgumentError; end + class AliasesNotEnabled < BadAlias; end + end +end + module Gem module YAMLSerializer Scalar = Struct.new(:value, :tag, :anchor, keyword_init: true) @@ -378,7 +387,7 @@ def build_node(node) end def resolve_alias(node) - raise ArgumentError, "YAML aliases are not allowed" unless @aliases + raise Psych::AliasesNotEnabled, "YAML aliases are not allowed" unless @aliases @anchor_values.fetch(node.name, nil) end @@ -530,19 +539,19 @@ def build_safe_requirement(req_value) def validate_tag!(tag) unless @permitted_tags.include?(tag) - raise ArgumentError, "Disallowed class: #{tag}" + raise Psych::DisallowedClass, "Disallowed class: #{tag}" end end def validate_symbol!(sym) if @permitted_symbols.any? && !@permitted_symbols.include?(sym.to_s) - raise ArgumentError, "Disallowed symbol: #{sym.inspect}" + raise Psych::DisallowedClass, "Disallowed symbol: #{sym.inspect}" end end def check_anchor!(node) if node.anchor - raise ArgumentError, "YAML aliases are not allowed" unless @aliases + raise Psych::AliasesNotEnabled, "YAML aliases are not allowed" unless @aliases end end diff --git a/test/rubygems/test_gem_commands_owner_command.rb b/test/rubygems/test_gem_commands_owner_command.rb index be4eee00e63cd0..80b1497c415ae9 100644 --- a/test/rubygems/test_gem_commands_owner_command.rb +++ b/test/rubygems/test_gem_commands_owner_command.rb @@ -55,6 +55,8 @@ def test_show_owners end def test_show_owners_dont_load_objects + pend "testing a psych-only API" unless defined?(::Psych::DisallowedClass) + response = < Date: Fri, 6 Mar 2026 16:22:01 +0900 Subject: [PATCH 71/87] [ruby/rubygems] Simplify indentation handling in YAML serializer https://github.com/ruby/rubygems/commit/50becac99a --- lib/rubygems/yaml_serializer.rb | 66 +++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 878c958a46bb1b..6c89cf2a9a2d53 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -71,23 +71,28 @@ def parse_node(base_indent) skip_blank_and_comments return nil if @lines.empty? - indent = @lines[0][/^ */].size - return nil if indent < base_indent - line = @lines[0] + stripped = line.lstrip + indent = line.size - stripped.size + return nil if indent < base_indent - return parse_alias_ref if line.lstrip.start_with?("*") + return parse_alias_ref if stripped.start_with?("*") anchor = consume_anchor - if line.lstrip.start_with?("- ") || line.lstrip == "-" + if anchor + line = @lines[0] + stripped = line.lstrip + end + + if stripped.start_with?("- ") || stripped == "-" parse_sequence(indent, anchor) - elsif line.lstrip =~ MAPPING_KEY_RE && !line.lstrip.start_with?("!ruby/object:") + elsif stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:") parse_mapping(indent, anchor) - elsif line.lstrip.start_with?("!ruby/object:") + elsif stripped.start_with?("!ruby/object:") parse_tagged_node(indent, anchor) - elsif line.lstrip.start_with?("|") - modifier = line.lstrip[1..].to_s.strip + elsif stripped.start_with?("|") + modifier = stripped[1..].to_s.strip @lines.shift register_anchor(anchor, Scalar.new(value: parse_block_scalar(indent, modifier))) else @@ -97,8 +102,11 @@ def parse_node(base_indent) def parse_sequence(indent, anchor) items = [] - while @lines.any? && @lines[0][/^ */].size == indent && - (@lines[0].lstrip.start_with?("- ") || @lines[0].lstrip == "-") + while @lines.any? + line = @lines[0] + stripped = line.lstrip + break unless line.size - stripped.size == indent && + (stripped.start_with?("- ") || stripped == "-") content = @lines.shift.lstrip[1..].strip item_anchor, content = extract_item_anchor(content) item = parse_sequence_item(content, indent) @@ -111,7 +119,7 @@ def parse_sequence_item(content, indent) if content.start_with?("*") parse_inline_alias(content) elsif content.empty? - @lines.any? && @lines[0][/^ */].size > indent ? parse_node(indent) : nil + @lines.any? && current_indent > indent ? parse_node(indent) : nil elsif content.start_with?("!ruby/object:") parse_tagged_content(content.strip, indent) elsif content.start_with?("-") @@ -129,11 +137,13 @@ def parse_sequence_item(content, indent) def parse_mapping(indent, anchor) pairs = [] - while @lines.any? && @lines[0][/^ */].size == indent && - @lines[0].lstrip =~ MAPPING_KEY_RE && !@lines[0].lstrip.start_with?("!ruby/object:") - l = @lines.shift - l.lstrip =~ MAPPING_KEY_RE + while @lines.any? + line = @lines[0] + stripped = line.lstrip + break unless line.size - stripped.size == indent && + stripped =~ MAPPING_KEY_RE && !stripped.start_with?("!ruby/object:") key = $1.strip + @lines.shift val = strip_comment($2.to_s.strip) val_anchor, val = consume_value_anchor(val) @@ -151,9 +161,13 @@ def parse_mapping_value(val, indent) elsif val.start_with?("!ruby/object:") parse_tagged_content(val.strip, indent) elsif val.empty? + if @lines.any? + next_stripped = @lines[0].lstrip + next_indent = @lines[0].size - next_stripped.size + end if @lines.any? && - (@lines[0].lstrip.start_with?("- ") || @lines[0].lstrip == "-") && - @lines[0][/^ */].size == indent + (next_stripped.start_with?("- ") || next_stripped == "-") && + next_indent == indent parse_node(indent) else parse_node(indent + 1) @@ -170,7 +184,7 @@ def parse_mapping_value(val, indent) end def parse_tagged_node(indent, anchor) - tag = @lines.shift.lstrip.strip + tag = @lines.shift.strip nested = parse_node(indent) apply_tag(nested, tag, anchor) end @@ -195,11 +209,12 @@ def parse_block_scalar(base_indent, modifier) block_indent = nil while @lines.any? - if @lines[0].strip.empty? + line = @lines[0] + if line.strip.empty? parts << "\n" @lines.shift else - line_indent = @lines[0][/^ */].size + line_indent = line.size - line.lstrip.size break if line_indent <= base_indent block_indent ||= line_indent parts << @lines.shift[block_indent..].to_s << "\n" @@ -216,7 +231,7 @@ def parse_plain_scalar(indent, anchor) return register_anchor(anchor, result) if result.is_a?(Mapping) || result.is_a?(Sequence) while result.is_a?(String) && @lines.any? && - !@lines[0].strip.empty? && @lines[0][/^ */].size > indent + !@lines[0].strip.empty? && current_indent > indent result << " " << @lines.shift.strip end register_anchor(anchor, Scalar.new(value: result)) @@ -227,7 +242,7 @@ def parse_inline_scalar(val, indent) return result if result.is_a?(Mapping) || result.is_a?(Sequence) while result.is_a?(String) && @lines.any? && - !@lines[0].strip.empty? && @lines[0][/^ */].size > indent + !@lines[0].strip.empty? && current_indent > indent result << " " << @lines.shift.strip end Scalar.new(value: result) @@ -275,6 +290,11 @@ def parse_inline_alias(content) AliasRef.new(name: content[1..].strip) end + def current_indent + line = @lines[0] + line.size - line.lstrip.size + end + def consume_anchor line = @lines[0] return nil unless line.lstrip =~ /^&(\S+)\s+/ From 00e054f21a105712fa15e5535e42c0be3eea728b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 16:28:46 +0900 Subject: [PATCH 72/87] [ruby/rubygems] Optimize YAML serializer line handling https://github.com/ruby/rubygems/commit/ef022c664f --- lib/rubygems/yaml_serializer.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 6c89cf2a9a2d53..89a3538a80e077 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -31,7 +31,7 @@ class Parser MAPPING_KEY_RE = /^((?:[^#:]|:[^ ])+):(?:[ ]+(.*))?$/ def initialize(source) - @lines = source.split(/\r?\n/) + @lines = source.split("\n") @anchors = {} strip_document_prefix end @@ -297,7 +297,8 @@ def current_indent def consume_anchor line = @lines[0] - return nil unless line.lstrip =~ /^&(\S+)\s+/ + stripped = line.lstrip + return nil unless stripped.start_with?("&") && stripped =~ /^&(\S+)\s+/ anchor = $1 @lines[0] = line.sub(/&#{Regexp.escape(anchor)}\s+/, "") @@ -327,8 +328,12 @@ def register_anchor(name, node) end def skip_blank_and_comments - @lines.shift while @lines.any? && - (@lines[0].strip.empty? || @lines[0].lstrip.start_with?("#")) + while @lines.any? + line = @lines[0] + stripped = line.lstrip + break unless stripped.empty? || stripped.start_with?("#") + @lines.shift + end end def strip_comment(val) From 8a19f693e7cc9b2736085b8d54eae6bbe242c10a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 16:53:25 +0900 Subject: [PATCH 73/87] [ruby/rubygems] Guard against nil next line in YAML serializer https://github.com/ruby/rubygems/commit/faab31b5cf --- lib/rubygems/yaml_serializer.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 89a3538a80e077..88b0100f2183ad 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -161,11 +161,13 @@ def parse_mapping_value(val, indent) elsif val.start_with?("!ruby/object:") parse_tagged_content(val.strip, indent) elsif val.empty? + next_stripped = nil + next_indent = nil if @lines.any? next_stripped = @lines[0].lstrip next_indent = @lines[0].size - next_stripped.size end - if @lines.any? && + if next_stripped && (next_stripped.start_with?("- ") || next_stripped == "-") && next_indent == indent parse_node(indent) From fad2934d9a3d50b06d0b58e8bc4115942964c05c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 13:38:57 +0900 Subject: [PATCH 74/87] [ruby/rubygems] Add comprehensive SafeYAML and YAMLSerializer tests Add tests covering the full pure-Ruby YAML implementation: - Gem object serialization round-trips (dump and load) - YAML anchors and aliases (enabled and disabled) - Permitted classes and symbols validation - Real-world gemspec parsing (fileutils, rubygems-bundler) - Edge cases: empty requirements, Hash-to-Array normalization, rdoc_options conversion, flow notation, non-specific tags, comment-only documents, special character quoting https://github.com/ruby/rubygems/commit/b38681e4e8 --- test/rubygems/test_gem_safe_yaml.rb | 550 ++++++++++++++++++++++++++++ 1 file changed, 550 insertions(+) diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb index 02df9f97da58ac..63cb91d29726f2 100644 --- a/test/rubygems/test_gem_safe_yaml.rb +++ b/test/rubygems/test_gem_safe_yaml.rb @@ -6,11 +6,13 @@ class TestGemSafeYAML < Gem::TestCase def test_aliases_enabled_by_default + pend "Psych is not loaded" if defined?(Gem::YAMLSerializer) assert_predicate Gem::SafeYAML, :aliases_enabled? assert_equal({ "a" => "a", "b" => "a" }, Gem::SafeYAML.safe_load("a: &a a\nb: *a\n")) end def test_aliases_disabled + pend "Psych is not loaded" if defined?(Gem::YAMLSerializer) aliases_enabled = Gem::SafeYAML.aliases_enabled? Gem::SafeYAML.aliases_enabled = false refute_predicate Gem::SafeYAML, :aliases_enabled? @@ -21,4 +23,552 @@ def test_aliases_disabled ensure Gem::SafeYAML.aliases_enabled = aliases_enabled end + + def test_specification_version_is_integer + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test + version: !ruby/object:Gem::Version + version: 1.0.0 + specification_version: 4 + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Integer, spec.specification_version + assert_equal 4, spec.specification_version + end + + def test_disallowed_class_rejected + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:SomeDisallowedClass + foo: bar + YAML + + exception = assert_raise(ArgumentError) do + Gem::SafeYAML.safe_load(yaml) + end + assert_match(/Disallowed class/, exception.message) + end + + def test_disallowed_symbol_rejected + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:Gem::Dependency + name: test + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 0 + type: :invalid_type + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 0 + YAML + + exception = assert_raise(ArgumentError) do + Gem::SafeYAML.safe_load(yaml) + end + assert_match(/Disallowed symbol/, exception.message) + end + + def test_yaml_serializer_aliases_disabled + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = false + refute_predicate Gem::SafeYAML, :aliases_enabled? + + yaml = "a: &anchor value\nb: *anchor\n" + + exception = assert_raise(ArgumentError) do + Gem::SafeYAML.safe_load(yaml) + end + assert_match(/YAML aliases are not allowed/, exception.message) + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_real_gemspec_fileutils + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: fileutils + version: !ruby/object:Gem::Version + version: 1.8.0 + platform: ruby + authors: + - Minero Aoki + bindir: bin + cert_chain: [] + date: 1980-01-02 00:00:00.000000000 Z + dependencies: [] + description: Several file utility methods for copying, moving, removing, etc. + email: + - + executables: [] + extensions: [] + extra_rdoc_files: [] + files: + - BSDL + - COPYING + - README.md + - Rakefile + - fileutils.gemspec + - lib/fileutils.rb + homepage: https://github.com/ruby/fileutils + licenses: + - Ruby + - BSD-2-Clause + metadata: + source_code_uri: https://github.com/ruby/fileutils + rdoc_options: [] + require_paths: + - lib + required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 2.5.0 + required_rubygems_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + requirements: [] + rubygems_version: 3.6.9 + specification_version: 4 + summary: Several file utility methods for copying, moving, removing, etc. + test_files: [] + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "fileutils", spec.name + assert_equal Gem::Version.new("1.8.0"), spec.version + assert_kind_of Integer, spec.specification_version + assert_equal 4, spec.specification_version + end + + def test_yaml_anchor_and_alias_enabled + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = true + + yaml = <<~YAML + dependencies: + - &req !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + - *req + YAML + + result = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Hash, result + assert_kind_of Array, result["dependencies"] + assert_equal 2, result["dependencies"].size + assert_kind_of Gem::Requirement, result["dependencies"][0] + assert_kind_of Gem::Requirement, result["dependencies"][1] + assert_equal result["dependencies"][0].requirements, result["dependencies"][1].requirements + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_real_gemspec_rubygems_bundler + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: rubygems-bundler + version: !ruby/object:Gem::Version + version: 1.4.5 + platform: ruby + authors: + - Josh Hull + - Michal Papis + autorequire: + bindir: bin + cert_chain: [] + date: 2018-06-24 00:00:00.000000000 Z + dependencies: + - !ruby/object:Gem::Dependency + name: bundler-unload + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.0.2 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: 1.0.2 + description: Stop using bundle exec. + email: + - joshbuddy@gmail.com + - mpapis@gmail.com + executables: [] + extensions: [] + extra_rdoc_files: [] + files: + - ".gem.config" + homepage: http://mpapis.github.com/rubygems-bundler + licenses: + - Apache-2.0 + metadata: {} + post_install_message: + rdoc_options: [] + require_paths: + - lib + required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + rubyforge_project: + rubygems_version: 2.7.6 + signing_key: + specification_version: 4 + summary: Stop using bundle exec + test_files: [] + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "rubygems-bundler", spec.name + assert_equal Gem::Version.new("1.4.5"), spec.version + assert_equal 1, spec.dependencies.size + + dep = spec.dependencies.first + assert_equal "bundler-unload", dep.name + assert_kind_of Gem::Requirement, dep.requirement + assert_kind_of Gem::Requirement, dep.instance_variable_get(:@version_requirements) + assert_equal dep.requirement.requirements, [[">=", Gem::Version.new("1.0.2")]] + + # Empty fields should be nil + assert_nil spec.autorequire + assert_nil spec.post_install_message + + # Metadata should be empty hash + assert_equal({}, spec.metadata) + + # specification_version should be Integer + assert_kind_of Integer, spec.specification_version + assert_equal 4, spec.specification_version + end + + def test_empty_requirements_array + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test + dependencies: + - !ruby/object:Gem::Dependency + name: foo + requirement: !ruby/object:Gem::Requirement + requirements: + type: :runtime + version_requirements: !ruby/object:Gem::Requirement + requirements: + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "test", spec.name + assert_equal 1, spec.dependencies.size + + dep = spec.dependencies.first + assert_equal "foo", dep.name + assert_kind_of Gem::Requirement, dep.requirement + + # Requirements should be empty array, not nil + reqs = dep.requirement.instance_variable_get(:@requirements) + assert_kind_of Array, reqs + assert_equal [], reqs + end + + def test_requirements_hash_converted_to_array + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Malformed YAML where requirements is a Hash instead of Array + yaml = <<~YAML + !ruby/object:Gem::Requirement + requirements: + foo: bar + YAML + + req = Gem::YAMLSerializer.load(yaml, permitted_classes: ["Gem::Requirement"]) + assert_kind_of Gem::Requirement, req + + # Requirements should be converted from Hash to empty Array + reqs = req.instance_variable_get(:@requirements) + assert_kind_of Array, reqs + assert_equal [], reqs + + # Should not raise error when used + assert req.satisfied_by?(Gem::Version.new("1.0")) + end + + def test_rdoc_options_hash_converted_to_array + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Some gemspecs incorrectly have rdoc_options: {} instead of rdoc_options: [] + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test-gem + version: !ruby/object:Gem::Version + version: 1.0.0 + rdoc_options: {} + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "test-gem", spec.name + + # rdoc_options should be converted from Hash to Array + assert_kind_of Array, spec.rdoc_options + assert_equal [], spec.rdoc_options + end + + def test_load_returns_hash_for_comment_only_yaml + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Bundler config files may contain only comments after deleting all keys + result = Gem::YAMLSerializer.load("---\n# BUNDLE_FOO: \"bar\"\n") + assert_kind_of Hash, result + assert_empty result + end + + def test_load_returns_hash_for_empty_document + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + assert_equal({}, Gem::YAMLSerializer.load("---\n")) + assert_equal({}, Gem::YAMLSerializer.load("")) + assert_equal({}, Gem::YAMLSerializer.load(nil)) + end + + def test_load_returns_hash_for_flow_empty_hash + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Gem::YAMLSerializer.dump({}) produces "--- {}\n" + result = Gem::YAMLSerializer.load("--- {}\n") + assert_kind_of Hash, result + assert_empty result + end + + def test_load_parses_flow_empty_hash_as_value + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + result = Gem::YAMLSerializer.load("metadata: {}\n") + assert_kind_of Hash, result + assert_kind_of Hash, result["metadata"] + assert_empty result["metadata"] + end + + def test_yaml_non_specific_tag_stripped + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Legacy RubyGems (1.x) generated YAML with ! non-specific tags like: + # - ! '>=' + # The ! prefix should be ignored. + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: legacy-gem + version: !ruby/object:Gem::Version + version: 0.1.0 + required_ruby_version: !ruby/object:Gem::Requirement + none: false + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '0' + required_rubygems_version: !ruby/object:Gem::Requirement + none: false + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: 1.3.5 + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "legacy-gem", spec.name + assert_equal Gem::Requirement.new(">= 0"), spec.required_ruby_version + assert_equal Gem::Requirement.new(">= 1.3.5"), spec.required_rubygems_version + end + + def test_legacy_gemspec_with_anchors_and_non_specific_tags + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = true + + # Real-world pattern from gems like vegas-0.1.11 that combine + # YAML anchors/aliases with ! non-specific tags + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: legacy-gem + version: !ruby/object:Gem::Version + version: 0.1.11 + dependencies: + - !ruby/object:Gem::Dependency + name: rack + requirement: &id001 !ruby/object:Gem::Requirement + none: false + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: 1.0.0 + type: :runtime + prerelease: false + version_requirements: *id001 + - !ruby/object:Gem::Dependency + name: mocha + requirement: &id002 !ruby/object:Gem::Requirement + none: false + requirements: + - - ~> + - !ruby/object:Gem::Version + version: 0.9.8 + type: :development + prerelease: false + version_requirements: *id002 + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "legacy-gem", spec.name + + assert_equal 2, spec.dependencies.size + + rack_dep = spec.dependencies.find {|d| d.name == "rack" } + assert_kind_of Gem::Dependency, rack_dep + assert_equal :runtime, rack_dep.type + assert_equal Gem::Requirement.new(">= 1.0.0"), rack_dep.requirement + + mocha_dep = spec.dependencies.find {|d| d.name == "mocha" } + assert_kind_of Gem::Dependency, mocha_dep + assert_equal :development, mocha_dep.type + assert_equal Gem::Requirement.new("~> 0.9.8"), mocha_dep.requirement + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_non_specific_tag_on_plain_value + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # ! tag on a bracketed value like rubyforge_project: ! '[none]' + result = Gem::YAMLSerializer.load("key: ! '[none]'\n") + assert_equal({ "key" => "[none]" }, result) + end + + def test_dump_quotes_dollar_sign_values + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Values starting with $ should be quoted to preserve them as strings + yaml = Gem::YAMLSerializer.dump({ "BUNDLE_FOO" => "$BUILD_DIR", "BUNDLE_BAR" => "baz" }) + assert_include yaml, 'BUNDLE_FOO: "$BUILD_DIR"' + assert_include yaml, "BUNDLE_BAR: baz" + + # Round-trip: ensure the quoted value is parsed back correctly + result = Gem::YAMLSerializer.load(yaml) + assert_equal "$BUILD_DIR", result["BUNDLE_FOO"] + assert_equal "baz", result["BUNDLE_BAR"] + end + + def test_dump_quotes_special_characters + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Various special characters that should trigger quoting + special_values = { + "dollar" => "$HOME", + "exclamation" => "!important", + "ampersand" => "&anchor", + "asterisk" => "*ref", + "colon_prefix" => ":symbol", + "at_sign" => "@mention", + "percent" => "%encoded", + } + + yaml = Gem::YAMLSerializer.dump(special_values) + special_values.each do |key, value| + assert_include yaml, "#{key}: #{value.inspect}", "Value #{value.inspect} for key #{key} should be quoted" + end + + # Round-trip + result = Gem::YAMLSerializer.load(yaml) + special_values.each do |key, value| + assert_equal value, result[key], "Round-trip failed for key #{key}" + end + end + + def test_load_ambiguous_value_with_colon + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # "invalid: yaml: hah" is ambiguous YAML - our parser treats it as + # {"invalid" => "yaml: hah"}, but the value looks like a nested mapping. + # config_file.rb's load_file should detect this and reject it. + result = Gem::YAMLSerializer.load("invalid: yaml: hah") + assert_kind_of Hash, result + assert_equal "yaml: hah", result["invalid"] + end + + def test_nested_anchor_in_array_item + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + # Ensure aliases are enabled for this test + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = true + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test-gem + version: !ruby/object:Gem::Version + version: 1.0.0 + dependencies: + - !ruby/object:Gem::Dependency + name: foo + requirement: !ruby/object:Gem::Requirement + requirements: + - &id002 + - ">=" + - !ruby/object:Gem::Version + version: "0" + type: :runtime + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + assert_equal "test-gem", spec.name + + dep = spec.dependencies.first + assert_kind_of Gem::Dependency, dep + + # Requirements should be parsed as nested arrays, not strings + assert_kind_of Array, dep.requirement.requirements + assert_equal 1, dep.requirement.requirements.size + + req_item = dep.requirement.requirements.first + assert_kind_of Array, req_item + assert_equal ">=", req_item[0] + assert_kind_of Gem::Version, req_item[1] + assert_equal "0", req_item[1].version + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end end From 88aeabf8dab66b46da8f82d312ba580b90772678 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 14:54:35 +0900 Subject: [PATCH 75/87] [ruby/rubygems] Add YAMLSerializer round-trip tests https://github.com/ruby/rubygems/commit/89ea9dbb19 --- test/rubygems/test_gem_safe_yaml.rb | 166 ++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb index 63cb91d29726f2..f9deda04ae01f5 100644 --- a/test/rubygems/test_gem_safe_yaml.rb +++ b/test/rubygems/test_gem_safe_yaml.rb @@ -571,4 +571,170 @@ def test_nested_anchor_in_array_item ensure Gem::SafeYAML.aliases_enabled = aliases_enabled end + + def test_roundtrip_specification + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + spec = Gem::Specification.new do |s| + s.name = "round-trip-test" + s.version = "2.3.4" + s.platform = "ruby" + s.authors = ["Test Author"] + s.summary = "A test gem for round-trip" + s.description = "Longer description of the test gem" + s.files = ["lib/foo.rb", "README.md"] + s.require_paths = ["lib"] + s.homepage = "https://example.com" + s.licenses = ["MIT"] + s.metadata = { "source_code_uri" => "https://example.com/src" } + s.add_dependency "rake", ">= 1.0" + end + + yaml = Gem::YAMLSerializer.dump(spec) + loaded = Gem::SafeYAML.safe_load(yaml) + + assert_kind_of Gem::Specification, loaded + assert_equal "round-trip-test", loaded.name + assert_equal Gem::Version.new("2.3.4"), loaded.version + assert_equal ["Test Author"], loaded.authors + assert_equal "A test gem for round-trip", loaded.summary + assert_equal ["README.md", "lib/foo.rb"], loaded.files + assert_equal ["lib"], loaded.require_paths + assert_equal "https://example.com", loaded.homepage + assert_equal ["MIT"], loaded.licenses + assert_equal({ "source_code_uri" => "https://example.com/src" }, loaded.metadata) + assert_equal 1, loaded.dependencies.size + + dep = loaded.dependencies.first + assert_equal "rake", dep.name + assert_equal :runtime, dep.type + end + + def test_roundtrip_version + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + ver = Gem::Version.new("1.2.3") + yaml = Gem::YAMLSerializer.dump(ver) + loaded = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + + assert_kind_of Gem::Version, loaded + assert_equal ver, loaded + end + + def test_roundtrip_platform + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + plat = Gem::Platform.new("x86_64-linux") + yaml = Gem::YAMLSerializer.dump(plat) + loaded = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + + assert_kind_of Gem::Platform, loaded + assert_equal plat.cpu, loaded.cpu + assert_equal plat.os, loaded.os + assert_equal plat.version, loaded.version + end + + def test_roundtrip_requirement + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + req = Gem::Requirement.new(">= 1.0", "< 2.0") + yaml = Gem::YAMLSerializer.dump(req) + loaded = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + + assert_kind_of Gem::Requirement, loaded + assert_equal req.requirements.sort_by(&:to_s), loaded.requirements.sort_by(&:to_s) + end + + def test_roundtrip_dependency + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + dep = Gem::Dependency.new("foo", ">= 1.0", :development) + yaml = Gem::YAMLSerializer.dump(dep) + loaded = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + + assert_kind_of Gem::Dependency, loaded + assert_equal "foo", loaded.name + assert_equal :development, loaded.type + assert_equal dep.requirement.requirements, loaded.requirement.requirements + end + + def test_roundtrip_nested_hash + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + obj = { "a" => { "b" => "c", "d" => [1, 2, 3] } } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + assert_equal obj, loaded + end + + def test_roundtrip_block_scalar + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + obj = { "text" => "line1\nline2\n" } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + assert_equal "line1\nline2\n", loaded["text"] + end + + def test_roundtrip_special_characters + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + obj = { + "dollar" => "$HOME", + "exclamation" => "!important", + "ampersand" => "&anchor", + "asterisk" => "*ref", + "colon_prefix" => ":symbol", + "hash_char" => "value#comment", + "brackets" => "[item]", + "braces" => "{key}", + "comma" => "a,b,c", + } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + obj.each do |key, value| + assert_equal value, loaded[key], "Round-trip failed for key #{key}" + end + end + + def test_roundtrip_boolean_nil_integer + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + obj = { "flag" => true, "count" => 42, "empty" => nil, "off" => false } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + assert_equal true, loaded["flag"] + assert_equal 42, loaded["count"] + assert_nil loaded["empty"] + assert_equal false, loaded["off"] + end + + def test_roundtrip_time + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + time = Time.utc(2024, 6, 15, 12, 30, 45) + obj = { "created" => time } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + assert_kind_of Time, loaded["created"] + assert_equal time.year, loaded["created"].year + assert_equal time.month, loaded["created"].month + assert_equal time.day, loaded["created"].day + end + + def test_roundtrip_empty_collections + pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + + obj = { "arr" => [], "hash" => {} } + yaml = Gem::YAMLSerializer.dump(obj) + loaded = Gem::YAMLSerializer.load(yaml) + + assert_equal [], loaded["arr"] + assert_equal({}, loaded["hash"]) + end end From 4cd372672aa21f65968aa37ce3cc7125edd1c302 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 6 Mar 2026 15:01:27 +0900 Subject: [PATCH 76/87] [ruby/rubygems] Add unit and regression tests for YAML serializer https://github.com/ruby/rubygems/commit/9741fbf151 --- lib/rubygems/safe_yaml.rb | 2 +- test/rubygems/test_gem_safe_yaml.rb | 499 +++++++++++++++++++++++++--- 2 files changed, 463 insertions(+), 38 deletions(-) diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index 1c15f10eb16661..03db77c6bf3a5d 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -59,7 +59,7 @@ def self.load(input) end end - def self.safe_load(input) + def self.unsafe_load(input) if Gem.use_psych? if ::Psych.respond_to?(:unsafe_load) ::Psych.unsafe_load(input) diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb index f9deda04ae01f5..dd1ddf96c2148a 100644 --- a/test/rubygems/test_gem_safe_yaml.rb +++ b/test/rubygems/test_gem_safe_yaml.rb @@ -6,13 +6,11 @@ class TestGemSafeYAML < Gem::TestCase def test_aliases_enabled_by_default - pend "Psych is not loaded" if defined?(Gem::YAMLSerializer) assert_predicate Gem::SafeYAML, :aliases_enabled? assert_equal({ "a" => "a", "b" => "a" }, Gem::SafeYAML.safe_load("a: &a a\nb: *a\n")) end def test_aliases_disabled - pend "Psych is not loaded" if defined?(Gem::YAMLSerializer) aliases_enabled = Gem::SafeYAML.aliases_enabled? Gem::SafeYAML.aliases_enabled = false refute_predicate Gem::SafeYAML, :aliases_enabled? @@ -25,7 +23,7 @@ def test_aliases_disabled end def test_specification_version_is_integer - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:Gem::Specification @@ -41,21 +39,21 @@ def test_specification_version_is_integer end def test_disallowed_class_rejected - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:SomeDisallowedClass foo: bar YAML - exception = assert_raise(ArgumentError) do + exception = assert_raise(Psych::DisallowedClass) do Gem::SafeYAML.safe_load(yaml) end assert_match(/Disallowed class/, exception.message) end def test_disallowed_symbol_rejected - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:Gem::Dependency @@ -74,14 +72,14 @@ def test_disallowed_symbol_rejected version: 0 YAML - exception = assert_raise(ArgumentError) do + exception = assert_raise(Psych::DisallowedClass) do Gem::SafeYAML.safe_load(yaml) end assert_match(/Disallowed symbol/, exception.message) end def test_yaml_serializer_aliases_disabled - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? aliases_enabled = Gem::SafeYAML.aliases_enabled? Gem::SafeYAML.aliases_enabled = false @@ -89,7 +87,7 @@ def test_yaml_serializer_aliases_disabled yaml = "a: &anchor value\nb: *anchor\n" - exception = assert_raise(ArgumentError) do + exception = assert_raise(Psych::AliasesNotEnabled) do Gem::SafeYAML.safe_load(yaml) end assert_match(/YAML aliases are not allowed/, exception.message) @@ -98,7 +96,7 @@ def test_yaml_serializer_aliases_disabled end def test_real_gemspec_fileutils - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:Gem::Specification @@ -160,7 +158,7 @@ def test_real_gemspec_fileutils end def test_yaml_anchor_and_alias_enabled - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? aliases_enabled = Gem::SafeYAML.aliases_enabled? Gem::SafeYAML.aliases_enabled = true @@ -187,7 +185,7 @@ def test_yaml_anchor_and_alias_enabled end def test_real_gemspec_rubygems_bundler - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:Gem::Specification @@ -272,7 +270,7 @@ def test_real_gemspec_rubygems_bundler end def test_empty_requirements_array - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? yaml = <<~YAML --- !ruby/object:Gem::Specification @@ -303,7 +301,7 @@ def test_empty_requirements_array end def test_requirements_hash_converted_to_array - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Malformed YAML where requirements is a Hash instead of Array yaml = <<~YAML @@ -325,7 +323,7 @@ def test_requirements_hash_converted_to_array end def test_rdoc_options_hash_converted_to_array - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Some gemspecs incorrectly have rdoc_options: {} instead of rdoc_options: [] yaml = <<~YAML @@ -346,7 +344,7 @@ def test_rdoc_options_hash_converted_to_array end def test_load_returns_hash_for_comment_only_yaml - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Bundler config files may contain only comments after deleting all keys result = Gem::YAMLSerializer.load("---\n# BUNDLE_FOO: \"bar\"\n") @@ -355,7 +353,7 @@ def test_load_returns_hash_for_comment_only_yaml end def test_load_returns_hash_for_empty_document - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? assert_equal({}, Gem::YAMLSerializer.load("---\n")) assert_equal({}, Gem::YAMLSerializer.load("")) @@ -363,7 +361,7 @@ def test_load_returns_hash_for_empty_document end def test_load_returns_hash_for_flow_empty_hash - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Gem::YAMLSerializer.dump({}) produces "--- {}\n" result = Gem::YAMLSerializer.load("--- {}\n") @@ -372,7 +370,7 @@ def test_load_returns_hash_for_flow_empty_hash end def test_load_parses_flow_empty_hash_as_value - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? result = Gem::YAMLSerializer.load("metadata: {}\n") assert_kind_of Hash, result @@ -381,7 +379,7 @@ def test_load_parses_flow_empty_hash_as_value end def test_yaml_non_specific_tag_stripped - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Legacy RubyGems (1.x) generated YAML with ! non-specific tags like: # - ! '>=' @@ -413,7 +411,7 @@ def test_yaml_non_specific_tag_stripped end def test_legacy_gemspec_with_anchors_and_non_specific_tags - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? aliases_enabled = Gem::SafeYAML.aliases_enabled? Gem::SafeYAML.aliases_enabled = true @@ -470,7 +468,7 @@ def test_legacy_gemspec_with_anchors_and_non_specific_tags end def test_non_specific_tag_on_plain_value - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # ! tag on a bracketed value like rubyforge_project: ! '[none]' result = Gem::YAMLSerializer.load("key: ! '[none]'\n") @@ -478,7 +476,7 @@ def test_non_specific_tag_on_plain_value end def test_dump_quotes_dollar_sign_values - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Values starting with $ should be quoted to preserve them as strings yaml = Gem::YAMLSerializer.dump({ "BUNDLE_FOO" => "$BUILD_DIR", "BUNDLE_BAR" => "baz" }) @@ -492,7 +490,7 @@ def test_dump_quotes_dollar_sign_values end def test_dump_quotes_special_characters - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Various special characters that should trigger quoting special_values = { @@ -518,7 +516,7 @@ def test_dump_quotes_special_characters end def test_load_ambiguous_value_with_colon - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # "invalid: yaml: hah" is ambiguous YAML - our parser treats it as # {"invalid" => "yaml: hah"}, but the value looks like a nested mapping. @@ -529,7 +527,7 @@ def test_load_ambiguous_value_with_colon end def test_nested_anchor_in_array_item - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? # Ensure aliases are enabled for this test aliases_enabled = Gem::SafeYAML.aliases_enabled? @@ -573,7 +571,7 @@ def test_nested_anchor_in_array_item end def test_roundtrip_specification - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? spec = Gem::Specification.new do |s| s.name = "round-trip-test" @@ -611,7 +609,7 @@ def test_roundtrip_specification end def test_roundtrip_version - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? ver = Gem::Version.new("1.2.3") yaml = Gem::YAMLSerializer.dump(ver) @@ -622,7 +620,7 @@ def test_roundtrip_version end def test_roundtrip_platform - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? plat = Gem::Platform.new("x86_64-linux") yaml = Gem::YAMLSerializer.dump(plat) @@ -635,7 +633,7 @@ def test_roundtrip_platform end def test_roundtrip_requirement - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? req = Gem::Requirement.new(">= 1.0", "< 2.0") yaml = Gem::YAMLSerializer.dump(req) @@ -646,7 +644,7 @@ def test_roundtrip_requirement end def test_roundtrip_dependency - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? dep = Gem::Dependency.new("foo", ">= 1.0", :development) yaml = Gem::YAMLSerializer.dump(dep) @@ -659,7 +657,7 @@ def test_roundtrip_dependency end def test_roundtrip_nested_hash - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? obj = { "a" => { "b" => "c", "d" => [1, 2, 3] } } yaml = Gem::YAMLSerializer.dump(obj) @@ -669,7 +667,7 @@ def test_roundtrip_nested_hash end def test_roundtrip_block_scalar - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? obj = { "text" => "line1\nline2\n" } yaml = Gem::YAMLSerializer.dump(obj) @@ -679,7 +677,7 @@ def test_roundtrip_block_scalar end def test_roundtrip_special_characters - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? obj = { "dollar" => "$HOME", @@ -701,7 +699,7 @@ def test_roundtrip_special_characters end def test_roundtrip_boolean_nil_integer - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? obj = { "flag" => true, "count" => 42, "empty" => nil, "off" => false } yaml = Gem::YAMLSerializer.dump(obj) @@ -714,7 +712,7 @@ def test_roundtrip_boolean_nil_integer end def test_roundtrip_time - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? time = Time.utc(2024, 6, 15, 12, 30, 45) obj = { "created" => time } @@ -728,7 +726,7 @@ def test_roundtrip_time end def test_roundtrip_empty_collections - pend "YAMLSerializer is not loaded" unless defined?(Gem::YAMLSerializer) + pend "Psych mode" if Gem.use_psych? obj = { "arr" => [], "hash" => {} } yaml = Gem::YAMLSerializer.dump(obj) @@ -737,4 +735,431 @@ def test_roundtrip_empty_collections assert_equal [], loaded["arr"] assert_equal({}, loaded["hash"]) end + + def test_load_double_quoted_escape_sequences + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("newline: \"hello\\nworld\"") + assert_equal "hello\nworld", result["newline"] + + result = Gem::YAMLSerializer.load("tab: \"col1\\tcol2\"") + assert_equal "col1\tcol2", result["tab"] + + result = Gem::YAMLSerializer.load("cr: \"line\\rend\"") + assert_equal "line\rend", result["cr"] + + result = Gem::YAMLSerializer.load("quote: \"say\\\"hi\\\"\"") + assert_equal "say\"hi\"", result["quote"] + end + + def test_load_single_quoted_escape + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: 'it''s'") + assert_equal "it's", result["key"] + + result = Gem::YAMLSerializer.load("key: 'no escape \\n here'") + assert_equal "no escape \\n here", result["key"] + end + + def test_load_quoted_numeric_stays_string + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: \"42\"") + assert_equal "42", result["key"] + assert_kind_of String, result["key"] + + result = Gem::YAMLSerializer.load("key: '99'") + assert_equal "99", result["key"] + assert_kind_of String, result["key"] + end + + def test_load_empty_string_value + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: \"\"") + assert_equal "", result["key"] + end + + def test_load_unquoted_integer + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: 42") + assert_equal 42, result["key"] + assert_kind_of Integer, result["key"] + + result = Gem::YAMLSerializer.load("key: -7") + assert_equal(-7, result["key"]) + end + + def test_load_boolean_values + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("a: true\nb: false") + assert_equal true, result["a"] + assert_equal false, result["b"] + end + + def test_load_nil_value + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: nil") + assert_nil result["key"] + end + + def test_load_time_value + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("date: 2024-06-15 12:30:45.000000000 Z") + assert_kind_of Time, result["date"] + assert_equal 2024, result["date"].year + assert_equal 6, result["date"].month + assert_equal 15, result["date"].day + end + + def test_load_block_scalar_keep_trailing_newline + pend "Psych mode" if Gem.use_psych? + + yaml = "text: |\n line1\n line2\n" + result = Gem::YAMLSerializer.load(yaml) + assert_equal "line1\nline2\n", result["text"] + end + + def test_load_block_scalar_strip_trailing_newline + pend "Psych mode" if Gem.use_psych? + + yaml = "text: |-\n no trailing newline\n" + result = Gem::YAMLSerializer.load(yaml) + assert_equal "no trailing newline", result["text"] + refute result["text"].end_with?("\n") + end + + def test_load_flow_array + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("items: [a, b, c]") + assert_equal ["a", "b", "c"], result["items"] + end + + def test_load_flow_empty_array + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("items: []") + assert_equal [], result["items"] + end + + def test_load_mapping_key_with_no_value + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key:") + assert_kind_of Hash, result + assert_nil result["key"] + end + + def test_load_sequence_item_as_mapping + pend "Psych mode" if Gem.use_psych? + + yaml = "items:\n- name: foo\n ver: 1\n- name: bar\n ver: 2" + result = Gem::YAMLSerializer.load(yaml) + assert_equal [{ "name" => "foo", "ver" => 1 }, { "name" => "bar", "ver" => 2 }], result["items"] + end + + def test_load_nested_sequence + pend "Psych mode" if Gem.use_psych? + + yaml = "matrix:\n- - a\n - b\n- - c\n - d" + result = Gem::YAMLSerializer.load(yaml) + assert_equal [["a", "b"], ["c", "d"]], result["matrix"] + end + + def test_load_comment_stripped_from_value + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: value # this is a comment") + assert_equal "value", result["key"] + end + + def test_load_comment_in_quoted_string_preserved + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: \"value # not a comment\"") + assert_equal "value # not a comment", result["key"] + + result = Gem::YAMLSerializer.load("key: 'value # not a comment'") + assert_equal "value # not a comment", result["key"] + end + + def test_load_crlf_line_endings + pend "Psych mode" if Gem.use_psych? + + result = Gem::YAMLSerializer.load("key: value\r\nother: data\r\n") + assert_equal "value", result["key"] + assert_equal "data", result["other"] + end + + def test_load_version_requirement_old_tag + pend "Psych mode" if Gem.use_psych? + + yaml = <<~YAML + !ruby/object:Gem::Version::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: "1.0" + YAML + + req = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + assert_kind_of Gem::Requirement, req + assert_equal [[">=", Gem::Version.new("1.0")]], req.requirements + end + + def test_load_platform_from_value_field + pend "Psych mode" if Gem.use_psych? + + yaml = "!ruby/object:Gem::Platform\nvalue: x86-linux\n" + plat = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + assert_kind_of Gem::Platform, plat + assert_equal "x86", plat.cpu + assert_equal "linux", plat.os + end + + def test_load_platform_from_cpu_os_version_fields + pend "Psych mode" if Gem.use_psych? + + yaml = "!ruby/object:Gem::Platform\ncpu: x86_64\nos: darwin\nversion: nil\n" + plat = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + assert_kind_of Gem::Platform, plat + assert_equal "x86_64", plat.cpu + assert_equal "darwin", plat.os + end + + def test_load_dependency_missing_requirement_uses_default + pend "Psych mode" if Gem.use_psych? + + yaml = <<~YAML + !ruby/object:Gem::Dependency + name: foo + type: :runtime + YAML + + dep = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + assert_kind_of Gem::Dependency, dep + assert_equal "foo", dep.name + assert_equal :runtime, dep.type + assert_kind_of Gem::Requirement, dep.requirement + end + + def test_load_dependency_missing_type_defaults_to_runtime + pend "Psych mode" if Gem.use_psych? + + yaml = <<~YAML + !ruby/object:Gem::Dependency + name: bar + requirement: !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + YAML + + dep = Gem::YAMLSerializer.load(yaml, permitted_classes: Gem::SafeYAML::PERMITTED_CLASSES) + assert_equal :runtime, dep.type + end + + def test_specification_version_non_numeric_string_not_converted + pend "Psych mode" if Gem.use_psych? + + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test + version: !ruby/object:Gem::Version + version: 1.0.0 + specification_version: abc + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Specification, spec + # Non-numeric string should not be converted to Integer + assert_equal "abc", spec.specification_version + end + + def test_unknown_permitted_tag_returns_hash_with_tag + pend "Psych mode" if Gem.use_psych? + + yaml = "!ruby/object:MyCustomClass\nfoo: bar\n" + result = Gem::YAMLSerializer.load(yaml, permitted_classes: ["MyCustomClass"]) + assert_kind_of Hash, result + assert_equal "bar", result["foo"] + assert_equal "!ruby/object:MyCustomClass", result[:tag] + end + + def test_dump_block_scalar_with_trailing_newline + pend "Psych mode" if Gem.use_psych? + + yaml = Gem::YAMLSerializer.dump({ "text" => "line1\nline2\n" }) + assert_include yaml, " |\n" + refute_includes yaml, " |-\n" + end + + def test_dump_block_scalar_without_trailing_newline + pend "Psych mode" if Gem.use_psych? + + yaml = Gem::YAMLSerializer.dump({ "text" => "line1\nline2" }) + assert_include yaml, " |-\n" + end + + def test_dump_nil_value + pend "Psych mode" if Gem.use_psych? + + yaml = Gem::YAMLSerializer.dump({ "key" => nil }) + assert_include yaml, "key: nil\n" + + loaded = Gem::YAMLSerializer.load(yaml) + assert_nil loaded["key"] + end + + def test_dump_symbol_keys_quoted + pend "Psych mode" if Gem.use_psych? + + yaml = Gem::YAMLSerializer.dump({ foo: "bar" }) + # Symbol keys should use inspect format + assert_include yaml, ":foo:" + + # Symbol values in hash with symbol keys should be quoted + yaml = Gem::YAMLSerializer.dump({ type: ":runtime" }) + assert_include yaml, "\":runtime\"" + end + + def test_regression_flow_empty_hash_as_root + pend "Psych mode" if Gem.use_psych? + + # Previously returned Mapping struct instead of Hash + result = Gem::YAMLSerializer.load("--- {}") + assert_kind_of Hash, result + assert_empty result + end + + def test_regression_alias_check_in_builder_not_parser + pend "Psych mode" if Gem.use_psych? + + # Previously aliases were resolved in Parser, bypassing Builder's policy check. + # The Builder must enforce aliases: false. + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = false + + # Alias in mapping value + exception = assert_raise(Psych::AliasesNotEnabled) do + Gem::YAMLSerializer.load("a: &x val\nb: *x", aliases: false) + end + assert_match(/YAML aliases are not allowed/, exception.message) + + # Alias in sequence item + exception = assert_raise(Psych::AliasesNotEnabled) do + Gem::YAMLSerializer.load("items:\n- &x val\n- *x", aliases: false) + end + assert_match(/YAML aliases are not allowed/, exception.message) + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_regression_anchored_mapping_stored_for_alias_resolution + pend "Psych mode" if Gem.use_psych? + + # Previously build_mapping didn't call store_anchor, so anchored + # Gem types (Requirement, etc.) couldn't be resolved via aliases. + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = true + + yaml = <<~YAML + a: &req !ruby/object:Gem::Requirement + requirements: + - - ">=" + - !ruby/object:Gem::Version + version: '0' + b: *req + YAML + + result = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Gem::Requirement, result["a"] + assert_kind_of Gem::Requirement, result["b"] + assert_equal result["a"].requirements, result["b"].requirements + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_regression_register_anchor_sets_node_anchor + pend "Psych mode" if Gem.use_psych? + + # Previously register_anchor only stored node in @anchors hash but + # didn't set node.anchor, so Builder couldn't track anchored values. + aliases_enabled = Gem::SafeYAML.aliases_enabled? + Gem::SafeYAML.aliases_enabled = true + + yaml = <<~YAML + items: + - &item !ruby/object:Gem::Version + version: '1.0' + - *item + YAML + + result = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Array, result["items"] + assert_equal 2, result["items"].size + assert_kind_of Gem::Version, result["items"][0] + assert_kind_of Gem::Version, result["items"][1] + assert_equal result["items"][0], result["items"][1] + ensure + Gem::SafeYAML.aliases_enabled = aliases_enabled + end + + def test_regression_coerce_empty_hash_not_wrapped_in_scalar + pend "Psych mode" if Gem.use_psych? + + # Previously coerce("{}") returned Mapping but parse_plain_scalar + # wrapped it in Scalar.new(value: Mapping), causing type mismatch. + result = Gem::YAMLSerializer.load("--- {}") + assert_kind_of Hash, result + + result = Gem::YAMLSerializer.load("key: {}") + assert_kind_of Hash, result["key"] + end + + def test_regression_rdoc_options_normalized_to_array + pend "Psych mode" if Gem.use_psych? + + # rdoc_options as Hash (malformed gemspec) + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test + version: !ruby/object:Gem::Version + version: 1.0.0 + rdoc_options: + --title: MyGem + --main: README + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Array, spec.rdoc_options + # Hash rdoc_options: normalize_rdoc_options! extracts values + assert_include spec.rdoc_options, "MyGem" + assert_include spec.rdoc_options, "README" + end + + def test_regression_requirements_field_normalized_to_array + pend "Psych mode" if Gem.use_psych? + + # The "requirements" field in a Specification (not Requirement) + # should be normalized from Hash to Array if malformed + yaml = <<~YAML + --- !ruby/object:Gem::Specification + name: test + version: !ruby/object:Gem::Version + version: 1.0.0 + requirements: + foo: bar + YAML + + spec = Gem::SafeYAML.safe_load(yaml) + assert_kind_of Array, spec.requirements + end end From 6a92781ff6b4a4338aaf26cf79fbf9f0f20dc382 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 12:35:06 +0900 Subject: [PATCH 77/87] [ruby/rubygems] Add Psych stub classes to yaml serializer https://github.com/ruby/rubygems/commit/f3a1b17fce --- lib/rubygems.rb | 7 +----- lib/rubygems/safe_yaml.rb | 2 +- lib/rubygems/yaml_serializer.rb | 36 +++++++++++++++++++++-------- test/rubygems/test_gem_safe_yaml.rb | 13 ++++------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index baf0599ee6bbd7..55e214e8631f58 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -656,14 +656,9 @@ def self.load_yaml return if @yaml_loaded @use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" || - (defined?(@configuration) && @configuration && !!@configuration[:use_psych]) + (defined?(@configuration) && @configuration && !@configuration[:use_psych].nil?) if @use_psych - # Remove Psych stubs (defined by yaml_serializer.rb) before loading - # real Psych to avoid superclass mismatch errors - if defined?(Psych) && !defined?(Psych::VERSION) - Object.send(:remove_const, :Psych) - end require "psych" require_relative "rubygems/psych_tree" end diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index 03db77c6bf3a5d..223c50ce335012 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -37,7 +37,7 @@ def self.aliases_enabled? # :nodoc: def self.safe_load(input) if Gem.use_psych? ::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, - permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) + permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled) else Gem::YAMLSerializer.load( input, diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 88b0100f2183ad..72207c7c5339d6 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -1,11 +1,27 @@ # frozen_string_literal: true -unless defined?(Psych) - module Psych - class SyntaxError < ::StandardError; end - class DisallowedClass < ::ArgumentError; end - class BadAlias < ::ArgumentError; end - class AliasesNotEnabled < BadAlias; end +unless defined?(Psych::Exception) + begin + require "psych/exception" + rescue LoadError + module Psych + class Exception < ::RuntimeError; end + class SyntaxError < Exception; end + + class DisallowedClass < Exception + def initialize(action, klass_name) + super("Tried to #{action} unspecified class: #{klass_name}") + end + end + + class BadAlias < Exception; end + + class AliasesNotEnabled < BadAlias + def initialize + super "Alias parsing was not enabled. To enable it, pass `aliases: true` to `Psych::load` or `Psych::safe_load`." + end + end + end end end @@ -414,7 +430,7 @@ def build_node(node) end def resolve_alias(node) - raise Psych::AliasesNotEnabled, "YAML aliases are not allowed" unless @aliases + raise Psych::AliasesNotEnabled unless @aliases @anchor_values.fetch(node.name, nil) end @@ -566,19 +582,19 @@ def build_safe_requirement(req_value) def validate_tag!(tag) unless @permitted_tags.include?(tag) - raise Psych::DisallowedClass, "Disallowed class: #{tag}" + raise Psych::DisallowedClass.new("load", tag) end end def validate_symbol!(sym) if @permitted_symbols.any? && !@permitted_symbols.include?(sym.to_s) - raise Psych::DisallowedClass, "Disallowed symbol: #{sym.inspect}" + raise Psych::DisallowedClass.new("load", sym.inspect) end end def check_anchor!(node) if node.anchor - raise Psych::AliasesNotEnabled, "YAML aliases are not allowed" unless @aliases + raise Psych::AliasesNotEnabled unless @aliases end end diff --git a/test/rubygems/test_gem_safe_yaml.rb b/test/rubygems/test_gem_safe_yaml.rb index dd1ddf96c2148a..72b913e0c1ed33 100644 --- a/test/rubygems/test_gem_safe_yaml.rb +++ b/test/rubygems/test_gem_safe_yaml.rb @@ -49,7 +49,7 @@ def test_disallowed_class_rejected exception = assert_raise(Psych::DisallowedClass) do Gem::SafeYAML.safe_load(yaml) end - assert_match(/Disallowed class/, exception.message) + assert_match(/unspecified class/, exception.message) end def test_disallowed_symbol_rejected @@ -75,7 +75,7 @@ def test_disallowed_symbol_rejected exception = assert_raise(Psych::DisallowedClass) do Gem::SafeYAML.safe_load(yaml) end - assert_match(/Disallowed symbol/, exception.message) + assert_match(/unspecified class/, exception.message) end def test_yaml_serializer_aliases_disabled @@ -87,10 +87,9 @@ def test_yaml_serializer_aliases_disabled yaml = "a: &anchor value\nb: *anchor\n" - exception = assert_raise(Psych::AliasesNotEnabled) do + assert_raise(Psych::AliasesNotEnabled) do Gem::SafeYAML.safe_load(yaml) end - assert_match(/YAML aliases are not allowed/, exception.message) ensure Gem::SafeYAML.aliases_enabled = aliases_enabled end @@ -1048,16 +1047,14 @@ def test_regression_alias_check_in_builder_not_parser Gem::SafeYAML.aliases_enabled = false # Alias in mapping value - exception = assert_raise(Psych::AliasesNotEnabled) do + assert_raise(Psych::AliasesNotEnabled) do Gem::YAMLSerializer.load("a: &x val\nb: *x", aliases: false) end - assert_match(/YAML aliases are not allowed/, exception.message) # Alias in sequence item - exception = assert_raise(Psych::AliasesNotEnabled) do + assert_raise(Psych::AliasesNotEnabled) do Gem::YAMLSerializer.load("items:\n- &x val\n- *x", aliases: false) end - assert_match(/YAML aliases are not allowed/, exception.message) ensure Gem::SafeYAML.aliases_enabled = aliases_enabled end From 6425157eae086f3c50c7055a936daab4647d6090 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 15:31:07 +0900 Subject: [PATCH 78/87] [ruby/rubygems] Simplify Psych exception stubs and fallback raises https://github.com/ruby/rubygems/commit/61bfb3fff8 --- lib/rubygems/yaml_serializer.rb | 41 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 72207c7c5339d6..28616cdfdea819 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -1,27 +1,12 @@ # frozen_string_literal: true -unless defined?(Psych::Exception) - begin - require "psych/exception" - rescue LoadError - module Psych - class Exception < ::RuntimeError; end - class SyntaxError < Exception; end - - class DisallowedClass < Exception - def initialize(action, klass_name) - super("Tried to #{action} unspecified class: #{klass_name}") - end - end - - class BadAlias < Exception; end - - class AliasesNotEnabled < BadAlias - def initialize - super "Alias parsing was not enabled. To enable it, pass `aliases: true` to `Psych::load` or `Psych::safe_load`." - end - end - end +unless defined?(Psych::VERSION) + module Psych + class Exception < ::RuntimeError; end + class SyntaxError < Exception; end + class DisallowedClass < Exception; end + class BadAlias < Exception; end + class AliasesNotEnabled < BadAlias; end end end @@ -582,13 +567,21 @@ def build_safe_requirement(req_value) def validate_tag!(tag) unless @permitted_tags.include?(tag) - raise Psych::DisallowedClass.new("load", tag) + if defined?(Psych::VERSION) + raise Psych::DisallowedClass.new("load", tag) + else + raise Psych::DisallowedClass, "Tried to load unspecified class: #{tag}" + end end end def validate_symbol!(sym) if @permitted_symbols.any? && !@permitted_symbols.include?(sym.to_s) - raise Psych::DisallowedClass.new("load", sym.inspect) + if defined?(Psych::VERSION) + raise Psych::DisallowedClass.new("load", sym.inspect) + else + raise Psych::DisallowedClass, "Tried to load unspecified class: #{sym.inspect}" + end end end From 4da2b2d912b750d681b5a78ee8d4bba7a9015040 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 16:05:05 +0900 Subject: [PATCH 79/87] [ruby/rubygems] Remove redundant SafeYAML.load and update tests https://github.com/ruby/rubygems/commit/fa4771bcf5 --- lib/rubygems/safe_yaml.rb | 11 ----------- test/rubygems/helper.rb | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/lib/rubygems/safe_yaml.rb b/lib/rubygems/safe_yaml.rb index 223c50ce335012..c59b4653586282 100644 --- a/lib/rubygems/safe_yaml.rb +++ b/lib/rubygems/safe_yaml.rb @@ -49,17 +49,6 @@ def self.safe_load(input) end def self.load(input) - if Gem.use_psych? - ::Psych.safe_load(input, permitted_classes: [::Symbol]) - else - Gem::YAMLSerializer.load( - input, - permitted_classes: [::Symbol] - ) - end - end - - def self.unsafe_load(input) if Gem.use_psych? if ::Psych.respond_to?(:unsafe_load) ::Psych.unsafe_load(input) diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index ec373d41e0202a..783818b6eb6f52 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -738,7 +738,7 @@ def write_dummy_extconf(gem_name) # Load a YAML string, the psych 3 way def load_yaml(yaml) - Gem::SafeYAML.safe_load(yaml) + Gem::SafeYAML.load(yaml) end ## From 1425c52227045fac17764b426a90d1b4ac369ba0 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 9 Mar 2026 17:26:02 +0900 Subject: [PATCH 80/87] Parse ISO8601 datetimes without Time.parse --- lib/rubygems/yaml_serializer.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/yaml_serializer.rb b/lib/rubygems/yaml_serializer.rb index 28616cdfdea819..edc0133ce272cc 100644 --- a/lib/rubygems/yaml_serializer.rb +++ b/lib/rubygems/yaml_serializer.rb @@ -271,12 +271,16 @@ def coerce(val) return Sequence.new if inner.empty? items = inner.split(/\s*,\s*/).reject(&:empty?).map {|e| Scalar.new(value: coerce(e)) } Sequence.new(items: items) - elsif /^\d{4}-\d{2}-\d{2}/.match?(val) - require "time" + elsif /\A\d{4}-\d{2}-\d{2}([ T]\d{2}:\d{2}:\d{2})?/.match?(val) begin - Time.parse(val) + Time.new(val) rescue ArgumentError - val + # date-only format like "2024-06-15" is not supported by Time.new + if /\A(\d{4})-(\d{2})-(\d{2})\z/.match(val) + Time.utc($1.to_i, $2.to_i, $3.to_i) + else + val + end end elsif /^-?\d+$/.match?(val) val.to_i From 364f2fc1471620371439c6add5f00ac6d18b0c4c Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 5 Dec 2025 20:26:04 +0100 Subject: [PATCH 81/87] Propose myself as maintainer of benchmark * I always had an interest about the benchmark stdlib and did significant contributions to it, notably 979ec8df5daf6db314b2f17e53b53d269881d6ca. * Ref: https://bugs.ruby-lang.org/issues/21948 --- doc/maintainers.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/maintainers.md b/doc/maintainers.md index 6b820a516a32d4..04f4d21683d9d6 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -553,6 +553,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes #### benchmark +* Benoit Daloze ([eregon]) * https://github.com/ruby/benchmark * https://rubygems.org/gems/benchmark @@ -672,6 +673,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes [earlopain]: https://github.com/earlopain [eban]: https://github.com/eban [eileencodes]: https://github.com/eileencodes +[eregon]: https://github.com/eregon [hasumikin]: https://github.com/hasumikin [hsbt]: https://github.com/hsbt [ima1zumi]: https://github.com/ima1zumi From b5ffaa3a01e53b838bc531b5ca45b967e211a8f6 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 9 Mar 2026 13:04:34 +0100 Subject: [PATCH 82/87] [ruby/timeout] Fix timing-dependent test * The timeout could trigger before the `raise`. https://github.com/ruby/timeout/commit/e4aa36096f --- test/test_timeout.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index 5db355a7da162d..be752d9b4ac773 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -128,8 +128,8 @@ def test_nested_timeout_error_identity def test_nested_timeout_which_error_bubbles_up raised_exception = nil begin - Timeout.timeout(0.1) { - Timeout.timeout(1) { + Timeout.timeout(1) { + Timeout.timeout(10) { raise Timeout::ExitException.new("inner message") } } From 4ce8515c699951e24d812511dee3a06818c35f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Mon, 9 Mar 2026 11:50:03 +0100 Subject: [PATCH 83/87] [ruby/timeout] Remove warnings https://github.com/ruby/timeout/commit/9b935535ff --- test/test_timeout.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_timeout.rb b/test/test_timeout.rb index be752d9b4ac773..2703a0314df99e 100644 --- a/test/test_timeout.rb +++ b/test/test_timeout.rb @@ -464,10 +464,12 @@ def test_timeout_in_trap_handler # Stubs Fiber.current_scheduler for the duration of the block, then restores it. def with_mock_scheduler(mock) original = Fiber.method(:current_scheduler) + Fiber.singleton_class.remove_method(:current_scheduler) Fiber.define_singleton_method(:current_scheduler) { mock } begin yield ensure + Fiber.singleton_class.remove_method(:current_scheduler) Fiber.define_singleton_method(:current_scheduler, original) end end From 208b173c258508057a3914b816bb69679da33fbf Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Tue, 3 Mar 2026 10:57:41 +0000 Subject: [PATCH 84/87] Look up slot sizes for allocations in a table Also remove BASE_SLOT_SIZE. --- benchmark/string_concat.yml | 4 +- gc/default/default.c | 77 +++++++++++++++-------------- gc/mmtk/mmtk.c | 19 +++++-- test/-ext-/string/test_capacity.rb | 11 +++-- test/-ext-/string/test_set_len.rb | 2 +- test/.excludes-mmtk/TestObjSpace.rb | 2 +- test/objspace/test_objspace.rb | 6 +-- test/ruby/test_file_exhaustive.rb | 6 ++- test/ruby/test_gc.rb | 4 +- test/ruby/test_gc_compact.rb | 4 +- test/ruby/test_string.rb | 2 +- test/ruby/test_time.rb | 2 +- 12 files changed, 81 insertions(+), 58 deletions(-) diff --git a/benchmark/string_concat.yml b/benchmark/string_concat.yml index f11f95ee9a7891..c07fd21013f4d0 100644 --- a/benchmark/string_concat.yml +++ b/benchmark/string_concat.yml @@ -1,8 +1,8 @@ prelude: | CHUNK = "a" * 64 UCHUNK = "é" * 32 - SHORT = "a" * (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] / 2) - LONG = "a" * (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 2) + SHORT = "a" * (GC.stat_heap(0, :slot_size) / 2) + LONG = "a" * (GC.stat_heap(0, :slot_size) * 2) GC.disable # GC causes a lot of variance benchmark: binary_concat_7bit: | diff --git a/gc/default/default.c b/gc/default/default.c index 1c91a5e0d5b0fc..2d743475344c1c 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -687,7 +687,17 @@ size_t rb_gc_impl_obj_slot_size(VALUE obj); # endif #endif -#define BASE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD) +#define RVALUE_SLOT_SIZE (sizeof(struct RBasic) + sizeof(VALUE[RBIMPL_RVALUE_EMBED_LEN_MAX]) + RVALUE_OVERHEAD) + +static const size_t pool_slot_sizes[HEAP_COUNT] = { + RVALUE_SLOT_SIZE, + RVALUE_SLOT_SIZE * 2, + RVALUE_SLOT_SIZE * 4, + RVALUE_SLOT_SIZE * 8, + RVALUE_SLOT_SIZE * 16, +}; + +static uint8_t size_to_heap_idx[RVALUE_SLOT_SIZE * (1 << (HEAP_COUNT - 1)) + 1]; #ifndef MAX # define MAX(a, b) (((a) > (b)) ? (a) : (b)) @@ -701,7 +711,7 @@ enum { HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG), HEAP_PAGE_ALIGN_MASK = (~(~0UL << HEAP_PAGE_ALIGN_LOG)), HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN, - HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, BASE_SLOT_SIZE), BITS_BITLENGTH), + HEAP_PAGE_BITMAP_LIMIT = CEILDIV(CEILDIV(HEAP_PAGE_SIZE, RVALUE_SLOT_SIZE), BITS_BITLENGTH), HEAP_PAGE_BITMAP_SIZE = (BITS_SIZE * HEAP_PAGE_BITMAP_LIMIT), }; #define HEAP_PAGE_ALIGN (1 << HEAP_PAGE_ALIGN_LOG) @@ -1636,7 +1646,7 @@ heap_page_add_freeobj(rb_objspace_t *objspace, struct heap_page *page, VALUE obj /* obj should belong to page */ !(page->start <= (uintptr_t)obj && (uintptr_t)obj < ((uintptr_t)page->start + (page->total_slots * page->slot_size)) && - obj % BASE_SLOT_SIZE == 0)) { + obj % pool_slot_sizes[0] == 0)) { rb_bug("heap_page_add_freeobj: %p is not rvalue.", (void *)obj); } @@ -2237,22 +2247,13 @@ heap_slot_size(unsigned char pool_id) { GC_ASSERT(pool_id < HEAP_COUNT); - size_t slot_size = (1 << pool_id) * BASE_SLOT_SIZE; - -#if RGENGC_CHECK_MODE - rb_objspace_t *objspace = rb_gc_get_objspace(); - GC_ASSERT(heaps[pool_id].slot_size == (short)slot_size); -#endif - - slot_size -= RVALUE_OVERHEAD; - - return slot_size; + return pool_slot_sizes[pool_id] - RVALUE_OVERHEAD; } bool rb_gc_impl_size_allocatable_p(size_t size) { - return size <= heap_slot_size(HEAP_COUNT - 1); + return size + RVALUE_OVERHEAD <= pool_slot_sizes[HEAP_COUNT - 1]; } static const size_t ALLOCATED_COUNT_STEP = 1024; @@ -2351,28 +2352,31 @@ ractor_cache_set_page(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, rb_asan_poison_object((VALUE)heap_cache->freelist); } -static inline size_t -heap_idx_for_size(size_t size) +static void +init_size_to_heap_idx(void) { - size += RVALUE_OVERHEAD; - - size_t slot_count = CEILDIV(size, BASE_SLOT_SIZE); + GC_ASSERT(pool_slot_sizes[HEAP_COUNT - 1] - RVALUE_OVERHEAD < sizeof(size_to_heap_idx)); - /* heap_idx is ceil(log2(slot_count)) */ - size_t heap_idx = 64 - nlz_int64(slot_count - 1); - - if (heap_idx >= HEAP_COUNT) { - rb_bug("heap_idx_for_size: allocation size too large " - "(size=%"PRIuSIZE"u, heap_idx=%"PRIuSIZE"u)", size, heap_idx); + for (size_t size = 0; size < sizeof(size_to_heap_idx); size++) { + size_t effective = size + RVALUE_OVERHEAD; + uint8_t idx; + for (idx = 0; idx < HEAP_COUNT; idx++) { + if (effective <= pool_slot_sizes[idx]) break; + } + size_to_heap_idx[size] = idx; } +} -#if RGENGC_CHECK_MODE - rb_objspace_t *objspace = rb_gc_get_objspace(); - GC_ASSERT(size <= (size_t)heaps[heap_idx].slot_size); - if (heap_idx > 0) GC_ASSERT(size > (size_t)heaps[heap_idx - 1].slot_size); -#endif +static inline size_t +heap_idx_for_size(size_t size) +{ + if (size < sizeof(size_to_heap_idx)) { + size_t heap_idx = size_to_heap_idx[size]; + if (RB_LIKELY(heap_idx < HEAP_COUNT)) return heap_idx; + } - return heap_idx; + rb_bug("heap_idx_for_size: allocation size too large " + "(size=%"PRIuSIZE")", size); } size_t @@ -2589,7 +2593,7 @@ is_pointer_to_heap(rb_objspace_t *objspace, const void *ptr) if (p < heap_pages_lomem || p > heap_pages_himem) return FALSE; RB_DEBUG_COUNTER_INC(gc_isptr_range); - if (p % BASE_SLOT_SIZE != 0) return FALSE; + if (p % pool_slot_sizes[0] != 0) return FALSE; RB_DEBUG_COUNTER_INC(gc_isptr_align); page = heap_page_for_ptr(objspace, (uintptr_t)ptr); @@ -3495,7 +3499,7 @@ gc_sweep_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t bit do { VALUE vp = (VALUE)p; - GC_ASSERT(vp % BASE_SLOT_SIZE == 0); + GC_ASSERT(vp % pool_slot_sizes[0] == 0); rb_asan_unpoison_object(vp, false); if (bitset & 1) { @@ -5594,7 +5598,7 @@ gc_compact_plane(rb_objspace_t *objspace, rb_heap_t *heap, uintptr_t p, bits_t b do { VALUE vp = (VALUE)p; - GC_ASSERT(vp % BASE_SLOT_SIZE == 0); + GC_ASSERT(vp % pool_slot_sizes[0] == 0); if (bitset & 1) { objspace->rcompactor.considered_count_table[BUILTIN_TYPE(vp)]++; @@ -9518,12 +9522,14 @@ rb_gc_impl_objspace_init(void *objspace_ptr) for (int i = 0; i < HEAP_COUNT; i++) { rb_heap_t *heap = &heaps[i]; - heap->slot_size = (1 << i) * BASE_SLOT_SIZE; + heap->slot_size = pool_slot_sizes[i]; slot_div_magics[i] = (uint32_t)((uint64_t)UINT32_MAX / heap->slot_size + 1); ccan_list_head_init(&heap->pages); } + init_size_to_heap_idx(); + rb_darray_make_without_gc(&objspace->heap_pages.sorted, 0); rb_darray_make_without_gc(&objspace->weak_references, 0); @@ -9556,7 +9562,6 @@ rb_gc_impl_init(void) { VALUE gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), GC_DEBUG ? Qtrue : Qfalse); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE - RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE)); diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 21d97b81efa611..9b09116cd8d0fe 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -644,7 +644,6 @@ void rb_gc_impl_init(void) { VALUE gc_constants = rb_hash_new(); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(sizeof(VALUE) * 5)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RBASIC_SIZE")), SIZET2NUM(sizeof(struct RBasic))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), INT2NUM(0)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVARGC_MAX_ALLOCATE_SIZE")), LONG2FIX(MMTK_MAX_OBJ_SIZE)); @@ -1536,12 +1535,24 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) VALUE rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym) { + if (FIXNUM_P(heap_name) && SYMBOL_P(hash_or_sym)) { + int heap_idx = FIX2INT(heap_name); + if (heap_idx < 0 || heap_idx >= MMTK_HEAP_COUNT) { + rb_raise(rb_eArgError, "size pool index out of range"); + } + + if (hash_or_sym == ID2SYM(rb_intern("slot_size"))) { + return SIZET2NUM(heap_sizes[heap_idx]); + } + + return Qundef; + } + if (RB_TYPE_P(hash_or_sym, T_HASH)) { return hash_or_sym; } - else { - return Qundef; - } + + return Qundef; } // Miscellaneous diff --git a/test/-ext-/string/test_capacity.rb b/test/-ext-/string/test_capacity.rb index df000f7cdb8103..a23892142afa94 100644 --- a/test/-ext-/string/test_capacity.rb +++ b/test/-ext-/string/test_capacity.rb @@ -2,16 +2,17 @@ require 'test/unit' require '-test-/string' require 'rbconfig/sizeof' +require 'objspace' class Test_StringCapacity < Test::Unit::TestCase def test_capacity_embedded - assert_equal GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] - embed_header_size - 1, capa('foo') + assert_equal pool_slot_size(0) - embed_header_size - 1, capa('foo') assert_equal max_embed_len, capa('1' * max_embed_len) assert_equal max_embed_len, capa('1' * (max_embed_len - 1)) end def test_capacity_shared - sym = ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).to_sym + sym = ("a" * pool_slot_size(0)).to_sym assert_equal 0, capa(sym.to_s) end @@ -47,7 +48,7 @@ def test_literal_capacity def test_capacity_frozen s = String.new("I am testing", capacity: 1000) - s << "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + s << "a" * pool_slot_size(0) s.freeze assert_equal(s.length, capa(s)) end @@ -69,6 +70,10 @@ def embed_header_size GC::INTERNAL_CONSTANTS[:RBASIC_SIZE] + RbConfig::SIZEOF['void*'] end + def pool_slot_size(_idx = 0) + Integer(ObjectSpace.dump("")[/"slot_size":(\d+)/, 1]) + end + def max_embed_len GC::INTERNAL_CONSTANTS[:RVARGC_MAX_ALLOCATE_SIZE] - embed_header_size - 1 end diff --git a/test/-ext-/string/test_set_len.rb b/test/-ext-/string/test_set_len.rb index 1531d76167c35c..41e14a293ac3c5 100644 --- a/test/-ext-/string/test_set_len.rb +++ b/test/-ext-/string/test_set_len.rb @@ -5,7 +5,7 @@ class Test_StrSetLen < Test::Unit::TestCase def setup # Make string long enough so that it is not embedded - @range_end = ("0".ord + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).chr + @range_end = ("0".ord + GC.stat_heap(0, :slot_size)).chr @s0 = [*"0"..@range_end].join("").freeze @s1 = Bug::String.new(@s0) end diff --git a/test/.excludes-mmtk/TestObjSpace.rb b/test/.excludes-mmtk/TestObjSpace.rb index 703efd79bb8c69..82858b256fdcc9 100644 --- a/test/.excludes-mmtk/TestObjSpace.rb +++ b/test/.excludes-mmtk/TestObjSpace.rb @@ -1,5 +1,5 @@ exclude(:test_dump_all_full, "testing behaviour specific to default GC") exclude(:test_dump_flag_age, "testing behaviour specific to default GC") exclude(:test_dump_flags, "testing behaviour specific to default GC") -exclude(:test_dump_includes_slot_size, "can be removed when BASE_SLOT_SIZE is 32 bytes") +exclude(:test_dump_includes_slot_size, "can be removed when pool 0 slot size is 32 bytes") exclude(:test_dump_objects_dumps_page_slot_sizes, "testing behaviour specific to default GC") diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index d631f97d1bcad8..b1cd894a7fb4b0 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -33,7 +33,7 @@ def test_memsize_of_root_shared_string b = a.dup c = nil ObjectSpace.each_object(String) {|x| break c = x if a == x and x.frozen?} - rv_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + rv_size = Integer(ObjectSpace.dump(a)[/"slot_size":(\d+)/, 1]) assert_equal([rv_size, rv_size, a.length + 1 + rv_size], [a, b, c].map {|x| ObjectSpace.memsize_of(x)}) end @@ -648,7 +648,7 @@ def dump_my_heap_please next if obj["type"] == "SHAPE" assert_not_nil obj["slot_size"] - assert_equal 0, obj["slot_size"] % (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) + assert_equal 0, obj["slot_size"] % GC.stat_heap(0, :slot_size) } end end @@ -707,7 +707,7 @@ def test_dump_includes_slot_size obj = klass.new dump = ObjectSpace.dump(obj) - assert_includes dump, "\"slot_size\":#{GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]}" + assert_includes dump, "\"slot_size\":#{GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]}" end def test_dump_reference_addresses_match_dump_all_addresses diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index be9e6bd44e702d..88469b32e18fa9 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -897,10 +897,12 @@ def test_expand_path_memsize bug9934 = '[ruby-core:63114] [Bug #9934]' require "objspace" path = File.expand_path("/foo") - assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934) + slot_size = Integer(ObjectSpace.dump(path)[/"slot_size":(\d+)/, 1]) + assert_operator(ObjectSpace.memsize_of(path), :<=, path.bytesize + slot_size, bug9934) path = File.expand_path("/a"*25) + slot_size = Integer(ObjectSpace.dump(path)[/"slot_size":(\d+)/, 1]) assert_operator(ObjectSpace.memsize_of(path), :<=, - (path.bytesize + 1) * 2 + GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE], bug9934) + (path.bytesize + 1) * 2 + slot_size, bug9934) end def test_expand_path_encoding diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 60f04f8e10cf11..627b3227ee872a 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -230,7 +230,7 @@ def test_stat_heap GC.stat(stat) end - assert_equal (GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD]) * (2**i), stat_heap[:slot_size] + assert_equal GC.stat_heap(0, :slot_size) * (2**i), stat_heap[:slot_size] assert_operator stat_heap[:heap_live_slots], :<=, stat[:heap_live_slots] assert_operator stat_heap[:heap_free_slots], :<=, stat[:heap_free_slots] assert_operator stat_heap[:heap_final_slots], :<=, stat[:heap_final_slots] @@ -692,7 +692,7 @@ def allocate_large_object = Array.new(10) end def test_gc_internals - assert_not_nil GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + assert_not_nil GC::INTERNAL_CONSTANTS[:HEAP_COUNT] end def test_sweep_in_finalizer diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index f3da8e4e138432..46b4a0605e35b4 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -387,7 +387,7 @@ def test_moving_strings_up_heaps GC.verify_compaction_references(expand_heap: true, toward: :empty) Fiber.new { - str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4 + str = "a" * GC.stat_heap(0, :slot_size) * 4 $ary = STR_COUNT.times.map { +"" << str } }.resume @@ -408,7 +408,7 @@ def test_moving_strings_down_heaps GC.verify_compaction_references(expand_heap: true, toward: :empty) Fiber.new { - $ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] * 4).squeeze! } + $ary = STR_COUNT.times.map { ("a" * GC.stat_heap(0, :slot_size) * 4).squeeze! } }.resume stats = GC.verify_compaction_references(expand_heap: true, toward: :empty) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 2458d38ef4b80b..bc911408d6d54a 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -675,7 +675,7 @@ def test_string_interpolations_across_heaps_get_embedded omit if GC::INTERNAL_CONSTANTS[:HEAP_COUNT] == 1 require 'objspace' - base_slot_size = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + base_slot_size = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] small_obj_size = (base_slot_size / 2) large_obj_size = base_slot_size * 2 diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 333edb80218a64..595e183f6c44ca 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -1433,7 +1433,7 @@ def test_memsize RbConfig::SIZEOF["void*"] # Same size as VALUE end sizeof_vtm = RbConfig::SIZEOF["void*"] * 4 + 8 - expect = GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE] + sizeof_timew + sizeof_vtm + expect = GC.stat_heap(0, :slot_size) - GC::INTERNAL_CONSTANTS[:RVALUE_OVERHEAD] + sizeof_timew + sizeof_vtm assert_operator ObjectSpace.memsize_of(t), :<=, expect rescue LoadError => e omit "failed to load objspace: #{e.message}" From 5001c1937e59febca6994c643a1e127fc7195f8b Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Fri, 6 Mar 2026 22:55:10 +0000 Subject: [PATCH 85/87] Compress the size_to_heap_idx table Index on 8 byte chunks instead of individual bytes. This works because all pool stot sizes are pointer aligned, so all sizes in an 8 byte range map to the same heap. --- gc/default/default.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 2d743475344c1c..a3b8a5685e0a69 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -697,7 +697,7 @@ static const size_t pool_slot_sizes[HEAP_COUNT] = { RVALUE_SLOT_SIZE * 16, }; -static uint8_t size_to_heap_idx[RVALUE_SLOT_SIZE * (1 << (HEAP_COUNT - 1)) + 1]; +static uint8_t size_to_heap_idx[RVALUE_SLOT_SIZE * (1 << (HEAP_COUNT - 1)) / 8 + 1]; #ifndef MAX # define MAX(a, b) (((a) > (b)) ? (a) : (b)) @@ -2355,23 +2355,22 @@ ractor_cache_set_page(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, static void init_size_to_heap_idx(void) { - GC_ASSERT(pool_slot_sizes[HEAP_COUNT - 1] - RVALUE_OVERHEAD < sizeof(size_to_heap_idx)); - - for (size_t size = 0; size < sizeof(size_to_heap_idx); size++) { - size_t effective = size + RVALUE_OVERHEAD; + for (size_t i = 0; i < sizeof(size_to_heap_idx); i++) { + size_t effective = i * 8 + RVALUE_OVERHEAD; uint8_t idx; for (idx = 0; idx < HEAP_COUNT; idx++) { if (effective <= pool_slot_sizes[idx]) break; } - size_to_heap_idx[size] = idx; + size_to_heap_idx[i] = idx; } } static inline size_t heap_idx_for_size(size_t size) { - if (size < sizeof(size_to_heap_idx)) { - size_t heap_idx = size_to_heap_idx[size]; + size_t compressed = (size + 7) >> 3; + if (compressed < sizeof(size_to_heap_idx)) { + size_t heap_idx = size_to_heap_idx[compressed]; if (RB_LIKELY(heap_idx < HEAP_COUNT)) return heap_idx; } From 05b87e26417b3c63aeb194a5ea9e0f46af176940 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 9 Mar 2026 11:51:14 -0400 Subject: [PATCH 86/87] WIP: Add load-store elimination HTML slideshow visualization --- zjit/src/hir.rs | 358 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 354 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 200502af52f880..fc1ade7c8d3d75 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4918,32 +4918,177 @@ impl Function { } fn optimize_load_store(&mut self) { + let ptr_map = PtrPrintMap::identity(); + + // Capture original IR lines before the pass mutates anything + enum OrigLine { Block(BlockId, String), Insn(InsnId, String) } + let mut orig_lines: Vec = vec![]; + let mut block_header_idx: HashMap = HashMap::new(); + for &block_id in &self.rpo() { + if block_id == self.entries_block { continue; } + let mut header = format!("{block_id}("); + for (i, param) in self.blocks[block_id.0].params.iter().enumerate() { + if i > 0 { header.push_str(", "); } + header.push_str(&format!("{param}")); + if self.insns[param.0].has_output() { + let ty = self.type_of(*param); + if !ty.is_subtype(types::Empty) { + header.push_str(&format!(":{}", ty.print(&ptr_map))); + } + } + } + header.push_str("):"); + block_header_idx.insert(block_id, orig_lines.len()); + orig_lines.push(OrigLine::Block(block_id, header)); + for &insn_id in &self.blocks[block_id.0].insns { + let insn = &self.insns[insn_id.0]; + if matches!(insn, Insn::Snapshot {..} | Insn::PatchPoint { invariant: Invariant::NoTracePoint, .. }) { + continue; + } + let mut text = String::from(" "); + if insn.has_output() { + let ty = self.insn_types[insn_id.0]; + if ty.is_subtype(types::Empty) { + text.push_str(&format!("{insn_id} = ")); + } else { + text.push_str(&format!("{insn_id}:{} = ", ty.print(&ptr_map))); + } + } + text.push_str(&format!("{}", insn.print(&ptr_map, Some(self.iseq)))); + orig_lines.push(OrigLine::Insn(insn_id, text)); + } + } + + // Trace step data + struct Step { + cursor: InsnId, + action: String, + after_texts: Vec>, // parallel to orig_lines: None=not yet, Some=processed + heap: Vec<((InsnId, i32), InsnId)>, + } + let mut steps: Vec = vec![]; + let mut offset_names: HashMap = HashMap::new(); + + // Build index from InsnId -> position in orig_lines + let mut insn_to_orig_idx: HashMap = HashMap::new(); + for (i, line) in orig_lines.iter().enumerate() { + if let OrigLine::Insn(id, _) = line { + insn_to_orig_idx.insert(*id, i); + } + } + + // Parallel to orig_lines: None = not yet processed, Some(text) = processed + // Block headers are set to Some when their first instruction is reached + let mut after_texts: Vec> = vec![None; orig_lines.len()]; + + // Track which block headers have been revealed + let mut revealed_block_headers: HashSet = HashSet::new(); + + fn fmt_offset(offset: i32) -> String { + if offset < 0 { format!("-{:#x}", -offset) } else { format!("{:#x}", offset) } + } + + fn sorted_heap(heap: &HashMap<(InsnId, i32), InsnId>) -> Vec<((InsnId, i32), InsnId)> { + let mut entries: Vec<_> = heap.iter().map(|(&k, &v)| (k, v)).collect(); + entries.sort_by_key(|&((recv, offset), _)| (offset, recv.0)); + entries + } + + // Helper: format an instruction with union-find resolved operands + fn format_resolved(fun: &Function, insn_id: InsnId, ptr_map: &PtrPrintMap) -> String { + let insn = fun.find(insn_id); + let mut text = String::from(" "); + if insn.has_output() { + let ty = fun.insn_types[insn_id.0]; + if ty.is_subtype(types::Empty) { + text.push_str(&format!("{insn_id} = ")); + } else { + text.push_str(&format!("{insn_id}:{} = ", ty.print(ptr_map))); + } + } + text.push_str(&format!("{}", insn.print(ptr_map, Some(fun.iseq)))); + text + } + + // Helper: reveal a block header in the after column if not yet revealed + macro_rules! reveal_block { + ($block:expr) => { + if !revealed_block_headers.contains(&$block) { + revealed_block_headers.insert($block); + if let Some(&idx) = block_header_idx.get(&$block) { + if let OrigLine::Block(_, ref header) = orig_lines[idx] { + after_texts[idx] = Some(header.clone()); + } + } + } + } + } + + // Helper: record a step snapshot + macro_rules! record_step { + ($cursor:expr, $action:expr, $heap:expr) => { + steps.push(Step { + cursor: $cursor, + action: $action, + after_texts: after_texts.clone(), + heap: sorted_heap($heap), + }); + } + } + let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new(); for block in self.rpo() { let old_insns = std::mem::take(&mut self.blocks[block.0].insns); let mut new_insns = vec![]; for insn_id in old_insns { + // Skip non-visible instructions for tracing + let is_visible = !matches!(self.insns[insn_id.0], Insn::Snapshot {..} | Insn::PatchPoint { invariant: Invariant::NoTracePoint, .. }); + let replacement_insn: InsnId = match self.find(insn_id) { - Insn::StoreField { recv, offset, val, .. } => { + Insn::StoreField { recv, offset, val, id, .. } => { + offset_names.entry(offset).or_insert_with(|| id.contents_lossy().to_string()); let key = (self.chase_insn(recv), offset); let heap_entry = compile_time_heap.get(&key).copied(); // TODO(Jacob): Switch from actual to partial equality if Some(val) == heap_entry { // If the value is already stored, short circuit and don't add an instruction to the block + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(String::new()); // empty line for deleted + } + record_step!(insn_id, + format!("StoreField {insn_id} \u{2014} eliminated (value {val} already at ({}, {}))", key.0, fmt_offset(offset)), + &compile_time_heap); continue } // TODO(Jacob): Add TBAA to avoid removing so many entries compile_time_heap.retain(|(_, off), _| *off != offset); compile_time_heap.insert(key, val); + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(format_resolved(self, insn_id, &ptr_map)); + } + record_step!(insn_id, + format!("StoreField {insn_id} \u{2014} recorded ({}, {}) \u{2192} {val}", key.0, fmt_offset(offset)), + &compile_time_heap); insn_id }, - Insn::LoadField { recv, offset, .. } => { + Insn::LoadField { recv, offset, id, .. } => { + offset_names.entry(offset).or_insert_with(|| id.contents_lossy().to_string()); let key = (self.chase_insn(recv), offset); match compile_time_heap.entry(key) { std::collections::hash_map::Entry::Occupied(entry) => { // If the value is stored already, we should short circuit. // However, we need to replace insn_id with its representative in the SSA union. - self.make_equal_to(insn_id, *entry.get()); + let target = *entry.get(); + self.make_equal_to(insn_id, target); + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(format!(" \u{2192} {target}")); + } + record_step!(insn_id, + format!("LoadField {insn_id} \u{2014} forwarded to {target}"), + &compile_time_heap); continue } std::collections::hash_map::Entry::Vacant(_) => { @@ -4951,6 +5096,13 @@ impl Function { compile_time_heap.insert(key, insn_id); } } + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(format_resolved(self, insn_id, &ptr_map)); + } + record_step!(insn_id, + format!("LoadField {insn_id} \u{2014} cached ({}, {}) \u{2192} {insn_id}", key.0, fmt_offset(offset)), + &compile_time_heap); insn_id } Insn::WriteBarrier { .. } => { @@ -4961,12 +5113,31 @@ impl Function { // TODO: use TBAA let offset = RUBY_OFFSET_RBASIC_FLAGS; compile_time_heap.retain(|(_, off), _| *off != offset); + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(format_resolved(self, insn_id, &ptr_map)); + } + record_step!(insn_id, + format!("WriteBarrier {insn_id} \u{2014} invalidated offset {}", fmt_offset(offset)), + &compile_time_heap); insn_id }, insn => { // If an instruction affects memory and we haven't modeled it, the compile_time_heap is invalidated - if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) { + let action = if insn.effects_of().includes(Effect::write(abstract_heaps::Memory)) { compile_time_heap.clear(); + Some(format!("{insn_id} \u{2014} cleared heap (writes Memory)")) + } else if is_visible { + Some(format!("{insn_id} \u{2014} no heap effect")) + } else { + None + }; + if let Some(action) = action { + reveal_block!(block); + if let Some(&idx) = insn_to_orig_idx.get(&insn_id) { + after_texts[idx] = Some(format_resolved(self, insn_id, &ptr_map)); + } + record_step!(insn_id, action, &compile_time_heap); } insn_id } @@ -4975,6 +5146,185 @@ impl Function { } self.blocks[block.0].insns = new_insns; } + + // Generate HTML slideshow + if steps.is_empty() { return; } + + fn html_escape(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for ch in s.chars() { + match ch { + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '&' => out.push_str("&"), + '"' => out.push_str("""), + _ => out.push(ch), + } + } + out + } + + let iseq_name = if self.iseq.is_null() { + String::from("unknown") + } else { + iseq_get_location(self.iseq, 0) + }; + let sanitized_name: String = iseq_name.chars() + .map(|c| if c.is_ascii_alphanumeric() || c == '_' || c == '-' { c } else { '_' }) + .collect(); + + let total = steps.len(); + let mut frames = String::new(); + + for (i, step) in steps.iter().enumerate() { + let display = if i == 0 { "inherit" } else { "none" }; + + frames.push_str(&format!( + "
\n")); + + // Action line + frames.push_str(&format!( + "

action: {}

\n", html_escape(&step.action))); + + // Three-column layout: Before | After | Heap + frames.push_str("\n"); + + // Left column: Before (original IR with cursor) + frames.push_str("\n"); + + // Middle column: After (incrementally built with resolved operands) + frames.push_str("\n"); + + // Right column: Heap model + frames.push_str("\n"); + + frames.push_str("
Before\n\n"); + for line in &orig_lines { + match line { + OrigLine::Block(_, header) => { + frames.push_str(&format!( + "\n", + html_escape(header))); + } + OrigLine::Insn(id, text) => { + let arrow = if *id == step.cursor { "→" } else { "" }; + frames.push_str(&format!( + "\n", + html_escape(text))); + } + } + } + frames.push_str("
{}
{arrow}{}
After\n\n"); + for (j, line) in orig_lines.iter().enumerate() { + match line { + OrigLine::Block(_, _) => { + match &step.after_texts[j] { + Some(header) => frames.push_str(&format!( + "\n", html_escape(header))), + None => frames.push_str("\n"), + } + } + OrigLine::Insn(_, _) => { + match &step.after_texts[j] { + Some(text) if text.is_empty() => { + // Deleted instruction: empty line + frames.push_str("\n"); + } + Some(text) if text.starts_with(" \u{2192}") => { + // Forwarded load: show in gray + frames.push_str(&format!( + "\n", + html_escape(text))); + } + Some(text) => { + // Kept instruction with resolved operands + frames.push_str(&format!( + "\n", html_escape(text))); + } + None => { + // Not yet processed + frames.push_str("\n"); + } + } + } + } + } + frames.push_str("
{}
 
 
{}
{}
 
Heap\n"); + if step.heap.is_empty() { + frames.push_str("
(empty)\n"); + } else { + frames.push_str("\n"); + let mut current_offset: Option = None; + for &((recv, offset), val) in &step.heap { + if current_offset != Some(offset) { + let name = offset_names.get(&offset).cloned().unwrap_or_default(); + let label = if name.is_empty() { + fmt_offset(offset) + } else { + format!("{} ({})", fmt_offset(offset), name) + }; + frames.push_str(&format!( + "\n", + html_escape(&label))); + current_offset = Some(offset); + } + frames.push_str(&format!( + "\n")); + } + frames.push_str("
{}
  {recv}\u{2192} {val}
\n"); + } + frames.push_str("
\n"); + frames.push_str("
\n"); + } + + let html = format!(r#" + + + + + +

Load-Store Elimination: {iseq_name}

+ + +1 / {total} +{frames} + +"#, iseq_name = html_escape(&iseq_name), total = total, frames = frames); + + let path = format!("{sanitized_name}_load_store.html"); + if let Err(e) = std::fs::write(&path, &html) { + eprintln!("Failed to write load-store slideshow to {path}: {e}"); + } } /// Fold a binary operator on fixnums. From be3a0675d3d93c65154477b772b3c2bb7fb54565 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 9 Mar 2026 11:54:26 -0400 Subject: [PATCH 87/87] WIP: Fix After column width stability with visibility:hidden --- zjit/src/hir.rs | 59 ++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index fc1ade7c8d3d75..64177589ca9dfc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -5211,36 +5211,39 @@ impl Function { // Middle column: After (incrementally built with resolved operands) frames.push_str("After\n\n"); for (j, line) in orig_lines.iter().enumerate() { - match line { - OrigLine::Block(_, _) => { - match &step.after_texts[j] { - Some(header) => frames.push_str(&format!( - "\n", html_escape(header))), - None => frames.push_str("\n"), + let (orig_text, is_block) = match line { + OrigLine::Block(_, h) => (h.as_str(), true), + OrigLine::Insn(_, t) => (t.as_str(), false), + }; + match &step.after_texts[j] { + None => { + // Not yet processed: render original text invisibly to reserve width + if is_block { + frames.push_str(&format!( + "\n", html_escape(orig_text))); + } else { + frames.push_str(&format!( + "\n", html_escape(orig_text))); } } - OrigLine::Insn(_, _) => { - match &step.after_texts[j] { - Some(text) if text.is_empty() => { - // Deleted instruction: empty line - frames.push_str("\n"); - } - Some(text) if text.starts_with(" \u{2192}") => { - // Forwarded load: show in gray - frames.push_str(&format!( - "\n", - html_escape(text))); - } - Some(text) => { - // Kept instruction with resolved operands - frames.push_str(&format!( - "\n", html_escape(text))); - } - None => { - // Not yet processed - frames.push_str("\n"); - } - } + Some(text) if is_block => { + frames.push_str(&format!( + "\n", html_escape(text))); + } + Some(text) if text.is_empty() => { + // Deleted instruction: empty line + frames.push_str("\n"); + } + Some(text) if text.starts_with(" \u{2192}") => { + // Forwarded load: show in gray + frames.push_str(&format!( + "\n", + html_escape(text))); + } + Some(text) => { + // Kept instruction with resolved operands + frames.push_str(&format!( + "\n", html_escape(text))); } } }
{}
 
{}
{}
 
{}
{}
 
{}
 
{}
{}