diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index 175b31a2bb2923..bc5b81a9b99b2c 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -39,10 +39,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 36962338f9114a..2bc1e39e0c807a 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -35,10 +35,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 3b7bfbb3039183..96bca3e3aa5573 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -56,6 +56,13 @@ jobs: run: | ruby -i~ tool/update-bundled_gems.rb gems/bundled_gems >> $GITHUB_OUTPUT + - name: Update spec/bundler/support/builders.rb + run: | + #!ruby + rake_version = File.read("gems/bundled_gems")[/^rake\s+(\S+)/, 1] + print ARGF.read.sub(/^ *def rake_version\s*\K".*?"/) {rake_version.dump} + shell: ruby -i~ {0} spec/bundler/support/builders.rb + - name: Maintain updated gems list in NEWS run: | ruby tool/update-NEWS-gemlist.rb bundled @@ -69,6 +76,7 @@ jobs: git diff --color --no-ext-diff --ignore-submodules --exit-code -- gems/bundled_gems || gems=true git add -- NEWS.md gems/bundled_gems + git add -- spec/bundler/support/builders.rb echo news=$news >> $GITHUB_OUTPUT echo gems=$gems >> $GITHUB_OUTPUT echo update=${news:-$gems} >> $GITHUB_OUTPUT diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 153d456e89db35..5187138be97c8e 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -37,10 +37,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 430a2c950d21b6..38148d4f667ce6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,10 +41,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -80,15 +80,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: category: '/language:${{ matrix.language }}' upload: False @@ -118,7 +118,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 4b58d9d10a1cb6..0eebb0eea21939 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -220,10 +220,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c89635ea822430..87231228bcef83 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -44,10 +44,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 7b9db05f116832..1ed83de53521a3 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -57,10 +57,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/prism.yml b/.github/workflows/prism.yml index 7fc10e2ab12775..adb9206c369178 100644 --- a/.github/workflows/prism.yml +++ b/.github/workflows/prism.yml @@ -47,10 +47,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/rjit-bindgen.yml b/.github/workflows/rjit-bindgen.yml index eea5a0c0151dd7..65f7b080a3d678 100644 --- a/.github/workflows/rjit-bindgen.yml +++ b/.github/workflows/rjit-bindgen.yml @@ -38,10 +38,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/rjit.yml b/.github/workflows/rjit.yml index 7382c1179f0d23..b393074919ad11 100644 --- a/.github/workflows/rjit.yml +++ b/.github/workflows/rjit.yml @@ -47,10 +47,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 2889d4d47c4747..69b31e6b698783 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v2.1.27 + uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v2.1.27 with: sarif_file: results.sarif diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index c14b4ee03845fd..f4d8c378373eb0 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -27,10 +27,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 62e1b564b8828b..42b485700d3838 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -33,6 +33,8 @@ jobs: configure: '--disable-yjit' - test_task: check configure: '--enable-shared --enable-load-relative' + - test_task: check + configure: '--with-shared-gc' - test_task: test-bundler-parallel - test_task: test-bundled-gems - test_task: check @@ -48,10 +50,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -103,7 +105,7 @@ jobs: - name: Set up Launchable uses: ./.github/actions/launchable/setup with: - os: ${{ matrix.os }} + os: ${{ matrix.os || 'ubuntu-22.04' }} test-opts: ${{ matrix.configure }} launchable-token: ${{ secrets.LAUNCHABLE_TOKEN }} builddir: build diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index ad7ea13cbf3e45..5ec4bfafaed22e 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -53,10 +53,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index db2235eb6c972a..56e2711b5b9d5d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -42,10 +42,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/yjit-macos.yml b/.github/workflows/yjit-macos.yml index be3539c2ea8c6a..5e209cc4cae5a1 100644 --- a/.github/workflows/yjit-macos.yml +++ b/.github/workflows/yjit-macos.yml @@ -29,10 +29,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -71,10 +71,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index b74629340f0a7e..92ff9eadaed607 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -31,7 +31,7 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -63,7 +63,7 @@ jobs: ${{!(false || contains(github.event.head_commit.message, '[DOC]') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} @@ -117,10 +117,10 @@ jobs: if: >- ${{!(false || contains(github.event.head_commit.message, '[DOC]') - || contains(github.event.head_commit.message, 'Documentation') + || contains(github.event.head_commit.message, 'Document') || contains(github.event.pull_request.title, '[DOC]') - || contains(github.event.pull_request.title, 'Documentation') - || contains(github.event.pull_request.labels.*.name, 'Documentation') + || contains(github.event.pull_request.title, 'Document') + || contains(github.event.pull_request.labels.*.name, 'Document') || (github.event_name == 'push' && github.actor == 'dependabot[bot]') )}} diff --git a/.mailmap b/.mailmap index 36bef53d644de6..213a0f49164c0f 100644 --- a/.mailmap +++ b/.mailmap @@ -3,429 +3,429 @@ git[bot] git svn # a_matsuda -Akira Matsuda (a_matsuda) -Akira Matsuda (a_matsuda) +Akira Matsuda +Akira Matsuda # aamine -Minero Aoki (aamine) -Minero Aoki (aamine) +Minero Aoki +Minero Aoki # akira -akira yamada (akira) -## akira yamada (akira) -akira yamada (akira) -akira yamada (akira) +akira yamada +## akira yamada +akira yamada +akira yamada # akiyoshi -AKIYOSHI, Masamichi (akiyoshi) -AKIYOSHI, Masamichi (akiyoshi) +AKIYOSHI, Masamichi +AKIYOSHI, Masamichi # akr -Tanaka Akira (akr) -Tanaka Akira (akr) +Tanaka Akira +Tanaka Akira # arai -Koji Arai (arai) -Koji Arai (arai) +Koji Arai +Koji Arai # arton -Akio Tajima (arton) -Akio Tajima (arton) +Akio Tajima +Akio Tajima # aycabta -aycabta (aycabta) -aycabta (aycabta) +aycabta +aycabta # ayumin -Ayumu AIZAWA (ayumin) -Ayumu AIZAWA (ayumin) +Ayumu AIZAWA +Ayumu AIZAWA # azav -Alexander Zavorine (azav) -Alexander Zavorine (azav) +Alexander Zavorine +Alexander Zavorine # charliesome -Charlie Somerville (charliesome) -Charlie Somerville (charliesome) +Charlie Somerville +Charlie Somerville # dave -Dave Thomas (dave) -Dave Thomas (dave) +Dave Thomas +Dave Thomas # davidflanagan -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) -David Flanagan (davidflanagan) +David Flanagan +David Flanagan +David Flanagan +David Flanagan # dblack -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) -David A. Black (dblack) +David A. Black +David A. Black +David A. Black +David A. Black # drbrain -Eric Hodel (drbrain) -Eric Hodel (drbrain) +Eric Hodel +Eric Hodel # duerst -Martin Dürst (duerst) -Martin Dürst (duerst) +Martin Dürst +Martin Dürst # eban -WATANABE Hirofumi (eban) -WATANABE Hirofumi (eban) +WATANABE Hirofumi +WATANABE Hirofumi # emboss -Martin Bosslet (emboss) -Martin Bosslet (emboss) -Martin Bosslet (emboss) +Martin Bosslet +Martin Bosslet +Martin Bosslet # eregon -Benoit Daloze (eregon) -Benoit Daloze (eregon) +Benoit Daloze +Benoit Daloze # evan -Evan Phoenix (evan) -Evan Phoenix (evan) -Evan Phoenix (evan) +Evan Phoenix +Evan Phoenix +Evan Phoenix # glass -Masaki Matsushita (glass) -Masaki Matsushita (glass) +Masaki Matsushita +Masaki Matsushita # gogotanaka -Kazuki Tanaka (gogotanaka) -Kazuki Tanaka (gogotanaka) +Kazuki Tanaka +Kazuki Tanaka # gotoken -Kentaro Goto (gotoken) -Kentaro Goto (gotoken) +Kentaro Goto +Kentaro Goto # gotoyuzo -GOTOU Yuuzou (gotoyuzo) -GOTOU Yuuzou (gotoyuzo) +GOTOU Yuuzou +GOTOU Yuuzou # gsinclair -Gavin Sinclair (gsinclair) -Gavin Sinclair (gsinclair) +Gavin Sinclair +Gavin Sinclair # H_Konishi -KONISHI Hiromasa (H_Konishi) -KONISHI Hiromasa (H_Konishi) +KONISHI Hiromasa +KONISHI Hiromasa # headius -Charles Oliver Nutter (headius) -Charles Oliver Nutter (headius) +Charles Oliver Nutter +Charles Oliver Nutter # hone -Terence Lee (hone) -Terence Lee (hone) +Terence Lee +Terence Lee # hsbt -Hiroshi SHIBATA (hsbt) -Hiroshi SHIBATA (hsbt) +Hiroshi SHIBATA +Hiroshi SHIBATA # iwamatsu -Nobuhiro Iwamatsu (iwamatsu) -Nobuhiro Iwamatsu (iwamatsu) +Nobuhiro Iwamatsu +Nobuhiro Iwamatsu # jeg2 -James Edward Gray II (jeg2) -James Edward Gray II (jeg2) +James Edward Gray II +James Edward Gray II # jim -Jim Weirich (jim) -Jim Weirich (jim) +Jim Weirich +Jim Weirich # k0kubun -Takashi Kokubun (k0kubun) -Takashi Kokubun (k0kubun) +Takashi Kokubun +Takashi Kokubun # kanemoto -Yutaka Kanemoto (kanemoto) -Yutaka Kanemoto (kanemoto) +Yutaka Kanemoto +Yutaka Kanemoto # katsu -UENO Katsuhiro (katsu) -UENO Katsuhiro (katsu) +UENO Katsuhiro +UENO Katsuhiro # kazu -Kazuhiro NISHIYAMA (kazu) -Kazuhiro NISHIYAMA (kazu) +Kazuhiro NISHIYAMA +Kazuhiro NISHIYAMA # keiju -Keiju Ishitsuka (keiju) -Keiju Ishitsuka (keiju) +Keiju Ishitsuka +Keiju Ishitsuka # knu -Akinori MUSHA (knu) -Akinori MUSHA (knu) +Akinori MUSHA +Akinori MUSHA # ko1 -Koichi Sasada (ko1) -Koichi Sasada (ko1) +Koichi Sasada +Koichi Sasada # kosaki -KOSAKI Motohiro (kosaki) -KOSAKI Motohiro (kosaki) +KOSAKI Motohiro +KOSAKI Motohiro # kosako -K.Kosako (kosako) -K.Kosako (kosako) +K.Kosako +K.Kosako # kou -Sutou Kouhei (kou) -Sutou Kouhei (kou) -Sutou Kouhei (kou) +Sutou Kouhei +Sutou Kouhei +Sutou Kouhei # kouji -Kouji Takao (kouji) -Kouji Takao (kouji) -Kouji Takao (kouji) +Kouji Takao +Kouji Takao +Kouji Takao # ksaito -Kazuo Saito (ksaito) -Kazuo Saito (ksaito) +Kazuo Saito +Kazuo Saito # ktsj -Kazuki Tsujimoto (ktsj) -Kazuki Tsujimoto (ktsj) +Kazuki Tsujimoto +Kazuki Tsujimoto # luislavena -Luis Lavena (luislavena) -Luis Lavena (luislavena) +Luis Lavena +Luis Lavena # mame -Yusuke Endoh (mame) -## Yusuke Endoh (mame) -Yusuke Endoh (mame) +Yusuke Endoh +## Yusuke Endoh +Yusuke Endoh # marcandre -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) -Marc-Andre Lafortune (marcandre) +Marc-Andre Lafortune +Marc-Andre Lafortune +Marc-Andre Lafortune # matz -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) -Yukihiro "Matz" Matsumoto (matz) +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto +Yukihiro "Matz" Matsumoto # michal -Michal Rokos (michal) -Michal Rokos (michal) +Michal Rokos +Michal Rokos # mneumann -Michael Neumann (mneumann) -Michael Neumann (mneumann) +Michael Neumann +Michael Neumann # mrkn -Kenta Murata (mrkn) -Kenta Murata (mrkn) -Kenta Murata (mrkn) <3959+mrkn@users.noreply.github.com> -Kenta Murata (mrkn) +Kenta Murata +Kenta Murata +Kenta Murata <3959+mrkn@users.noreply.github.com> +Kenta Murata # nagachika -nagachika (nagachika) -nagachika (nagachika) +nagachika +nagachika # nagai -Hidetoshi NAGAI (nagai) -Hidetoshi NAGAI (nagai) +Hidetoshi NAGAI +Hidetoshi NAGAI # nahi -Hiroshi Nakamura (nahi) -Hiroshi Nakamura (nahi) +Hiroshi Nakamura +Hiroshi Nakamura # nari -Narihiro Nakamura (nari) -Narihiro Nakamura (nari) +Narihiro Nakamura +Narihiro Nakamura # naruse -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) -NARUSE, Yui (naruse) +NARUSE, Yui +NARUSE, Yui +NARUSE, Yui # ngoto -Naohisa Goto (ngoto) -Naohisa Goto (ngoto) +Naohisa Goto +Naohisa Goto # nobu -Nobuyoshi Nakada (nobu) -Nobuyoshi Nakada (nobu) +Nobuyoshi Nakada +Nobuyoshi Nakada # normal -Eric Wong (normal) -Eric Wong (normal) -Eric Wong (normal) +Eric Wong +Eric Wong +Eric Wong # ntalbott -Nathaniel Talbott (ntalbott) -Nathaniel Talbott (ntalbott) +Nathaniel Talbott +Nathaniel Talbott # ocean -Hirokazu Yamamoto (ocean) -Hirokazu Yamamoto (ocean) +Hirokazu Yamamoto +Hirokazu Yamamoto # odaira -Rei Odaira (odaira) -Rei Odaira (odaira) -Rei Odaira (odaira) +Rei Odaira +Rei Odaira +Rei Odaira # okkez -okkez (okkez) -okkez (okkez) +okkez +okkez # rhe -Kazuki Yamaguchi (rhe) -Kazuki Yamaguchi (rhe) +Kazuki Yamaguchi +Kazuki Yamaguchi # ryan -Ryan Davis (ryan) -Ryan Davis (ryan) -Ryan Davis (ryan) +Ryan Davis +Ryan Davis +Ryan Davis # samuel -Samuel Williams (samuel) -Samuel Williams (samuel) +Samuel Williams +Samuel Williams # seki -Masatoshi SEKI (seki) -Masatoshi SEKI (seki) +Masatoshi SEKI +Masatoshi SEKI # ser -Sean Russell (ser) -Sean Russell (ser) +Sean Russell +Sean Russell # shigek -Shigeo Kobayashi (shigek) -Shigeo Kobayashi (shigek) +Shigeo Kobayashi +Shigeo Kobayashi # shirosaki -Hiroshi Shirosaki (shirosaki) -Hiroshi Shirosaki (shirosaki) +Hiroshi Shirosaki +Hiroshi Shirosaki # sho-h -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) -Sho Hashimoto (sho-h) +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto +Sho Hashimoto # shugo -Shugo Maeda (shugo) -Shugo Maeda (shugo) +Shugo Maeda +Shugo Maeda # shyouhei -卜部昌平 (shyouhei) -卜部昌平 (shyouhei) +卜部昌平 +卜部昌平 # siena -Siena. (siena) -Siena. (siena) +Siena. +Siena. # sonots -sonots (sonots) -sonots (sonots) +sonots +sonots # sorah -Sorah Fukumori (sorah) -Sorah Fukumori (sorah) +Sorah Fukumori +Sorah Fukumori # stomar -Marcus Stollsteimer (stomar) -Marcus Stollsteimer (stomar) +Marcus Stollsteimer +Marcus Stollsteimer # suke -Masaki Suketa (suke) -Masaki Suketa (suke) +Masaki Suketa +Masaki Suketa # tadd -Tadashi Saito (tadd) -Tadashi Saito (tadd) +Tadashi Saito +Tadashi Saito # tadf -Tadayoshi Funaba (tadf) -Tadayoshi Funaba (tadf) +Tadayoshi Funaba +Tadayoshi Funaba # takano32 -TAKANO Mitsuhiro (takano32) -TAKANO Mitsuhiro (takano32) +TAKANO Mitsuhiro +TAKANO Mitsuhiro # tarui -Masaya Tarui (tarui) -Masaya Tarui (tarui) +Masaya Tarui +Masaya Tarui # technorama -Technorama Ltd. (technorama) -Technorama Ltd. (technorama) +Technorama Ltd. +Technorama Ltd. # tenderlove -Aaron Patterson (tenderlove) -Aaron Patterson (tenderlove) +Aaron Patterson +Aaron Patterson # tmm1 -Aman Gupta (tmm1) -Aman Gupta (tmm1) +Aman Gupta +Aman Gupta # ts -Guy Decoux (ts) -Guy Decoux (ts) +Guy Decoux +Guy Decoux # ttate -Takaaki Tateishi (ttate) -## Takaaki Tateishi (ttate) -Takaaki Tateishi (ttate) +Takaaki Tateishi +## Takaaki Tateishi +Takaaki Tateishi # uema2 -Takaaki Uematsu (uema2) -Takaaki Uematsu (uema2) +Takaaki Uematsu +Takaaki Uematsu # usa -U.Nakamura (usa) -U.Nakamura (usa) -U.Nakamura (usa) +U.Nakamura +U.Nakamura +U.Nakamura # wakou -Wakou Aoyama (wakou) -Wakou Aoyama (wakou) +Wakou Aoyama +Wakou Aoyama # wanabe -wanabe (wanabe) -wanabe (wanabe) +wanabe +wanabe # watson1978 -Watson (watson1978) -Watson (watson1978) +Watson +Watson # wew -William Webber (wew) -William Webber (wew) +William Webber +William Webber # why -why the lucky stiff (why) -why the lucky stiff (why) +why the lucky stiff +why the lucky stiff # xibbar -Takeyuki FUJIOKA (xibbar) -Takeyuki FUJIOKA (xibbar) +Takeyuki FUJIOKA +Takeyuki FUJIOKA # yugui -Yuki Yugui Sonoda (yugui) -Yuki Yugui Sonoda (yugui) +Yuki Yugui Sonoda +Yuki Yugui Sonoda # yui-knk -yui-knk (yui-knk) -yui-knk (yui-knk) +yui-knk +yui-knk # yuki -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) -Yuki Nishijima (yuki) +Yuki Nishijima +Yuki Nishijima +Yuki Nishijima # zsombor -Dee Zsombor (zsombor) -Dee Zsombor (zsombor) +Dee Zsombor +Dee Zsombor # zzak -zzak (zzak) -zzak (zzak) +zzak +zzak diff --git a/NEWS.md b/NEWS.md index e8c80d295ea8ce..34fa51ce9d051a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,17 +8,16 @@ Note that each entry is kept to a minimum, see links for details. ## Language changes * String literals in files without a `frozen_string_literal` comment now behave - as if they were frozen. If they are mutated a deprecation warning is emited. + as if they were frozen. If they are mutated a deprecation warning is emitted. These warnings can be enabled with `-W:deprecated` or by setting `Warning[:deprecated] = true`. - To disable this change you can run Ruby with the `--disable-frozen-string-literal` command line - argument. [[Feature #20205]] + To disable this change, you can run Ruby with the `--disable-frozen-string-literal` + command line argument. [[Feature #20205]] * `it` is added to reference a block parameter. [[Feature #18980]] * Keyword splatting `nil` when calling methods is now supported. - `**nil` is treated similar to `**{}`, passing no keywords, - and not calling any conversion methods. - [[Bug #20064]] + `**nil` is treated similarly to `**{}`, passing no keywords, + and not calling any conversion methods. [[Bug #20064]] * Block passing is no longer allowed in index. [[Bug #19918]] @@ -45,9 +44,10 @@ The following default gems are updated. * irb 1.12.0 * json 2.7.2 * net-http 0.4.1 -* prism 0.24.0 +* optparse 0.5.0 +* prism 0.25.0 * rdoc 6.6.3.1 -* reline 0.5.0 +* reline 0.5.1 * resolv 0.4.0 * stringio 3.1.1 * strscan 3.1.1 @@ -112,7 +112,14 @@ See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/log ## JIT +## Miscellaneous changes + +* Passing a block to a method which doesn't use the passed block will show + a warning on verbose mode (`-w`). + [[Feature #15554]] + [Feature #13557]: https://bugs.ruby-lang.org/issues/13557 +[Feature #15554]: https://bugs.ruby-lang.org/issues/15554 [Feature #16495]: https://bugs.ruby-lang.org/issues/16495 [Feature #18290]: https://bugs.ruby-lang.org/issues/18290 [Feature #18980]: https://bugs.ruby-lang.org/issues/18980 diff --git a/array.c b/array.c index 4040bfcdae0825..2fa797081ce29a 100644 --- a/array.c +++ b/array.c @@ -3397,6 +3397,9 @@ rb_ary_sort_bang(VALUE ary) rb_ary_unshare(ary); FL_SET_EMBED(ary); } + if (ARY_EMBED_LEN(tmp) > ARY_CAPA(ary)) { + ary_resize_capa(ary, ARY_EMBED_LEN(tmp)); + } ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); ARY_SET_LEN(ary, ARY_EMBED_LEN(tmp)); } diff --git a/array.rb b/array.rb index 8e809b35c9a58f..f63ff000568186 100644 --- a/array.rb +++ b/array.rb @@ -43,6 +43,8 @@ class Array # Related: #each_index, #reverse_each. def each Primitive.attr! :inline_block + Primitive.attr! :use_block + unless defined?(yield) return Primitive.cexpr! 'SIZED_ENUMERATOR(self, 0, 0, ary_enum_length)' end diff --git a/ast.c b/ast.c index 70f298c7f8adf5..a4c57b898be6b6 100644 --- a/ast.c +++ b/ast.c @@ -97,7 +97,7 @@ rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE StringValue(str); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); @@ -120,7 +120,7 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VAL f = rb_file_open_str(path, "r"); rb_funcall(f, rb_intern("set_encoding"), 2, rb_enc_from_encoding(enc), rb_str_new_cstr("-")); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); @@ -148,7 +148,7 @@ rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, V array = rb_check_array_type(array); VALUE vparser = ast_parse_new(); - if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser, Qtrue); + if (RTEST(keep_script_lines)) rb_parser_set_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); @@ -806,9 +806,9 @@ ast_node_script_lines(rb_execution_context_t *ec, VALUE self) { struct ASTNodeData *data; TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); - VALUE ret = data->ast->body.script_lines; - if (!RB_TYPE_P(ret, T_ARRAY)) return Qnil; - return ret; + rb_parser_ary_t *ret = data->ast->body.script_lines; + if (!ret || FIXNUM_P((VALUE)ret)) return Qnil; + return rb_parser_build_script_lines_from(ret); } #include "ast.rbinc" diff --git a/benchmark/object_allocate.yml b/benchmark/object_allocate.yml index 93ff463e415585..bdbd4536db2e6a 100644 --- a/benchmark/object_allocate.yml +++ b/benchmark/object_allocate.yml @@ -11,6 +11,26 @@ prelude: | class OneTwentyEight 128.times { include(Module.new) } end + class OnePositional + def initialize a; end + end + class TwoPositional + def initialize a, b; end + end + class ThreePositional + def initialize a, b, c; end + end + class FourPositional + def initialize a, b, c, d; end + end + class KWArg + def initialize a:, b:, c:, d: + end + end + class Mixed + def initialize a, b, c:, d: + end + end # Disable GC to see raw throughput: GC.disable benchmark: @@ -18,4 +38,11 @@ benchmark: allocate_32_deep: ThirtyTwo.new allocate_64_deep: SixtyFour.new allocate_128_deep: OneTwentyEight.new + allocate_1_positional_params: OnePositional.new(1) + allocate_2_positional_params: TwoPositional.new(1, 2) + allocate_3_positional_params: ThreePositional.new(1, 2, 3) + allocate_4_positional_params: FourPositional.new(1, 2, 3, 4) + allocate_kwarg_params: "KWArg.new(a: 1, b: 2, c: 3, d: 4)" + allocate_mixed_params: "Mixed.new(1, 2, c: 3, d: 4)" + allocate_no_params: "Object.new" loop_count: 100000 diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 2135f1427656d1..20f121cdf492f0 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -6,7 +6,6 @@ # Never use optparse in this file. # Never use test/unit in this file. # Never use Ruby extensions in this file. -# Maintain Ruby 1.8 compatibility for now $start_time = Time.now @@ -428,7 +427,7 @@ def add as def initialize(*args) super self.class.add self - @category = self.path.match(/test_(.+)\.rb/)[1] + @category = self.path[/\Atest_(.+)\.rb\z/, 1] end def call diff --git a/compile.c b/compile.c index aab9ca811b2c1a..791620aaa25578 100644 --- a/compile.c +++ b/compile.c @@ -870,10 +870,6 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) DECL_ANCHOR(ret); INIT_ANCHOR(ret); - if (IMEMO_TYPE_P(node, imemo_ifunc)) { - rb_raise(rb_eArgError, "unexpected imemo_ifunc"); - } - if (node == 0) { NO_CHECK(COMPILE(ret, "nil", node)); iseq_set_local_table(iseq, 0); @@ -1488,7 +1484,7 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node, ast.root = node; ast.frozen_string_literal = -1; ast.coverage_enabled = -1; - ast.script_lines = ISEQ_BODY(iseq)->variable.script_lines; + ast.script_lines = NULL; debugs("[new_child_iseq]> ---------------------------------------\n"); int isolated_depth = ISEQ_COMPILE_DATA(iseq)->isolated_depth; @@ -1496,7 +1492,8 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node, rb_iseq_path(iseq), rb_iseq_realpath(iseq), line_no, parent, isolated_depth ? isolated_depth + 1 : 0, - type, ISEQ_COMPILE_DATA(iseq)->option); + type, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); debugs("[new_child_iseq]< ---------------------------------------\n"); return ret_iseq; } @@ -2103,6 +2100,7 @@ iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *cons if (block_id) { body->param.block_start = arg_size++; body->param.flags.has_block = TRUE; + body->param.flags.use_block = 1; } iseq_calc_param_size(iseq); @@ -4343,7 +4341,7 @@ compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *cons while (list) { const NODE *const head = list->nd_head; if (nd_type_p(head, NODE_STR)) { - lit = rb_fstring(rb_node_str_string_val(head)); + lit = rb_node_str_string_val(head); ADD_INSN1(ret, head, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); lit = Qnil; @@ -4382,7 +4380,7 @@ compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) { int cnt; if (!RNODE_DSTR(node)->nd_next) { - VALUE lit = rb_fstring(rb_node_dstr_string_val(node)); + VALUE lit = rb_node_dstr_string_val(node); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -4770,14 +4768,13 @@ static_literal_value(const NODE *node, rb_iseq_t *iseq) case NODE_FILE: case NODE_STR: if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { - VALUE lit; VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX((int)nd_line(node))); - lit = rb_str_dup(get_string_value(node)); + VALUE lit = rb_str_dup(get_string_value(node)); rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); return rb_str_freeze(lit); } else { - return rb_fstring(get_string_value(node)); + return get_string_value(node); } default: rb_bug("unexpected node: %s", ruby_node_name(nd_type(node))); @@ -5147,9 +5144,9 @@ rb_node_case_when_optimizable_literal(const NODE *const node) case NODE_LINE: return rb_node_line_lineno_val(node); case NODE_STR: - return rb_fstring(rb_node_str_string_val(node)); + return rb_node_str_string_val(node); case NODE_FILE: - return rb_fstring(rb_node_file_path_val(node)); + return rb_node_file_path_val(node); } return Qundef; } @@ -5171,7 +5168,7 @@ when_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, if (nd_type_p(val, NODE_STR) || nd_type_p(val, NODE_FILE)) { debugp_param("nd_lit", get_string_value(val)); - lit = rb_fstring(get_string_value(val)); + lit = get_string_value(val); ADD_INSN1(cond_seq, val, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); } @@ -5924,6 +5921,7 @@ defined_expr0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, ADD_INSN(ret, line_node, putnil); ADD_INSN3(ret, line_node, defined, INT2FIX(DEFINED_YIELD), 0, PUSH_VAL(DEFINED_YIELD)); + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; return; case NODE_BACK_REF: @@ -8454,7 +8452,7 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE get_nd_args(node) == NULL && ISEQ_COMPILE_DATA(iseq)->current_block == NULL && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(get_nd_recv(node))); + VALUE str = get_string_value(get_nd_recv(node)); if (get_node_call_nd_mid(node) == idUMinus) { ADD_INSN2(ret, line_node, opt_str_uminus, str, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); @@ -8478,7 +8476,7 @@ compile_call_precheck_freeze(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE ISEQ_COMPILE_DATA(iseq)->current_block == NULL && !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(get_nd_args(node))->nd_head)); + VALUE str = get_string_value(RNODE_LIST(get_nd_args(node))->nd_head); CHECK(COMPILE(ret, "recv", get_nd_recv(node))); ADD_INSN2(ret, line_node, opt_aref_with, str, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); @@ -8638,6 +8636,9 @@ compile_builtin_attr(rb_iseq_t *iseq, const NODE *node) else if (strcmp(RSTRING_PTR(string), "inline_block") == 0) { ISEQ_BODY(iseq)->builtin_attrs |= BUILTIN_ATTR_INLINE_BLOCK; } + else if (strcmp(RSTRING_PTR(string), "use_block") == 0) { + ISEQ_BODY(iseq)->param.flags.use_block = 1; + } else { goto unknown_arg; } @@ -8745,14 +8746,15 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N .root = RNODE(&scope_node), .frozen_string_literal = -1, .coverage_enabled = -1, - .script_lines = ISEQ_BODY(iseq)->variable.script_lines, + .script_lines = NULL }; ISEQ_BODY(iseq)->mandatory_only_iseq = rb_iseq_new_with_opt(&ast, rb_iseq_base_label(iseq), rb_iseq_path(iseq), rb_iseq_realpath(iseq), nd_line(line_node), NULL, 0, - ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option); + ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option, + ISEQ_BODY(iseq)->variable.script_lines); ALLOCV_END(idtmp); return COMPILE_OK; @@ -9374,9 +9376,11 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i unsigned int flag = 0; struct rb_callinfo_kwarg *keywords = NULL; const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; + int use_block = 1; INIT_ANCHOR(args); ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + if (type == NODE_SUPER) { VALUE vargc = setup_args(iseq, args, RNODE_SUPER(node)->nd_args, &flag, &keywords); CHECK(!NIL_P(vargc)); @@ -9384,6 +9388,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i if ((flag & VM_CALL_ARGS_BLOCKARG) && (flag & VM_CALL_KW_SPLAT) && !(flag & VM_CALL_KW_SPLAT_MUT)) { ADD_INSN(args, node, splatkw); } + + if (flag & VM_CALL_ARGS_BLOCKARG) { + use_block = 0; + } } else { /* NODE_ZSUPER */ @@ -9477,6 +9485,10 @@ compile_super(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i } } + if (use_block && parent_block == NULL) { + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; + } + flag |= VM_CALL_SUPER | VM_CALL_FCALL; if (type == NODE_ZSUPER) flag |= VM_CALL_ZSUPER; ADD_INSN(ret, node, putself); @@ -9520,6 +9532,7 @@ compile_yield(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i ADD_SEQ(ret, args); ADD_INSN1(ret, node, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), flag, keywords, FALSE)); + ISEQ_BODY(ISEQ_BODY(iseq)->local_iseq)->param.flags.use_block = 1; if (popped) { ADD_INSN(ret, node, pop); @@ -9755,7 +9768,7 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node !frozen_string_literal_p(iseq) && ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction) { - VALUE str = rb_fstring(get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head)); + VALUE str = get_string_value(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_head); CHECK(COMPILE(ret, "recv", RNODE_ATTRASGN(node)->nd_recv)); CHECK(COMPILE(ret, "value", RNODE_LIST(RNODE_LIST(RNODE_ATTRASGN(node)->nd_args)->nd_next)->nd_head)); if (!popped) { @@ -9791,16 +9804,7 @@ compile_attrasgn(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node ADD_SEQ(ret, recv); ADD_SEQ(ret, args); - if (flag & VM_CALL_ARGS_BLOCKARG) { - ADD_INSN1(ret, node, topn, INT2FIX(1)); - if (flag & VM_CALL_ARGS_SPLAT) { - ADD_INSN1(ret, node, putobject, INT2FIX(-1)); - ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); - } - ADD_INSN1(ret, node, setn, FIXNUM_INC(argc, 3)); - ADD_INSN (ret, node, pop); - } - else if (flag & VM_CALL_ARGS_SPLAT) { + if (flag & VM_CALL_ARGS_SPLAT) { ADD_INSN(ret, node, dup); ADD_INSN1(ret, node, putobject, INT2FIX(-1)); ADD_SEND_WITH_FLAG(ret, node, idAREF, INT2FIX(1), INT2FIX(asgnflag)); @@ -9989,7 +9993,7 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa return COMPILE_OK; case NODE_STR:{ - VALUE lit = rb_fstring(rb_node_str_string_val(node)); + VALUE lit = rb_node_str_string_val(node); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); *value_p = lit; @@ -9999,7 +10003,7 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa } case NODE_FILE:{ - VALUE lit = rb_fstring(rb_node_file_path_val(node)); + VALUE lit = rb_node_file_path_val(node); ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); *value_p = lit; @@ -10594,12 +10598,10 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no VALUE lit = get_string_value(node); switch (ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal) { case ISEQ_FROZEN_STRING_LITERAL_UNSET: - lit = rb_fstring(lit); ADD_INSN1(ret, node, putchilledstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; case ISEQ_FROZEN_STRING_LITERAL_DISABLED: - lit = rb_fstring(lit); ADD_INSN1(ret, node, putstring, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; @@ -10610,9 +10612,6 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info)); lit = rb_str_freeze(lit); } - else { - lit = rb_fstring(lit); - } ADD_INSN1(ret, node, putobject, lit); RB_OBJ_WRITTEN(iseq, Qundef, lit); break; @@ -10632,7 +10631,7 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no } case NODE_XSTR:{ ADD_CALL_RECEIVER(ret, node); - VALUE str = rb_fstring(rb_node_str_string_val(node)); + VALUE str = rb_node_str_string_val(node); ADD_INSN1(ret, node, putobject, str); RB_OBJ_WRITTEN(iseq, Qundef, str); ADD_CALL(ret, node, idBackquote, INT2FIX(1)); @@ -12959,7 +12958,10 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) (body->param.flags.has_block << 6) | (body->param.flags.ambiguous_param0 << 7) | (body->param.flags.accepts_no_kwarg << 8) | - (body->param.flags.ruby2_keywords << 9); + (body->param.flags.ruby2_keywords << 9) | + (body->param.flags.anon_rest << 10) | + (body->param.flags.anon_kwrest << 11) | + (body->param.flags.use_block << 12); #if IBF_ISEQ_ENABLE_LOCAL_BUFFER # define IBF_BODY_OFFSET(x) (x) @@ -13175,6 +13177,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->param.flags.ruby2_keywords = (param_flags >> 9) & 1; load_body->param.flags.anon_rest = (param_flags >> 10) & 1; load_body->param.flags.anon_kwrest = (param_flags >> 11) & 1; + load_body->param.flags.use_block = (param_flags >> 12) & 1; load_body->param.size = param_size; load_body->param.lead_num = param_lead_num; load_body->param.opt_num = param_opt_num; diff --git a/configure.ac b/configure.ac index 28d006e4368b7d..9ea001385007d3 100644 --- a/configure.ac +++ b/configure.ac @@ -38,6 +38,7 @@ m4_include([tool/m4/ruby_replace_type.m4])dnl m4_include([tool/m4/ruby_require_funcs.m4])dnl m4_include([tool/m4/ruby_rm_recursive.m4])dnl m4_include([tool/m4/ruby_setjmp_type.m4])dnl +m4_include([tool/m4/ruby_shared_gc.m4])dnl m4_include([tool/m4/ruby_stack_grow_direction.m4])dnl m4_include([tool/m4/ruby_thread.m4])dnl m4_include([tool/m4/ruby_try_cflags.m4])dnl @@ -277,7 +278,7 @@ AC_CHECK_TOOLS([STRIP], [gstrip strip], [:]) # nm errors with Rust's LLVM bitcode when Rust uses a newer LLVM version than nm. # In case we're working with llvm-nm, tell it to not worry about the bitcode. -AS_IF([${NM} --help | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) +AS_IF([${NM} --help 2>&1 | grep -q 'llvm-bc'], [NM="$NM --no-llvm-bc"]) AS_IF([test ! $rb_test_CFLAGS], [AS_UNSET(CFLAGS)]); AS_UNSET(rb_test_CFLAGS) AS_IF([test ! $rb_test_CXXFLAGS], [AS_UNSET(CXXFLAGS)]); AS_UNSET(rb_save_CXXFLAGS) @@ -3750,6 +3751,7 @@ AS_IF([test x"$gcov" = xyes], [ ]) RUBY_SETJMP_TYPE +RUBY_SHARED_GC } [begin]_group "installation section" && { @@ -4656,6 +4658,7 @@ config_summary "target OS" "$target_os" config_summary "compiler" "$CC" config_summary "with thread" "$THREAD_MODEL" config_summary "with coroutine" "$coroutine_type" +config_summary "with shared GC" "$with_shared_gc" config_summary "enable shared libs" "$ENABLE_SHARED" config_summary "dynamic library ext" "$DLEXT" config_summary "CFLAGS" "$cflags" diff --git a/cont.c b/cont.c index c6a94e0709f369..f7a4863f2cd270 100644 --- a/cont.c +++ b/cont.c @@ -2382,7 +2382,7 @@ rb_fiber_initialize(int argc, VALUE* argv, VALUE self) VALUE rb_fiber_new_storage(rb_block_call_func_t func, VALUE obj, VALUE storage) { - return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 1, storage); + return fiber_initialize(fiber_alloc(rb_cFiber), rb_proc_new(func, obj), rb_fiber_pool_default(Qnil), 0, storage); } VALUE diff --git a/darray.h b/darray.h index 41d386e4563347..d24e3c3eb5f5c3 100644 --- a/darray.h +++ b/darray.h @@ -6,7 +6,6 @@ #include #include "internal/bits.h" -#include "internal/gc.h" // Type for a dynamic array. Use to declare a dynamic array. // It is a pointer so it fits in st_table nicely. Designed @@ -125,8 +124,7 @@ rb_darray_capa(const void *ary) static inline void rb_darray_free(void *ary) { - rb_darray_meta_t *meta = ary; - if (meta) ruby_sized_xfree(ary, meta->capa); + xfree(ary); } /* Internal function. Resizes the capacity of a darray. The new capacity must @@ -137,7 +135,7 @@ rb_darray_resize_capa_impl(void *ptr_to_ary, size_t new_capa, size_t header_size rb_darray_meta_t **ptr_to_ptr_to_meta = ptr_to_ary; rb_darray_meta_t *meta = *ptr_to_ptr_to_meta; - rb_darray_meta_t *new_ary = rb_xrealloc_mul_add(meta, new_capa, element_size, header_size); + rb_darray_meta_t *new_ary = xrealloc(meta, new_capa * element_size + header_size); if (meta == NULL) { /* First allocation. Initialize size. On subsequence allocations @@ -180,7 +178,7 @@ rb_darray_make_impl(void *ptr_to_ary, size_t array_size, size_t header_size, siz return; } - rb_darray_meta_t *meta = rb_xcalloc_mul_add(array_size, element_size, header_size); + rb_darray_meta_t *meta = xcalloc(array_size * element_size + header_size, 1); meta->size = array_size; meta->capa = array_size; diff --git a/dir.rb b/dir.rb index 632c49eee93bab..42b475ab2cd82c 100644 --- a/dir.rb +++ b/dir.rb @@ -408,6 +408,7 @@ def self.[](*args, base: nil, sort: true) # specifies that patterns may match short names if they exist; Windows only. # def self.glob(pattern, _flags = 0, flags: _flags, base: nil, sort: true) + Primitive.attr! :use_block Primitive.dir_s_glob(pattern, flags, base, sort) end end diff --git a/dln.c b/dln.c index db6ea5aa0fb03d..3e08728a6beb02 100644 --- a/dln.c +++ b/dln.c @@ -339,7 +339,7 @@ dln_disable_dlclose(void) #endif #if defined(_WIN32) || defined(USE_DLN_DLOPEN) -static void * +void * dln_open(const char *file) { static const char incompatible[] = "incompatible library version"; @@ -427,7 +427,7 @@ dln_open(const char *file) dln_loaderror("%s - %s", error, file); } -static void * +void * dln_sym(void *handle, const char *symbol) { #if defined(_WIN32) diff --git a/dln.h b/dln.h index d624bb6611d207..13eef58d9fbc88 100644 --- a/dln.h +++ b/dln.h @@ -25,6 +25,7 @@ RUBY_SYMBOL_EXPORT_BEGIN char *dln_find_exe_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); char *dln_find_file_r(const char*,const char*,char*,size_t DLN_FIND_EXTRA_ARG_DECL); void *dln_load(const char*); +void *dln_open(const char *file); void *dln_symbol(void*,const char*); RUBY_SYMBOL_EXPORT_END diff --git a/dln_find.c b/dln_find.c index 91c51394a95463..ae37b9dde42426 100644 --- a/dln_find.c +++ b/dln_find.c @@ -11,11 +11,9 @@ #ifdef RUBY_EXPORT #include "ruby/ruby.h" -#define dln_warning rb_warning -#define dln_warning_arg +#define dln_warning(...) rb_warning(__VA_ARGS__) #else -#define dln_warning fprintf -#define dln_warning_arg stderr, +#define dln_warning(...) fprintf(stderr, __VA_ARGS__) #endif #include "dln.h" @@ -109,7 +107,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, static const char pathname_too_long[] = "openpath: pathname too long (ignored)\n\ \tDirectory \"%.*s\"%s\n\tFile \"%.*s\"%s\n"; -#define PATHNAME_TOO_LONG() dln_warning(dln_warning_arg pathname_too_long, \ +#define PATHNAME_TOO_LONG() dln_warning(pathname_too_long, \ ((bp - fbuf) > 100 ? 100 : (int)(bp - fbuf)), fbuf, \ ((bp - fbuf) > 100 ? "..." : ""), \ (fnlen > 100 ? 100 : (int)fnlen), fname, \ @@ -120,8 +118,7 @@ dln_find_1(const char *fname, const char *path, char *fbuf, size_t size, RETURN_IF(!fname); fnlen = strlen(fname); if (fnlen >= size) { - dln_warning(dln_warning_arg - "openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", + dln_warning("openpath: pathname too long (ignored)\n\tFile \"%.*s\"%s\n", (fnlen > 100 ? 100 : (int)fnlen), fname, (fnlen > 100 ? "..." : "")); return NULL; diff --git a/dmydln.c b/dmydln.c index 35824ebec8b832..22f40e82eb886f 100644 --- a/dmydln.c +++ b/dmydln.c @@ -20,3 +20,13 @@ dln_symbol(void *handle, const char *symbol) UNREACHABLE_RETURN(NULL); } + +NORETURN(void *dln_open(const char*)); +void* +dln_open(const char *library) +{ + rb_loaderror("this executable file can't load extension libraries"); + + UNREACHABLE_RETURN(NULL); +} + diff --git a/doc/pty/README.expect.ja b/doc/pty/README.expect.ja index 7c0456f24fe504..a4eb6b01df24a0 100644 --- a/doc/pty/README.expect.ja +++ b/doc/pty/README.expect.ja @@ -1,21 +1,23 @@ - README for expect += README for expect by A. Ito, 28 October, 1998 - Expectライブラリは,tcl の expect パッケージと似たような機能を +Expectライブラリは,tcl の expect パッケージと似たような機能を IOクラスに追加します. - 追加されるメソッドの使い方は次の通りです. +追加されるメソッドの使い方は次の通りです. - IO#expect(pattern,timeout=9999999) +[IO#expect(pattern,timeout=9999999)] -pattern は String か Regexp のインスタンス,timeout は Fixnum -のインスタンスです.timeout は省略できます. - このメソッドがブロックなしで呼ばれた場合,まずレシーバである -IOオブジェクトから pattern にマッチするパターンが読みこまれる -まで待ちます.パターンが得られたら,そのパターンに関する配列を -返します.配列の最初の要素は,pattern にマッチするまでに読みこ -まれた内容の文字列です.2番目以降の要素は,pattern の正規表現 -の中にアンカーがあった場合に,そのアンカーにマッチする部分です. -もしタイムアウトが起きた場合は,このメソッドはnilを返します. - このメソッドがブロック付きで呼ばれた場合には,マッチした要素の -配列がブロック引数として渡され,ブロックが評価されます. + _pattern_ は String か Regexp のインスタンス,_timeout_ は Fixnum + のインスタンスです._timeout_ は省略できます. + + このメソッドがブロックなしで呼ばれた場合,まずレシーバである + IOオブジェクトから _pattern_ にマッチするパターンが読みこまれる + まで待ちます.パターンが得られたら,そのパターンに関する配列を + 返します.配列の最初の要素は,_pattern_ にマッチするまでに読みこ + まれた内容の文字列です.2番目以降の要素は,_pattern_ の正規表現 + の中にアンカーがあった場合に,そのアンカーにマッチする部分です. + もしタイムアウトが起きた場合は,このメソッドは +nil+ を返します. + + このメソッドがブロック付きで呼ばれた場合には,マッチした要素の + 配列がブロック引数として渡され,ブロックが評価されます. diff --git a/doc/pty/README.ja b/doc/pty/README.ja index 2d83ffa033e067..a26b4932ff904e 100644 --- a/doc/pty/README.ja +++ b/doc/pty/README.ja @@ -1,27 +1,26 @@ -pty 拡張モジュール version 0.3 by A.ito += pty 拡張モジュール version 0.3 by A.ito 1. はじめに -この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを -実行する機能を ruby に提供します. + この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを + 実行する機能を ruby に提供します. 2. インストール -次のようにしてインストールしてください. + 次のようにしてインストールしてください. -(1) ruby extconf.rb + 1. ruby extconf.rb + を実行すると Makefile が生成されます. - を実行すると Makefile が生成されます. - -(2) make; make install を実行してください. + 2. make; make install を実行してください. 3. 何ができるか -この拡張モジュールは,PTY というモジュールを定義します.その中 -には,次のようなモジュール関数が含まれています. + この拡張モジュールは,PTY というモジュールを定義します.その中 + には,次のようなモジュール関数が含まれています. - getpty(command) - spawn(command) + [PTY.getpty(command)] + [PTY.spawn(command)] この関数は,仮想ttyを確保し,指定されたコマンドをその仮想tty の向こうで実行し,配列を返します.戻り値は3つの要素からなる @@ -35,12 +34,7 @@ pty 拡張モジュール version 0.3 by A.ito のみ例外が発生します.子プロセスをモニターしているスレッドはブロッ クを抜けるときに終了します. - protect_signal - reset_signal - - 廃止予定です. - - PTY.open + [PTY.open] 仮想ttyを確保し,マスター側に対応するIOオブジェクトとスレーブ側に 対応するFileオブジェクトの配列を返します.ブロック付きで呼び出さ @@ -48,7 +42,7 @@ pty 拡張モジュール version 0.3 by A.ito クから返された結果を返します.また、このマスターIOとスレーブFile は、ブロックを抜けるときにクローズ済みでなければクローズされます. - PTY.check(pid[, raise=false]) + [PTY.check(pid[, raise=false])] pidで指定された子プロセスの状態をチェックし,実行中であればnilを 返します.終了しているか停止している場合、第二引数が偽であれば、 @@ -57,20 +51,20 @@ pty 拡張モジュール version 0.3 by A.ito 4. 利用について -伊藤彰則が著作権を保有します. + 伊藤彰則が著作権を保有します. -ソースプログラムまたはドキュメントに元の著作権表示が改変されずに -表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 -権者に無断で利用・配布・改変できます.利用目的は限定されていませ -ん. + ソースプログラムまたはドキュメントに元の著作権表示が改変されずに + 表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作 + 権者に無断で利用・配布・改変できます.利用目的は限定されていませ + ん. -このプログラムの利用・配布その他このプログラムに関係する行為によ -って生じたいかなる損害に対しても,作者は一切責任を負いません. + このプログラムの利用・配布その他このプログラムに関係する行為によ + って生じたいかなる損害に対しても,作者は一切責任を負いません. 5. バグ報告等 -バグレポートは歓迎します. + バグレポートは歓迎します. aito@ei5sun.yz.yamagata-u.ac.jp -まで電子メールでバグレポートをお送りください. + まで電子メールでバグレポートをお送りください. diff --git a/eval_intern.h b/eval_intern.h index fa02f229122d54..9a551120ff3ec9 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -321,16 +321,4 @@ rb_char_next(const char *p) # endif #endif -#if defined DOSISH || defined __CYGWIN__ -static inline void -translit_char(char *p, int from, int to) -{ - while (*p) { - if ((unsigned char)*p == from) - *p = to; - p = CharNext(p); - } -} -#endif - #endif /* RUBY_EVAL_INTERN_H */ diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index ba0c4286fd31d1..da3655cf4d7118 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -13,10 +13,13 @@ have_header("util.h") # OpenBSD openpty util = have_library("util", "openpty") end - if have_func("posix_openpt") or + openpt = have_func("posix_openpt") + if openpt + have_func("ptsname_r") or have_func("ptsname") + end + if openpt or (util or have_func("openpty")) or have_func("_getpty") or - have_func("ptsname") or have_func("ioctl") create_makefile('pty') end diff --git a/ext/pty/pty.c b/ext/pty/pty.c index 49d92d15d41d47..5ca55e41d67744 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -92,9 +92,13 @@ struct pty_info { static void getDevice(int*, int*, char [DEVICELEN], int); +static int start_new_session(char *errbuf, size_t errbuf_len); +static int obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len); +static int drop_privilige(char *errbuf, size_t errbuf_len); + struct child_info { int master, slave; - char *slavename; + const char *slavename; VALUE execarg_obj; struct rb_execarg *eargp; }; @@ -102,26 +106,42 @@ struct child_info { static int chfunc(void *data, char *errbuf, size_t errbuf_len) { - struct child_info *carg = data; + const struct child_info *carg = data; int master = carg->master; int slave = carg->slave; + const char *slavename = carg->slavename; + + if (start_new_session(errbuf, errbuf_len)) + return -1; + + if (obtain_ctty(master, slave, slavename, errbuf, errbuf_len)) + return -1; + + if (drop_privilige(errbuf, errbuf_len)) + return -1; + + return rb_exec_async_signal_safe(carg->eargp, errbuf, errbuf_len); +} #define ERROR_EXIT(str) do { \ strlcpy(errbuf, (str), errbuf_len); \ return -1; \ } while (0) - /* - * Set free from process group and controlling terminal - */ +/* + * Set free from process group and controlling terminal + */ +static int +start_new_session(char *errbuf, size_t errbuf_len) +{ #ifdef HAVE_SETSID (void) setsid(); #else /* HAS_SETSID */ # ifdef HAVE_SETPGRP -# ifdef SETGRP_VOID +# ifdef SETPGRP_VOID if (setpgrp() == -1) ERROR_EXIT("setpgrp()"); -# else /* SETGRP_VOID */ +# else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) ERROR_EXIT("setpgrp()"); { @@ -132,20 +152,25 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) ERROR_EXIT("ioctl(TIOCNOTTY)"); close(i); } -# endif /* SETGRP_VOID */ +# endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ + return 0; +} - /* - * obtain new controlling terminal - */ +/* + * obtain new controlling terminal + */ +static int +obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len) +{ #if defined(TIOCSCTTY) close(master); (void) ioctl(slave, TIOCSCTTY, (char *)0); /* errors ignored for sun */ #else close(slave); - slave = rb_cloexec_open(carg->slavename, O_RDWR, 0); + slave = rb_cloexec_open(slavename, O_RDWR, 0); if (slave < 0) { ERROR_EXIT("open: pty slave"); } @@ -156,13 +181,19 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) dup2(slave,1); dup2(slave,2); if (slave < 0 || slave > 2) (void)!close(slave); + return 0; +} + +static int +drop_privilige(char *errbuf, size_t errbuf_len) +{ #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID) if (seteuid(getuid())) ERROR_EXIT("seteuid()"); #endif + return 0; +} - return rb_exec_async_signal_safe(carg->eargp, errbuf, sizeof(errbuf_len)); #undef ERROR_EXIT -} static void establishShell(int argc, VALUE *argv, struct pty_info *info, @@ -225,7 +256,21 @@ establishShell(int argc, VALUE *argv, struct pty_info *info, RB_GC_GUARD(carg.execarg_obj); } -#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME) +#if (defined(HAVE_POSIX_OPENPT) || defined(HAVE_PTSNAME)) && !defined(HAVE_PTSNAME_R) +/* glibc only, not obsolete interface on Tru64 or HP-UX */ +static int +ptsname_r(int fd, char *buf, size_t buflen) +{ + extern char *ptsname(int); + char *name = ptsname(fd); + if (!name) return -1; + strlcpy(buf, name, buflen); + return 0; +} +# define HAVE_PTSNAME_R 1 +#endif + +#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) static int no_mesg(char *slavedevice, int nomesg) { @@ -289,7 +334,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; - if ((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -302,7 +348,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; grantpt_error: @@ -356,7 +401,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, char *slavedevice; void (*s)(); - extern char *ptsname(int); extern int unlockpt(int); extern int grantpt(int); @@ -374,7 +418,8 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; - if((slavedevice = ptsname(masterfd)) == NULL) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; if (no_mesg(slavedevice, nomesg) == -1) goto error; if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; rb_update_max_fd(slavefd); @@ -385,7 +430,6 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; error: diff --git a/ext/ripper/ripper_init.h b/ext/ripper/ripper_init.h index e9e7bc7e5fdbf4..664bb7bce368fc 100644 --- a/ext/ripper/ripper_init.h +++ b/ext/ripper/ripper_init.h @@ -2,8 +2,6 @@ #define RIPPER_INIT_H extern VALUE rb_ripper_none; -VALUE ripper_get_value(VALUE v); -ID ripper_get_id(VALUE v); PRINTF_ARGS(void ripper_compile_error(struct parser_params*, const char *fmt, ...), 2, 3); #endif /* RIPPER_INIT_H */ diff --git a/gc.c b/gc.c index 07dd1d37e138d0..6655d62fa89855 100644 --- a/gc.c +++ b/gc.c @@ -2416,6 +2416,55 @@ rb_gc_initial_stress_set(VALUE flag) initial_stress = flag; } +static void * Alloc_GC_impl(void); + +#if USE_SHARED_GC +# include "dln.h" +# define Alloc_GC rb_gc_functions->init + +void +ruby_external_gc_init() +{ + rb_gc_function_map_t *map = malloc(sizeof(rb_gc_function_map_t)); + rb_gc_functions = map; + + char *gc_so_path = getenv("RUBY_GC_LIBRARY_PATH"); + if (!gc_so_path) { + map->init = Alloc_GC_impl; + return; + } + + void *h = dln_open(gc_so_path); + if (!h) { + rb_bug( + "ruby_external_gc_init: Shared library %s cannot be opened.", + gc_so_path + ); + } + + void *gc_init_func = dln_symbol(h, "Init_GC"); + if (!gc_init_func) { + rb_bug( + "ruby_external_gc_init: Init_GC func not exported by library %s", + gc_so_path + ); + } + + map->init = gc_init_func; +} +#else +# define Alloc_GC Alloc_GC_impl +#endif + +rb_objspace_t * +rb_objspace_alloc(void) +{ +#if USE_SHARED_GC + ruby_external_gc_init(); +#endif + return (rb_objspace_t *)Alloc_GC(); +} + static void free_stack_chunks(mark_stack_t *); static void mark_stack_free_cache(mark_stack_t *); static void heap_page_free(rb_objspace_t *objspace, struct heap_page *page, bool global_pages_locked); @@ -4895,8 +4944,8 @@ obj_free(rb_objspace_t *objspace, VALUE obj) } -#define OBJ_ID_INCREMENT (sizeof(RVALUE) / 2) -#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT * 2) +#define OBJ_ID_INCREMENT (sizeof(RVALUE)) +#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT) static int object_id_cmp(st_data_t x, st_data_t y) @@ -5026,8 +5075,8 @@ objspace_setup(rb_objspace_t *objspace, rb_ractor_t *ractor) return objspace; } -rb_objspace_t * -rb_objspace_alloc(void) +static void * +Alloc_GC_impl(void) { rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); ruby_current_vm_ptr->objspace = objspace; @@ -5426,6 +5475,7 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void objspace_each_exec(protected, &each_obj_data); } +#if GC_CAN_COMPILE_COMPACTION static void objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void *data, bool protected) { @@ -5440,6 +5490,7 @@ objspace_each_pages(rb_objspace_t *objspace, each_page_callback *callback, void } objspace_each_exec(protected, &each_obj_data); } +#endif struct os_each_struct { size_t num; @@ -6403,29 +6454,32 @@ id2ref(VALUE objid) #define NUM2PTR(x) NUM2ULL(x) #endif rb_objspace_t *objspace = &rb_objspace; - VALUE ptr; - VALUE orig; - void *p0; objid = rb_to_int(objid); if (FIXNUM_P(objid) || rb_big_size(objid) <= SIZEOF_VOIDP) { - ptr = NUM2PTR(objid); - if (ptr == Qtrue) return Qtrue; - if (ptr == Qfalse) return Qfalse; - if (NIL_P(ptr)) return Qnil; - if (FIXNUM_P(ptr)) return (VALUE)ptr; - if (FLONUM_P(ptr)) return (VALUE)ptr; + VALUE ptr = NUM2PTR(objid); + if (SPECIAL_CONST_P(ptr)) { + if (ptr == Qtrue) return Qtrue; + if (ptr == Qfalse) return Qfalse; + if (NIL_P(ptr)) return Qnil; + if (FIXNUM_P(ptr)) return ptr; + if (FLONUM_P(ptr)) return ptr; + + if (SYMBOL_P(ptr)) { + // Check that the symbol is valid + if (rb_static_id_valid_p(SYM2ID(ptr))) { + return ptr; + } + else { + rb_raise(rb_eRangeError, "%p is not symbol id value", (void *)ptr); + } + } - ptr = obj_id_to_ref(objid); - if ((ptr % sizeof(RVALUE)) == (4 << 2)) { - ID symid = ptr / sizeof(RVALUE); - p0 = (void *)ptr; - if (!rb_static_id_valid_p(symid)) - rb_raise(rb_eRangeError, "%p is not symbol id value", p0); - return ID2SYM(symid); + rb_raise(rb_eRangeError, "%+"PRIsVALUE" is not id value", rb_int2str(objid, 10)); } } + VALUE orig; if (!UNDEF_P(orig = rb_gc_id2ref_obj_tbl(objid)) && is_live_object(objspace, orig)) { if (GET_OBJSPACE_OF_VALUE(orig) == objspace || rb_ractor_shareable_p(orig)) { @@ -6459,19 +6513,13 @@ os_id2ref(VALUE os, VALUE objid) static VALUE rb_find_object_id(VALUE obj, VALUE (*get_heap_object_id)(VALUE)) { - if (STATIC_SYM_P(obj)) { - return (SYM2ID(obj) * sizeof(RVALUE) + (4 << 2)) | FIXNUM_FLAG; - } - else if (FLONUM_P(obj)) { + if (SPECIAL_CONST_P(obj)) { #if SIZEOF_LONG == SIZEOF_VOIDP return LONG2NUM((SIGNED_VALUE)obj); #else return LL2NUM((SIGNED_VALUE)obj); #endif } - else if (SPECIAL_CONST_P(obj)) { - return LONG2NUM((SIGNED_VALUE)obj); - } return get_heap_object_id(obj); } @@ -12389,15 +12437,15 @@ gc_set_candidate_object_i(void *vstart, void *vend, size_t stride, void *data) for (; v != (VALUE)vend; v += stride) { asan_unpoisoning_object(v) { switch (BUILTIN_TYPE(v)) { - case T_NONE: - case T_ZOMBIE: + case T_NONE: + case T_ZOMBIE: break; - case T_STRING: + case T_STRING: // precompute the string coderange. This both save time for when it will be // eventually needed, and avoid mutating heap pages after a potential fork. rb_enc_str_coderange(v); // fall through - default: + default: if (!RVALUE_OLD_P(v) && !RVALUE_WB_UNPROTECTED(v)) { RVALUE_AGE_SET_CANDIDATE(objspace, v); } @@ -13623,7 +13671,7 @@ gc_verify_compaction_references(rb_execution_context_t *ec, VALUE self, VALUE do /* Find out which pool has the most pages */ size_t max_existing_pages = 0; - for(int i = 0; i < SIZE_POOL_COUNT; i++) { + for (int i = 0; i < SIZE_POOL_COUNT; i++) { rb_size_pool_t *size_pool = &size_pools[i]; rb_heap_t *heap = SIZE_POOL_EDEN_HEAP(size_pool); max_existing_pages = MAX(max_existing_pages, heap->total_pages); diff --git a/gems/bundled_gems b/gems/bundled_gems index 842fbe0ce35e50..ad979c1c5c4b1e 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 5.22.3 https://github.com/minitest/minitest 287b35d60c8e19c11ba090efc6eeb225325a8520 +minitest 5.22.3 https://github.com/minitest/minitest ea9caafc0754b1d6236a490d59e624b53209734a power_assert 2.0.3 https://github.com/ruby/power_assert 84e85124c5014a139af39161d484156cfe87a9ed rake 13.2.1 https://github.com/ruby/rake test-unit 3.6.2 https://github.com/test-unit/test-unit diff --git a/imemo.c b/imemo.c index 9304f7b4cce9e0..9c4e053f0095fc 100644 --- a/imemo.c +++ b/imemo.c @@ -294,7 +294,7 @@ rb_imemo_mark_and_move(VALUE obj, bool reference_updating) { switch (imemo_type(obj)) { case imemo_ast: - rb_ast_mark_and_move((rb_ast_t *)obj, reference_updating); + // TODO: Make AST decoupled from IMEMO break; case imemo_callcache: { diff --git a/include/ruby/assert.h b/include/ruby/assert.h index ceab090427924a..e9edd9e640a186 100644 --- a/include/ruby/assert.h +++ b/include/ruby/assert.h @@ -281,6 +281,17 @@ RBIMPL_WARNING_IGNORED(-Wgnu-zero-variadic-macro-arguments) # define RUBY_ASSERT_WHEN(cond, expr) RUBY_ASSERT_MESG_WHEN((cond), (expr), #expr) #endif +/** + * A variant of #RUBY_ASSERT that asserts when either #RUBY_DEBUG or built-in + * type of `obj` is `type`. + * + * @param obj Object to check its built-in typue. + * @param type Built-in type constant, T_ARRAY, T_STRING, etc. + */ +#define RUBY_ASSERT_BUILTIN_TYPE(obj, type) \ + RUBY_ASSERT(RB_TYPE_P(obj, type), \ + "Actual type is %s", rb_builtin_type_name(BUILTIN_TYPE(obj))) + /** * This is either #RUBY_ASSERT or #RBIMPL_ASSUME, depending on #RUBY_DEBUG. * diff --git a/include/ruby/internal/gc.h b/include/ruby/internal/gc.h index 4ae4e405346d0d..fa84b20b848ed9 100644 --- a/include/ruby/internal/gc.h +++ b/include/ruby/internal/gc.h @@ -837,4 +837,7 @@ rb_obj_write( return a; } +RBIMPL_ATTR_DEPRECATED(("Will be removed soon")) +static inline void rb_gc_force_recycle(VALUE obj){} + #endif /* RBIMPL_GC_H */ diff --git a/internal/gc.h b/internal/gc.h index 5f3597d2958576..2a317af867a5c7 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -16,6 +16,10 @@ #include "ruby/ruby.h" /* for rb_event_flag_t */ #include "vm_core.h" /* for GET_EC() */ +#ifndef USE_SHARED_GC +# define USE_SHARED_GC 0 +#endif + #if defined(__x86_64__) && !defined(_ILP32) && defined(__GNUC__) #define SET_MACHINE_STACK_END(p) __asm__ __volatile__ ("movq\t%%rsp, %0" : "=r" (*(p))) #elif defined(__i386) && defined(__GNUC__) diff --git a/internal/parse.h b/internal/parse.h index f4f2f0c0f521ca..80328686c1f8c4 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -51,7 +51,7 @@ size_t rb_ruby_parser_memsize(const void *ptr); void rb_ruby_parser_set_options(rb_parser_t *p, int print, int loop, int chomp, int split); rb_parser_t *rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, int main); -void rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines_array); +void rb_ruby_parser_set_script_lines(rb_parser_t *p); void rb_ruby_parser_error_tolerant(rb_parser_t *p); rb_ast_t* rb_ruby_parser_compile_file_path(rb_parser_t *p, VALUE fname, VALUE file, int start); void rb_ruby_parser_keep_tokens(rb_parser_t *p); @@ -65,11 +65,8 @@ int rb_ruby_parser_end_seen_p(rb_parser_t *p); int rb_ruby_parser_set_yydebug(rb_parser_t *p, int flag); rb_parser_string_t *rb_str_to_parser_string(rb_parser_t *p, VALUE str); -void rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc); -void rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash); int rb_parser_dvar_defined_ref(struct parser_params*, ID, ID**); ID rb_parser_internal_id(struct parser_params*); -VALUE rb_parser_node_case_when_optimizable_literal(struct parser_params *p, const NODE *const node); int rb_parser_reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); int rb_reg_named_capture_assign_iter_impl(struct parser_params *p, const char *s, long len, rb_encoding *enc, NODE **succ_block, const rb_code_location_t *loc); int rb_parser_local_defined(struct parser_params *p, ID id, const struct rb_iseq_struct *iseq); diff --git a/internal/ruby_parser.h b/internal/ruby_parser.h index 7dc6a46d4f42ac..f0cec8666886f9 100644 --- a/internal/ruby_parser.h +++ b/internal/ruby_parser.h @@ -19,6 +19,7 @@ VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); VALUE rb_parser_new(void); rb_ast_t *rb_parser_compile_string_path(VALUE vparser, VALUE fname, VALUE src, int line); VALUE rb_str_new_parser_string(rb_parser_string_t *str); +VALUE rb_str_new_mutable_parser_string(rb_parser_string_t *str); VALUE rb_node_str_string_val(const NODE *); VALUE rb_node_sym_string_val(const NODE *); @@ -38,9 +39,11 @@ RUBY_SYMBOL_EXPORT_END VALUE rb_parser_end_seen_p(VALUE); VALUE rb_parser_encoding(VALUE); VALUE rb_parser_set_yydebug(VALUE, VALUE); +VALUE rb_parser_build_script_lines_from(rb_parser_ary_t *script_lines); +void rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *script_lines); void rb_parser_set_options(VALUE, int, int, int, int); void *rb_parser_load_file(VALUE parser, VALUE name); -void rb_parser_set_script_lines(VALUE vparser, VALUE lines_array); +void rb_parser_set_script_lines(VALUE vparser); void rb_parser_error_tolerant(VALUE vparser); void rb_parser_keep_tokens(VALUE vparser); diff --git a/iseq.c b/iseq.c index c0943d19db6b68..7b3fc1a3743d7e 100644 --- a/iseq.c +++ b/iseq.c @@ -540,6 +540,11 @@ iseq_location_setup(rb_iseq_t *iseq, VALUE name, VALUE path, VALUE realpath, int RB_OBJ_WRITE(iseq, &loc->label, name); RB_OBJ_WRITE(iseq, &loc->base_label, name); loc->first_lineno = first_lineno; + + if (ISEQ_BODY(iseq)->local_iseq == iseq && strcmp(RSTRING_PTR(name), "initialize") == 0) { + ISEQ_BODY(iseq)->param.flags.use_block = 1; + } + if (code_location) { loc->node_id = node_id; loc->code_location = *code_location; @@ -836,20 +841,21 @@ rb_iseq_new(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, const rb_iseq_t *parent, enum rb_iseq_type type) { return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, - 0, type, &COMPILE_OPTION_DEFAULT); + 0, type, &COMPILE_OPTION_DEFAULT, + Qnil); } static int ast_line_count(const rb_ast_body_t *ast) { - if (ast->script_lines == Qfalse) { + if (ast->script_lines == NULL) { // this occurs when failed to parse the source code with a syntax error return 0; } - if (RB_TYPE_P(ast->script_lines, T_ARRAY)){ - return (int)RARRAY_LEN(ast->script_lines); + if (!FIXNUM_P((VALUE)ast->script_lines)) { + return (int)ast->script_lines->len; } - return FIX2INT(ast->script_lines); + return FIX2INT((VALUE)ast->script_lines); } static VALUE @@ -885,7 +891,8 @@ rb_iseq_new_top(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath iseq_new_setup_coverage(path, ast, 0); return rb_iseq_new_with_opt(ast, name, path, realpath, 0, parent, 0, - ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT); + ISEQ_TYPE_TOP, &COMPILE_OPTION_DEFAULT, + Qnil); } /** @@ -907,7 +914,8 @@ rb_iseq_new_main(const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_ return rb_iseq_new_with_opt(ast, rb_fstring_lit("
"), path, realpath, 0, - parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE); + parent, 0, ISEQ_TYPE_MAIN, opt ? &COMPILE_OPTION_DEFAULT : &COMPILE_OPTION_FALSE, + Qnil); } /** @@ -935,7 +943,8 @@ rb_iseq_new_eval(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpat } return rb_iseq_new_with_opt(ast, name, path, realpath, first_lineno, - parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT); + parent, isolated_depth, ISEQ_TYPE_EVAL, &COMPILE_OPTION_DEFAULT, + Qnil); } rb_iseq_t * @@ -963,7 +972,8 @@ iseq_translate(rb_iseq_t *iseq) rb_iseq_t * rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth, - enum rb_iseq_type type, const rb_compile_option_t *option) + enum rb_iseq_type type, const rb_compile_option_t *option, + VALUE script_lines) { const NODE *node = ast ? ast->root : 0; /* TODO: argument check */ @@ -976,10 +986,11 @@ rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE rea option = set_compile_option_from_ast(&new_opt, ast); } - VALUE script_lines = Qnil; - - if (ast && !FIXNUM_P(ast->script_lines) && ast->script_lines) { - script_lines = ast->script_lines; + if (!NIL_P(script_lines)) { + // noop + } + else if (ast && !FIXNUM_P((VALUE)ast->script_lines) && ast->script_lines) { + script_lines = rb_parser_build_script_lines_from(ast->script_lines); } else if (parent) { script_lines = ISEQ_BODY(parent)->variable.script_lines; @@ -1013,6 +1024,7 @@ pm_iseq_new_with_opt(pm_scope_node_t *node, VALUE name, VALUE path, VALUE realpa { rb_iseq_t *iseq = iseq_alloc(); ISEQ_BODY(iseq)->prism = true; + ISEQ_BODY(iseq)->param.flags.use_block = true; // unused block warning is not supported yet if (!option) option = &COMPILE_OPTION_DEFAULT; @@ -1221,7 +1233,7 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V const rb_iseq_t *outer_scope = rb_iseq_new(NULL, name, name, Qnil, 0, ISEQ_TYPE_TOP); VALUE outer_scope_v = (VALUE)outer_scope; rb_parser_set_context(parser, outer_scope, FALSE); - rb_parser_set_script_lines(parser, RBOOL(ruby_vm_keep_script_lines)); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); RB_GC_GUARD(outer_scope_v); ast = (*parse)(parser, file, src, ln); } @@ -1232,7 +1244,8 @@ rb_iseq_compile_with_option(VALUE src, VALUE file, VALUE realpath, VALUE line, V } else { iseq = rb_iseq_new_with_opt(&ast->body, name, file, realpath, ln, - NULL, 0, ISEQ_TYPE_TOP, &option); + NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil); rb_ast_dispose(ast); } @@ -1625,7 +1638,8 @@ iseqw_s_compile_file(int argc, VALUE *argv, VALUE self) ret = iseqw_new(rb_iseq_new_with_opt(&ast->body, rb_fstring_lit("
"), file, rb_realpath_internal(Qnil, file, 1), - 1, NULL, 0, ISEQ_TYPE_TOP, &option)); + 1, NULL, 0, ISEQ_TYPE_TOP, &option, + Qnil)); rb_ast_dispose(ast); rb_vm_pop_frame(ec); diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index f3e1708e238a61..e61c1ad231b297 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -108,7 +108,7 @@ def self.warning?(name, specs: nil) _t, path = $:.resolve_feature_path(feature) if gem = find_gem(path) return if specs.include?(gem) - caller = caller_locations(3, 3).find {|c| c&.absolute_path} + caller = caller_locations(3, 3)&.find {|c| c&.absolute_path} return if find_gem(caller&.absolute_path) elsif SINCE[name] && !path gem = true diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 36405367620b7b..8db725bbde9f99 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -347,6 +347,7 @@ def binstubs(*gems) method_option "github", type: :string method_option "branch", type: :string method_option "ref", type: :string + method_option "glob", type: :string, banner: "The location of a dependency's .gemspec, expanded within Ruby (single quotes recommended)" method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it" method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem" method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem" diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 77d7a00362b931..2a4f72fe55a305 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -7,7 +7,7 @@ module Bundler class Dependency < Gem::Dependency attr_reader :autorequire - attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref + attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :glob ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..34).to_a).freeze PLATFORM_MAP = { @@ -39,6 +39,7 @@ def initialize(name, version, options = {}, &blk) @github = options["github"] @branch = options["branch"] @ref = options["ref"] + @glob = options["glob"] @platforms = Array(options["platforms"]) @env = options["env"] @should_include = options.fetch("should_include", true) diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index cf561c2ee49de3..879b481339281e 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -120,9 +120,10 @@ def build_gem_lines(conservative_versioning) github = ", :github => \"#{d.github}\"" unless d.github.nil? branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil? ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil? + glob = ", :glob => \"#{d.glob}\"" unless d.glob.nil? require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil? - %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{require_path}) + %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path}) end.join("\n") end diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 6771f3f1537698..4f60862bb47f64 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -77,7 +77,7 @@ def install_git(names, version, options) def install_path(names, version, path) source_list = SourceList.new - source = source_list.add_path_source({ "path" => path }) + source = source_list.add_path_source({ "path" => path, "root_path" => SharedHelpers.pwd }) install_all_sources(names, version, source_list, source) end diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb index 1b60724b5e4ea3..58a8fa7426b448 100644 --- a/lib/bundler/plugin/installer/path.rb +++ b/lib/bundler/plugin/installer/path.rb @@ -5,7 +5,7 @@ module Plugin class Installer class Path < Bundler::Source::Path def root - Plugin.root + SharedHelpers.in_bundle? ? Bundler.root : Plugin.root end def generate_bin(spec, disable_extensions = false) diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a6734455321f64..c1073ecd2bc4e1 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -471,6 +471,20 @@ def netmask _to_string(@mask_addr) end + # Returns the wildcard mask in string format e.g. 0.0.255.255 + def wildcard_mask + case @family + when Socket::AF_INET + mask = IN4MASK ^ @mask_addr + when Socket::AF_INET6 + mask = IN6MASK ^ @mask_addr + else + raise AddressFamilyError, "unsupported address family" + end + + _to_string(mask) + end + # Returns the IPv6 zone identifier, if present. # Raises InvalidAddressError if not an IPv6 address. def zone_id diff --git a/lib/irb.rb b/lib/irb.rb index 0855c59de005d1..ab50c797c7e9fa 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -10,7 +10,7 @@ require_relative "irb/init" require_relative "irb/context" -require_relative "irb/command" +require_relative "irb/default_commands" require_relative "irb/ruby-lex" require_relative "irb/statement" @@ -929,7 +929,7 @@ class Irb # Creates a new irb session def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) - @context.workspace.load_commands_to_main + @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @scanner = RubyLex.new @line_no = 1 @@ -950,7 +950,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.replace_workspace(workspace) - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: @@ -1028,7 +1028,7 @@ def eval_input return statement.code end - @context.evaluate(statement.evaluable_code, line_no) + @context.evaluate(statement, line_no) if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1084,10 +1084,7 @@ def readmultiline end code << line - - # Accept any single-line input for symbol aliases or commands that transform - # args - return code if single_line_command?(code) + return code if command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) return code if terminated @@ -1114,23 +1111,36 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - command_or_alias, arg = code.split(/\s/, 2) - # Transform a non-identifier alias (@, $) or keywords (next, break) - command_name = @context.command_aliases[command_or_alias.to_sym] - command = command_name || command_or_alias - command_class = ExtendCommandBundle.load_command(command) - - if command_class - Statement::Command.new(code, command, arg, command_class) + if (command, arg = parse_command(code)) + command_class = ExtendCommandBundle.load_command(command) + Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) Statement::Expression.new(code, is_assignment_expression) end end - def single_line_command?(code) - command = code.split(/\s/, 2).first - @context.symbol_alias?(command) || @context.transform_args?(command) + def parse_command(code) + command_name, arg = code.strip.split(/\s+/, 2) + return unless code.lines.size == 1 && command_name + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = @context.command_aliases[command]) + return [alias_name, arg] + end + + # Check visibility + public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false + if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) + [command, arg] + end + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1148,9 +1158,7 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform - # args - next true if single_line_command?(code) + next true if command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) terminated diff --git a/lib/irb/command.rb b/lib/irb/command.rb index ef930492370c7a..19fde56356c47d 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -7,317 +7,23 @@ require_relative "command/base" module IRB # :nodoc: - module Command; end - ExtendCommand = Command + module Command + @commands = {} - # Installs the default irb extensions command bundle. - module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: + class << self + attr_reader :commands - # See #install_alias_method. - NO_OVERRIDE = 0 - # See #install_alias_method. - OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. - OVERRIDE_ALL = 0x02 - - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - def irb_context - IRB.CurrentContext - end - - @ALIASES = [ - [:context, :irb_context, NO_OVERRIDE], - [:conf, :irb_context, NO_OVERRIDE], - ] - - - @EXTEND_COMMANDS = [ - [ - :irb_exit, :Exit, "command/exit", - [:exit, OVERRIDE_PRIVATE_ONLY], - [:quit, OVERRIDE_PRIVATE_ONLY], - [:irb_quit, OVERRIDE_PRIVATE_ONLY], - ], - [ - :irb_exit!, :ForceExit, "command/force_exit", - [:exit!, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_current_working_workspace, :CurrentWorkingWorkspace, "command/chws", - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], - [:irb_print_working_workspace, OVERRIDE_ALL], - [:irb_cwws, OVERRIDE_ALL], - [:irb_pwws, OVERRIDE_ALL], - [:irb_current_working_binding, OVERRIDE_ALL], - [:irb_print_working_binding, OVERRIDE_ALL], - [:irb_cwb, OVERRIDE_ALL], - [:irb_pwb, OVERRIDE_ALL], - ], - [ - :irb_change_workspace, :ChangeWorkspace, "command/chws", - [:chws, NO_OVERRIDE], - [:cws, NO_OVERRIDE], - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], - [:irb_change_binding, OVERRIDE_ALL], - [:irb_cb, OVERRIDE_ALL], - [:cb, NO_OVERRIDE], - ], - - [ - :irb_workspaces, :Workspaces, "command/pushws", - [:workspaces, NO_OVERRIDE], - [:irb_bindings, OVERRIDE_ALL], - [:bindings, NO_OVERRIDE], - ], - [ - :irb_push_workspace, :PushWorkspace, "command/pushws", - [:pushws, NO_OVERRIDE], - [:irb_pushws, OVERRIDE_ALL], - [:irb_push_binding, OVERRIDE_ALL], - [:irb_pushb, OVERRIDE_ALL], - [:pushb, NO_OVERRIDE], - ], - [ - :irb_pop_workspace, :PopWorkspace, "command/pushws", - [:popws, NO_OVERRIDE], - [:irb_popws, OVERRIDE_ALL], - [:irb_pop_binding, OVERRIDE_ALL], - [:irb_popb, OVERRIDE_ALL], - [:popb, NO_OVERRIDE], - ], - - [ - :irb_load, :Load, "command/load"], - [ - :irb_require, :Require, "command/load"], - [ - :irb_source, :Source, "command/load", - [:source, NO_OVERRIDE], - ], - - [ - :irb, :IrbCommand, "command/subirb"], - [ - :irb_jobs, :Jobs, "command/subirb", - [:jobs, NO_OVERRIDE], - ], - [ - :irb_fg, :Foreground, "command/subirb", - [:fg, NO_OVERRIDE], - ], - [ - :irb_kill, :Kill, "command/subirb", - [:kill, OVERRIDE_PRIVATE_ONLY], - ], - - [ - :irb_debug, :Debug, "command/debug", - [:debug, NO_OVERRIDE], - ], - [ - :irb_edit, :Edit, "command/edit", - [:edit, NO_OVERRIDE], - ], - [ - :irb_break, :Break, "command/break", - ], - [ - :irb_catch, :Catch, "command/catch", - ], - [ - :irb_next, :Next, "command/next" - ], - [ - :irb_delete, :Delete, "command/delete", - [:delete, NO_OVERRIDE], - ], - [ - :irb_step, :Step, "command/step", - [:step, NO_OVERRIDE], - ], - [ - :irb_continue, :Continue, "command/continue", - [:continue, NO_OVERRIDE], - ], - [ - :irb_finish, :Finish, "command/finish", - [:finish, NO_OVERRIDE], - ], - [ - :irb_backtrace, :Backtrace, "command/backtrace", - [:backtrace, NO_OVERRIDE], - [:bt, NO_OVERRIDE], - ], - [ - :irb_debug_info, :Info, "command/info", - [:info, NO_OVERRIDE], - ], - - [ - :irb_help, :Help, "command/help", - [:help, NO_OVERRIDE], - [:show_cmds, NO_OVERRIDE], - ], - - [ - :irb_show_doc, :ShowDoc, "command/show_doc", - [:show_doc, NO_OVERRIDE], - ], - - [ - :irb_info, :IrbInfo, "command/irb_info" - ], - - [ - :irb_ls, :Ls, "command/ls", - [:ls, NO_OVERRIDE], - ], - - [ - :irb_measure, :Measure, "command/measure", - [:measure, NO_OVERRIDE], - ], - - [ - :irb_show_source, :ShowSource, "command/show_source", - [:show_source, NO_OVERRIDE], - ], - [ - :irb_whereami, :Whereami, "command/whereami", - [:whereami, NO_OVERRIDE], - ], - [ - :irb_history, :History, "command/history", - [:history, NO_OVERRIDE], - [:hist, NO_OVERRIDE], - ], - - [ - :irb_disable_irb, :DisableIrb, "command/disable_irb", - [:disable_irb, NO_OVERRIDE], - ], - ] - - - @@commands = [] - - def self.all_commands_info - return @@commands unless @@commands.empty? - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name + # Registers a command with the given name. + # Aliasing is intentionally not supported at the moment. + def register(name, command_class) + @commands[name] = [command_class, []] end - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - - klass = Command.const_get(cmd_class, false) - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[cmd_name] - aliases += additional_aliases - end - - display_name = aliases.shift || cmd_name - @@commands << { display_name: display_name, description: klass.description, category: klass.category } + # This API is for IRB's internal use only and may change at any time. + # Please do NOT use it. + def _register_with_aliases(name, command_class, *aliases) + @commands[name] = [command_class, aliases] end - - @@commands end - - # Convert a command name to its implementation class if such command exists - def self.load_command(command) - command = command.to_sym - @EXTEND_COMMANDS.each do |cmd_name, cmd_class, load_file, *aliases| - next if cmd_name != command && aliases.all? { |alias_name, _| alias_name != command } - - if !defined?(Command) || !Command.const_defined?(cmd_class, false) - require_relative load_file - end - return Command.const_get(cmd_class, false) - end - nil - end - - # Installs the default irb commands. - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +cmd_name+ on the given +cmd_class+ Class. - # - # Will also define any given +aliases+ for the method. - # - # The optional +load_file+ parameter will be required within the method - # definition. - def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - case cmd_class - when Symbol - cmd_class = cmd_class.id2name - when String - when Class - cmd_class = cmd_class.name - end - - line = __LINE__; eval %[ - def #{cmd_name}(*opts, **kwargs, &b) - Kernel.require_relative "#{load_file}" - ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line - - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end - end - - # Installs alias methods for the default irb commands, see - # ::install_extend_commands. - def install_alias_method(to, from, override = NO_OVERRIDE) - to = to.id2name unless to.kind_of?(String) - from = from.id2name unless from.kind_of?(String) - - if override == OVERRIDE_ALL or - (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or - (override == NO_OVERRIDE) && !respond_to?(to, true) - target = self - (class << self; self; end).instance_eval{ - if target.respond_to?(to, true) && - !target.respond_to?(EXCB.irb_original_method_name(to), true) - alias_method(EXCB.irb_original_method_name(to), to) - end - alias_method to, from - } - else - Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n" - end - end - - def self.irb_original_method_name(method_name) # :nodoc: - "irb_" + method_name + "_org" - end - - # Installs alias methods for the default irb commands on the given object - # using #install_alias_method. - def self.extend_object(obj) - unless (class << obj; ancestors; end).include?(EXCB) - super - for ali, com, flg in @ALIASES - obj.install_alias_method(ali, com, flg) - end - end - end - - install_extend_commands end end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 47e5e60724029d..687bb075ace9fc 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -7,12 +7,8 @@ module IRB module Command class Backtrace < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["backtrace", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}") end end end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 3ce4f4d6c19922..ff74b5fb35438b 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,6 +10,10 @@ module IRB module Command class CommandArgumentError < StandardError; end + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + class Base class << self def category(category = nil) @@ -29,19 +33,13 @@ def help_message(help_message = nil) private - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end - def highlight(text) Color.colorize(text, [:BOLD, :BLUE]) end end - def self.execute(irb_context, *opts, **kwargs, &block) - command = new(irb_context) - command.execute(*opts, **kwargs, &block) + def self.execute(irb_context, arg) + new(irb_context).execute(arg) rescue CommandArgumentError => e puts e.message end @@ -52,7 +50,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # Use throw and catch to handle arg that includes `;` + # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] + catch(:EXTRACT_RUBY_ARGS) do + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" + end || [[], {}] + end + + def execute(arg) #nop end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index fa200f2d7811dc..a8f81fe665077b 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -7,12 +7,8 @@ module IRB module Command class Break < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(args = nil) - super(pre_cmds: "break #{args}") + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}") end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 6b2edff5e5e6f9..529dcbca5aed09 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -7,12 +7,8 @@ module IRB module Command class Catch < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["catch", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}") end end end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e1047686c520cf..e0a406885f87c1 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -14,7 +14,7 @@ class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute(_arg) irb_context.main end end @@ -23,8 +23,13 @@ class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." - def execute(*obj) - irb_context.change_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end irb_context.main end end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 00000000000000..b4fc807343c0f5 --- /dev/null +++ b/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 8b6ffc860ce6c9..0daa029b154042 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -7,8 +7,8 @@ module IRB module Command class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}") end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index bdf91766bcc335..f9aca0a672b4c6 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -13,7 +13,14 @@ class Debug < Base binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(pre_cmds: nil, do_cmds: nil) + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) + pre_cmds = pre_cmds&.rstrip + do_cmds = do_cmds&.rstrip + if irb_context.with_debugger # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. if cmd = pre_cmds || do_cmds diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index a36b4577b0c23e..2a57a4a3de06d2 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -7,8 +7,8 @@ module IRB module Command class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}") end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index ab8c62663ea250..3c4a54e5e2fa64 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -1,5 +1,6 @@ require 'shellwords' +require_relative "../color" require_relative "../source_finder" module IRB @@ -26,19 +27,9 @@ class Edit < Base edit Foo#bar HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.nil? || args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(*args) - path = args.first + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) if path.nil? path = @irb_context.irb_path diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index 05501819e2d0cd..3311a0e6e9e405 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -7,8 +7,8 @@ module IRB module Command class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}") end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbfed843..c9f16e05bc50b4 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,27 +6,16 @@ class Help < Base category "Help" description "List all available commands. Use `help ` to get information about a specific command." - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(command_name = nil) + def execute(command_name) content = - if command_name + if command_name.empty? + help_message + else if command_class = ExtendCommandBundle.load_command(command_name) command_class.help_message || command_class.description else "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" end - else - help_message end Pager.page_content(content) end diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index a47a8795ddc3f0..90f87f91023554 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -12,14 +12,12 @@ class History < Base category "IRB" description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - def self.transform_args(args) - match = args&.match(/(-g|-G)\s+(?.+)\s*\n\z/) - return unless match + def execute(arg) - "grep: #{Regexp.new(match[:grep]).inspect}" - end + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + end - def execute(grep: nil) formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| next if grep && !input.match?(grep) diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index a67be3eb858658..d08ce00a320aab 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -7,12 +7,8 @@ module IRB module Command class Info < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["info", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}") end end end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index cc93fdcbd5586c..6d868de94c8c76 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -8,7 +8,7 @@ class IrbInfo < Base category "IRB" description "Show information about IRB." - def execute + def execute(_arg) str = "Ruby version: #{RUBY_VERSION}\n" str += "IRB version: #{IRB.version}\n" str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 9e89a7b7f35751..33e327f4a93838 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -21,7 +21,12 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil, priv = nil) raise_cmd_argument_error unless file_name irb_load(file_name, priv) end @@ -30,7 +35,13 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") @@ -63,7 +74,12 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name source_file(file_name) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 6b6136c2feb78e..f6b19648640489 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -20,27 +20,35 @@ class Ls < Base -g [query] Filter the output with a query. HELP_MESSAGE - def self.transform_args(args) - if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) - args = match[:args] - "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:target].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:target]) + end + grep = Regexp.new(match[:grep]) else - args + args, kwargs = ruby_args(arg) + use_main = args.empty? + obj = args.first + grep = kwargs[:grep] + end + + if use_main + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables end - end - def execute(*arg, grep: nil) o = Output.new(grep: grep) - obj = arg.empty? ? irb_context.workspace.main : arg.first - locals = arg.empty? ? irb_context.workspace.binding.local_variables : [] klass = (obj.class == Class || obj.class == Module ? obj : obj.class) o.dump("constants", obj.constants) if obj.respond_to?(:constants) dump_methods(o, klass, obj) o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) - o.dump("locals", locals) + o.dump("locals", locals) if locals o.print_result end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index ee7927b010bdc0..70dc69cdec5ef5 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -10,15 +10,19 @@ def initialize(*args) super(*args) end - def execute(type = nil, arg = nil) - # Please check IRB.init_config in lib/irb/init.rb that sets - # IRB.conf[:MEASURE_PROC] to register default "measure" methods, - # "measure :time" (abbreviated as "measure") and "measure :stackprof". - - if block_given? + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' return end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(type = nil, arg = nil) + # Please check IRB.init_config in lib/irb/init.rb that sets + # IRB.conf[:MEASURE_PROC] to register default "measure" methods, + # "measure :time" (abbreviated as "measure") and "measure :stackprof". case type when :off diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 6487c9d24cb6ee..3fc6b68d21f4b3 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -7,8 +7,8 @@ module IRB module Command class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}") end end end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index 888fe466f1a176..b51928c6508a92 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -14,7 +14,7 @@ class Workspaces < Base category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| truncated_inspect(ws.main) end @@ -39,8 +39,13 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) - irb_context.push_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end super end end @@ -49,8 +54,8 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) - irb_context.pop_workspace(*obj) + def execute(_arg) + irb_context.pop_workspace super end end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index 4dde28bee9bbaf..f9393cd3b5cab9 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,17 +3,6 @@ module IRB module Command class ShowDoc < Base - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - category "Context" description "Look up documentation with RI." @@ -31,7 +20,9 @@ def transform_args(args) HELP_MESSAGE - def execute(*names) + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -39,15 +30,13 @@ def execute(*names) ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) end - if names.empty? + if name.nil? Ri.interactive else - names.each do |name| - begin - Ri.display_name(name.to_s) - rescue RDoc::RI::Error - puts $!.message - end + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message end end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index 32bdf74d313e92..c4c8fc004121c2 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -24,18 +24,9 @@ class ShowSource < Base show_source Foo::BAR HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(str = nil) + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) unless str.is_a?(String) puts "Error: Expected a string but got #{str.inspect}" return diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index cce7d2b0f8443a..29e5e35ac0135b 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -7,8 +7,8 @@ module IRB module Command class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}") end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 5cc7b8c6f85fa2..24428a5c130ed7 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,10 +9,6 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -36,7 +32,12 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) print_deprecated_warning if irb_context.with_debugger @@ -44,7 +45,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +54,7 @@ class Jobs < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "List of current sessions." - def execute + def execute(_arg) print_deprecated_warning if irb_context.with_debugger @@ -61,7 +62,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +71,12 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) print_deprecated_warning if irb_context.with_debugger @@ -78,7 +84,7 @@ def execute(key = nil) return end - super + extend_irb_context raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key IRB.JobManager.switch(key) @@ -89,7 +95,12 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) print_deprecated_warning if irb_context.with_debugger @@ -97,7 +108,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb index d6658d70430e7f..c8439f12120333 100644 --- a/lib/irb/command/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -8,7 +8,7 @@ class Whereami < Base category "Context" description "Show the source code around binding.irb again." - def execute(*) + def execute(_arg) code = irb_context.workspace.code_around_binding if code puts code diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index b3813e89391d22..8a1df11561283b 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -86,6 +86,14 @@ def retrieve_files_to_require_from_load_path ) end + def command_completions(preposing, target) + if preposing.empty? && !target.empty? + IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + else + [] + end + end + def retrieve_files_to_require_relative_from_current_dir @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') @@ -103,9 +111,11 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) + commands = command_completions(preposing, target) result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - return [] unless result - result.completion_candidates.map { target + _1 } + return commands unless result + + commands | result.completion_candidates.map { target + _1 } end def doc_namespace(preposing, matched, _postposing, bind:) @@ -181,7 +191,8 @@ def completion_candidates(preposing, target, postposing, bind:) result = complete_require_path(target, preposing, postposing) return result if result end - retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands = command_completions(preposing || '', target) + commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } end def doc_namespace(_preposing, matched, _postposing, bind:) diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 60dfb9668dac3c..836b8d26257473 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -585,31 +585,44 @@ def inspect_mode=(opt) @inspect_mode end - def evaluate(line, line_no) # :nodoc: + def evaluate(statement, line_no) # :nodoc: @line_no = line_no result = nil + case statement + when Statement::EmptyInput + return + when Statement::Expression + result = evaluate_expression(statement.code, line_no) + when Statement::Command + result = statement.command_class.execute(self, statement.arg) + end + + set_last_value(result) + end + + def evaluate_expression(code, line_no) # :nodoc: + result = nil if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? IRB.set_measure_callback end if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? last_proc = proc do - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| _name, callback, arg = item proc do - callback.(self, line, line_no, arg) do + callback.(self, code, line_no, arg) do chain.call end end end.call else - result = workspace.evaluate(line, @eval_path, line_no) + result = workspace.evaluate(code, @eval_path, line_no) end - - set_last_value(result) + result end def inspect_last_value # :nodoc: @@ -646,17 +659,5 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end - - # Return true if it's aliased from the argument and it's not an identifier. - def symbol_alias?(command) - return nil if command.match?(/\A\w+\z/) - command_aliases.key?(command.to_sym) - end - - # Return true if the command supports transforming args - def transform_args?(command) - command = command_aliases.fetch(command.to_sym, command) - ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) - end end end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb new file mode 100644 index 00000000000000..6025b0547da0be --- /dev/null +++ b/lib/irb/default_commands.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require_relative "command" +require_relative "command/context" +require_relative "command/exit" +require_relative "command/force_exit" +require_relative "command/chws" +require_relative "command/pushws" +require_relative "command/subirb" +require_relative "command/load" +require_relative "command/debug" +require_relative "command/edit" +require_relative "command/break" +require_relative "command/catch" +require_relative "command/next" +require_relative "command/delete" +require_relative "command/step" +require_relative "command/continue" +require_relative "command/finish" +require_relative "command/backtrace" +require_relative "command/info" +require_relative "command/help" +require_relative "command/show_doc" +require_relative "command/irb_info" +require_relative "command/ls" +require_relative "command/measure" +require_relative "command/show_source" +require_relative "command/whereami" +require_relative "command/history" + +module IRB + ExtendCommand = Command + + # Installs the default irb extensions command bundle. + module ExtendCommandBundle + # See #install_alias_method. + NO_OVERRIDE = 0 + # See #install_alias_method. + OVERRIDE_PRIVATE_ONLY = 0x01 + # See #install_alias_method. + OVERRIDE_ALL = 0x02 + + Command._register_with_aliases(:irb_context, Command::Context, + [ + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], + ) + + Command._register_with_aliases(:irb_exit, Command::Exit, + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_exit!, Command::ForceExit, + [:exit!, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, + [:cwws, NO_OVERRIDE], + [:pwws, NO_OVERRIDE], + [:irb_print_working_workspace, OVERRIDE_ALL], + [:irb_cwws, OVERRIDE_ALL], + [:irb_pwws, OVERRIDE_ALL], + [:irb_current_working_binding, OVERRIDE_ALL], + [:irb_print_working_binding, OVERRIDE_ALL], + [:irb_cwb, OVERRIDE_ALL], + [:irb_pwb, OVERRIDE_ALL], + ) + + Command._register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, + [:chws, NO_OVERRIDE], + [:cws, NO_OVERRIDE], + [:irb_chws, OVERRIDE_ALL], + [:irb_cws, OVERRIDE_ALL], + [:irb_change_binding, OVERRIDE_ALL], + [:irb_cb, OVERRIDE_ALL], + [:cb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_workspaces, Command::Workspaces, + [:workspaces, NO_OVERRIDE], + [:irb_bindings, OVERRIDE_ALL], + [:bindings, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_push_workspace, Command::PushWorkspace, + [:pushws, NO_OVERRIDE], + [:irb_pushws, OVERRIDE_ALL], + [:irb_push_binding, OVERRIDE_ALL], + [:irb_pushb, OVERRIDE_ALL], + [:pushb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, + [:popws, NO_OVERRIDE], + [:irb_popws, OVERRIDE_ALL], + [:irb_pop_binding, OVERRIDE_ALL], + [:irb_popb, OVERRIDE_ALL], + [:popb, NO_OVERRIDE], + ) + + Command._register_with_aliases(:irb_load, Command::Load) + Command._register_with_aliases(:irb_require, Command::Require) + Command._register_with_aliases(:irb_source, Command::Source, + [:source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb, Command::IrbCommand) + Command._register_with_aliases(:irb_jobs, Command::Jobs, + [:jobs, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_fg, Command::Foreground, + [:fg, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_kill, Command::Kill, + [:kill, OVERRIDE_PRIVATE_ONLY] + ) + + Command._register_with_aliases(:irb_debug, Command::Debug, + [:debug, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_edit, Command::Edit, + [:edit, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_break, Command::Break) + Command._register_with_aliases(:irb_catch, Command::Catch) + Command._register_with_aliases(:irb_next, Command::Next) + Command._register_with_aliases(:irb_delete, Command::Delete, + [:delete, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_step, Command::Step, + [:step, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_continue, Command::Continue, + [:continue, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_finish, Command::Finish, + [:finish, NO_OVERRIDE] + ) + Command._register_with_aliases(:irb_backtrace, Command::Backtrace, + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_debug_info, Command::Info, + [:info, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_help, Command::Help, + [:help, NO_OVERRIDE], + [:show_cmds, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_doc, Command::ShowDoc, + [:show_doc, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_info, Command::IrbInfo) + + Command._register_with_aliases(:irb_ls, Command::Ls, + [:ls, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_measure, Command::Measure, + [:measure, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_show_source, Command::ShowSource, + [:show_source, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_whereami, Command::Whereami, + [:whereami, NO_OVERRIDE] + ) + + Command._register_with_aliases(:irb_history, Command::History, + [:history, NO_OVERRIDE], + [:hist, NO_OVERRIDE] + ) + + def self.all_commands_info + user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| + result[target] ||= [] + result[target] << alias_name + end + + Command.commands.map do |command_name, (command_class, aliases)| + aliases = aliases.map { |a| a.first } + + if additional_aliases = user_aliases[command_name] + aliases += additional_aliases + end + + display_name = aliases.shift || command_name + { + display_name: display_name, + description: command_class.description, + category: command_class.category + } + end + end + + def self.command_override_policies + @@command_override_policies ||= Command.commands.flat_map do |cmd_name, (cmd_class, aliases)| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method + when NO_OVERRIDE + !public_method && !private_method + end + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end + + # Convert a command name to its implementation class if such command exists + def self.load_command(command) + command = command.to_sym + Command.commands.each do |command_name, (command_class, aliases)| + if command_name == command || aliases.any? { |alias_name, _| alias_name == command } + return command_class + end + end + nil + end + + # Deprecated. Doesn't have any effect. + @EXTEND_COMMANDS = [] + + # Drepcated. Use Command.regiser instead. + def self.def_extend_command(cmd_name, cmd_class, _, *aliases) + Command._register_with_aliases(cmd_name, cmd_class, *aliases) + @@command_override_policies = nil + end + end +end diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index 87fe03e23d953d..60e8afe31f88f2 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -29,11 +29,9 @@ def change_workspace(*_main) return main end - replace_workspace(WorkSpace.new(_main[0])) - - if !(class< @command_class end - - def evaluable_code - # Hook command-specific transformation to return valid Ruby code - if @command_class.respond_to?(:transform_args) - arg = @command_class.transform_args(@arg) - else - arg = @arg - end - - [@command, arg].compact.join(' ') - end end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425e1183..1490f7b478d432 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + if !(class<" diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index d43fcfd36b219b..6cf28460c2d5af 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "0.24.0" + spec.version = "0.25.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index 0b1893cade9a4e..9437589623f26b 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -947,8 +947,35 @@ def visit_interpolated_regular_expression_node(node) def visit_interpolated_string_node(node) if node.heredoc? children, closing = visit_heredoc(node) + opening = token(node.opening_loc) + + start_offset = node.opening_loc.end_offset + 1 + end_offset = node.parts.first.location.start_offset + + # In the below case, the offsets should be the same: + # + # <<~HEREDOC + # a #{b} + # HEREDOC + # + # But in this case, the end_offset would be greater than the start_offset: + # + # <<~HEREDOC + # #{b} + # HEREDOC + # + # So we need to make sure the result node's heredoc range is correct, without updating the children + result = if start_offset < end_offset + # We need to add a padding string to ensure that the heredoc has correct range for its body + padding_string_node = builder.string_internal(["", srange_offsets(start_offset, end_offset)]) + node_with_correct_location = builder.string_compose(opening, [padding_string_node, *children], closing) + # But the padding string should not be included in the final AST, so we need to update the result's children + node_with_correct_location.updated(:dstr, children) + else + builder.string_compose(opening, children, closing) + end - return builder.string_compose(token(node.opening_loc), children, closing) + return result end parts = if node.parts.one? { |part| part.type == :string_node } diff --git a/lib/rdoc/context.rb b/lib/rdoc/context.rb index c6edfb473c2d16..c688d562c3e567 100644 --- a/lib/rdoc/context.rb +++ b/lib/rdoc/context.rb @@ -710,7 +710,7 @@ def display(method_attr) # :nodoc: # This method exists to make it easy to work with Context subclasses that # aren't part of RDoc. - def each_ancestor # :nodoc: + def each_ancestor(&_) # :nodoc: end ## diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index 0d7226b36f41e0..a3719f502c957a 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -315,7 +315,7 @@ def self.move_cursor_down(x) end def self.hide_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('civis') rescue Reline::Terminfo::TerminfoError @@ -327,7 +327,7 @@ def self.hide_cursor end def self.show_cursor - if Reline::Terminfo.enabled? + if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? begin @@output.write Reline::Terminfo.tigetstr('cnorm') rescue Reline::Terminfo::TerminfoError diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index a561feee578cd2..5d0a7fb63d2939 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -49,13 +49,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 23 ^W :em_kill_region, # 24 ^X - :ed_sequence_lead_in, + :ed_unassigned, # 25 ^Y :em_yank, # 26 ^Z :ed_ignore, # 27 ^[ - :em_meta_next, + :ed_unassigned, # 28 ^\ :ed_ignore, # 29 ^] @@ -319,9 +319,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 158 M-^^ :ed_unassigned, # 159 M-^_ - :em_copy_prev_word, - # 160 M-SPACE :ed_unassigned, + # 160 M-SPACE + :em_set_mark, # 161 M-! :ed_unassigned, # 162 M-" @@ -415,7 +415,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 206 M-N :vi_search_next, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :vi_search_prev, # 209 M-Q @@ -431,15 +431,15 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 214 M-V :ed_unassigned, # 215 M-W - :em_copy_region, + :ed_unassigned, # 216 M-X - :ed_command, - # 217 M-Y :ed_unassigned, + # 217 M-Y + :em_yank_pop, # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] @@ -495,9 +495,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 246 M-v :ed_unassigned, # 247 M-w - :em_copy_region, + :ed_unassigned, # 248 M-x - :ed_command, + :ed_unassigned, # 249 M-y :ed_unassigned, # 250 M-z diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb index 98146d2f7794e8..06bb0ba8e44fdb 100644 --- a/lib/reline/key_actor/vi_command.rb +++ b/lib/reline/key_actor/vi_command.rb @@ -17,7 +17,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 7 ^G :ed_unassigned, # 8 ^H - :ed_unassigned, + :ed_prev_char, # 9 ^I :ed_unassigned, # 10 ^J @@ -41,7 +41,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 19 ^S :ed_ignore, # 20 ^T - :ed_unassigned, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 24 ^X :ed_unassigned, # 25 ^Y - :ed_unassigned, + :em_yank, # 26 ^Z :ed_unassigned, # 27 ^[ @@ -75,7 +75,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 36 $ :ed_move_to_end, # 37 % - :vi_match, + :ed_unassigned, # 38 & :ed_unassigned, # 39 ' @@ -89,11 +89,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 43 + :ed_next_history, # 44 , - :vi_repeat_prev_char, + :ed_unassigned, # 45 - :ed_prev_history, # 46 . - :vi_redo, + :ed_unassigned, # 47 / :vi_search_prev, # 48 0 @@ -117,9 +117,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 57 9 :ed_argument_digit, # 58 : - :ed_command, + :ed_unassigned, # 59 ; - :vi_repeat_next_char, + :ed_unassigned, # 60 < :ed_unassigned, # 61 = @@ -157,21 +157,21 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 77 M :ed_unassigned, # 78 N - :vi_repeat_search_prev, + :ed_unassigned, # 79 O - :ed_sequence_lead_in, + :ed_unassigned, # 80 P :vi_paste_prev, # 81 Q :ed_unassigned, # 82 R - :vi_replace_mode, + :ed_unassigned, # 83 S - :vi_substitute_line, + :ed_unassigned, # 84 T :vi_to_prev_char, # 85 U - :vi_undo_line, + :ed_unassigned, # 86 V :ed_unassigned, # 87 W @@ -179,11 +179,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 88 X :ed_delete_prev_char, # 89 Y - :vi_yank_end, + :ed_unassigned, # 90 Z :ed_unassigned, # 91 [ - :ed_sequence_lead_in, + :ed_unassigned, # 92 \ :ed_unassigned, # 93 ] @@ -191,7 +191,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 94 ^ :vi_first_print, # 95 _ - :vi_history_word, + :ed_unassigned, # 96 ` :ed_unassigned, # 97 a @@ -221,7 +221,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 109 m :ed_unassigned, # 110 n - :vi_repeat_search_next, + :ed_unassigned, # 111 o :ed_unassigned, # 112 p @@ -231,11 +231,11 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 114 r :vi_replace_char, # 115 s - :vi_substitute_char, + :ed_unassigned, # 116 t :vi_to_next_char, # 117 u - :vi_undo, + :ed_unassigned, # 118 v :vi_histedit, # 119 w @@ -253,9 +253,9 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 125 } :ed_unassigned, # 126 ~ - :vi_change_case, - # 127 ^? :ed_unassigned, + # 127 ^? + :em_delete_prev_char, # 128 M-^@ :ed_unassigned, # 129 M-^A @@ -415,7 +415,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 206 M-N :ed_unassigned, # 207 M-O - :ed_sequence_lead_in, + :ed_unassigned, # 208 M-P :ed_unassigned, # 209 M-Q @@ -439,7 +439,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base # 218 M-Z :ed_unassigned, # 219 M-[ - :ed_sequence_lead_in, + :ed_unassigned, # 220 M-\ :ed_unassigned, # 221 M-] diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb index b8e89f81d8075d..c3d7f9c12d04de 100644 --- a/lib/reline/key_actor/vi_insert.rb +++ b/lib/reline/key_actor/vi_insert.rb @@ -41,7 +41,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 19 ^S :vi_search_next, # 20 ^T - :ed_insert, + :ed_transpose_chars, # 21 ^U :vi_kill_line_prev, # 22 ^V @@ -51,7 +51,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 24 ^X :ed_insert, # 25 ^Y - :ed_insert, + :em_yank, # 26 ^Z :ed_insert, # 27 ^[ diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index c236044e41fed9..e4d7ecf1a29189 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -33,8 +33,6 @@ class Reline::LineEditor vi_next_big_word vi_prev_big_word vi_end_big_word - vi_repeat_next_char - vi_repeat_prev_char } module CompletionState @@ -229,10 +227,11 @@ def reset_variables(prompt = '', encoding:) @vi_clipboard = '' @vi_arg = nil @waiting_proc = nil - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil @completion_journey_state = nil @completion_state = CompletionState::NORMAL + @completion_occurs = false @perfect_matched = nil @menu_info = nil @searching_prompt = nil @@ -541,10 +540,6 @@ def render_differential new_lines.size - y end - def current_row - wrapped_lines.flatten[wrapped_cursor_y] - end - def upper_space_height(wrapped_cursor_y) wrapped_cursor_y - screen_scroll_top end @@ -935,37 +930,23 @@ def dialog_proc_scope_completion_journey_data end private def run_for_operators(key, method_symbol, &block) - if @waiting_operator_proc + if @vi_waiting_operator if VI_MOTIONS.include?(method_symbol) old_byte_pointer = @byte_pointer - @vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1 + @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg block.(true) unless @waiting_proc byte_pointer_diff = @byte_pointer - old_byte_pointer @byte_pointer = old_byte_pointer - @waiting_operator_proc.(byte_pointer_diff) - else - old_waiting_proc = @waiting_proc - old_waiting_operator_proc = @waiting_operator_proc - current_waiting_operator_proc = @waiting_operator_proc - @waiting_proc = proc { |k| - old_byte_pointer = @byte_pointer - old_waiting_proc.(k) - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - current_waiting_operator_proc.(byte_pointer_diff) - @waiting_operator_proc = old_waiting_operator_proc - } + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting end else # Ignores operator when not motion is given. block.(false) + cleanup_waiting end - @waiting_operator_proc = nil - @waiting_operator_vi_arg = nil - if @vi_arg - @vi_arg = nil - end + @vi_arg = nil else block.(false) end @@ -982,7 +963,7 @@ def dialog_proc_scope_completion_journey_data end def wrap_method_call(method_symbol, method_obj, key, with_operator = false) - if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil? + if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? not_insertion = method_symbol != :ed_insert process_insert(force: not_insertion) end @@ -1001,11 +982,32 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) end end + private def cleanup_waiting + @waiting_proc = nil + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + @searching_prompt = nil + @drop_terminate_spaces = false + end + private def process_key(key, method_symbol) + if key.is_a?(Symbol) + cleanup_waiting + elsif @waiting_proc + old_byte_pointer = @byte_pointer + @waiting_proc.call(key) + if @vi_waiting_operator + byte_pointer_diff = @byte_pointer - old_byte_pointer + @byte_pointer = old_byte_pointer + send(@vi_waiting_operator, byte_pointer_diff) + cleanup_waiting + end + @kill_ring.process + return + end + if method_symbol and respond_to?(method_symbol, true) method_obj = method(method_symbol) - else - method_obj = nil end if method_symbol and key.is_a?(Symbol) if @vi_arg and argumentable?(method_obj) @@ -1027,8 +1029,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) run_for_operators(key, method_symbol) do |with_operator| wrap_method_call(method_symbol, method_obj, key, with_operator) end - elsif @waiting_proc - @waiting_proc.(key) elsif method_obj wrap_method_call(method_symbol, method_obj, key) else @@ -1039,9 +1039,6 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false) @vi_arg = nil end end - elsif @waiting_proc - @waiting_proc.(key) - @kill_ring.process elsif method_obj if method_symbol == :ed_argument_digit wrap_method_call(method_symbol, method_obj, key) @@ -1118,42 +1115,35 @@ def input_key(key) end old_lines = @buffer_of_lines.dup @first_char = false - completion_occurs = false + @completion_occurs = false if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord if !@config.disable_completion process_insert(force: true) if @config.autocompletion @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list(:down) + @completion_occurs = move_completed_list(:down) else @completion_journey_state = nil result = call_completion_proc if result.is_a?(Array) - completion_occurs = true + @completion_occurs = true complete(result, false) end end end - elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up - if not @config.disable_completion and @config.autocompletion - process_insert(force: true) - @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list(:up) - end elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) # In vi mode, move completed list even if autocompletion is off if not @config.disable_completion process_insert(force: true) @completion_state = CompletionState::NORMAL - completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) + @completion_occurs = move_completed_list("\C-p".ord == key.char ? :up : :down) end elsif Symbol === key.char and respond_to?(key.char, true) process_key(key.char, key.char) else normal_char(key) end - - unless completion_occurs + unless @completion_occurs @completion_state = CompletionState::NORMAL @completion_journey_state = nil end @@ -1164,7 +1154,7 @@ def input_key(key) end modified = old_lines != @buffer_of_lines - if !completion_occurs && modified && !@config.disable_completion && @config.autocompletion + if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion # Auto complete starts only when edited process_insert(force: true) @completion_journey_state = retrieve_completion_journey_state @@ -1433,6 +1423,14 @@ def finish end end + private def completion_journey_up(key) + if not @config.disable_completion and @config.autocompletion + @completion_state = CompletionState::NORMAL + @completion_occurs = move_completed_list(:up) + end + end + alias_method :menu_complete_backward, :completion_journey_up + # Editline:: +ed-unassigned+ This editor command always results in an error. # GNU Readline:: There is no corresponding macro. private def ed_unassigned(key) end # do nothing @@ -1534,6 +1532,7 @@ def finish @byte_pointer = 0 end alias_method :beginning_of_line, :ed_move_to_beg + alias_method :vi_zero, :ed_move_to_beg private def ed_move_to_end(key) @byte_pointer = 0 @@ -2319,50 +2318,63 @@ def finish copy_for_vi(deleted) end - private def vi_zero(key) - @byte_pointer = 0 + private def vi_change_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @drop_terminate_spaces = true + @vi_waiting_operator = :vi_change_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end end - private def vi_change_meta(key, arg: 1) - @drop_terminate_spaces = true - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - set_current_line(line) - copy_for_vi(cut) - @byte_pointer += byte_pointer_diff if byte_pointer_diff < 0 - @config.editing_mode = :vi_insert - @drop_terminate_spaces = false - } - @waiting_operator_vi_arg = arg + private def vi_change_meta_confirm(byte_pointer_diff) + vi_delete_meta_confirm(byte_pointer_diff) + @config.editing_mode = :vi_insert + @drop_terminate_spaces = false end - private def vi_delete_meta(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) - } - @waiting_operator_vi_arg = arg + private def vi_delete_meta(key, arg: nil) + if @vi_waiting_operator + set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_delete_meta_confirm + @vi_waiting_operator_arg = arg || 1 + end end - private def vi_yank(key, arg: 1) - @waiting_operator_proc = proc { |byte_pointer_diff| - if byte_pointer_diff > 0 - cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) - end - copy_for_vi(cut) - } - @waiting_operator_vi_arg = arg + private def vi_delete_meta_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) + set_current_line(line || '', @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) + end + + private def vi_yank(key, arg: nil) + if @vi_waiting_operator + copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? + @vi_waiting_operator = nil + @vi_waiting_operator_arg = nil + else + @vi_waiting_operator = :vi_yank_confirm + @vi_waiting_operator_arg = arg || 1 + end + end + + private def vi_yank_confirm(byte_pointer_diff) + if byte_pointer_diff > 0 + cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) + elsif byte_pointer_diff < 0 + cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) + end + copy_for_vi(cut) end private def vi_list_or_eof(key) @@ -2467,18 +2479,11 @@ def finish end private def vi_to_column(key, arg: 0) - current_row_width = calculate_width(current_row) - @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |total, gc| - # total has [byte_size, cursor] + # Implementing behavior of vi, not Readline's vi-mode. + @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc| mbchar_width = Reline::Unicode.get_mbchar_width(gc) - if (total.last + mbchar_width) >= arg - break total - elsif (total.last + mbchar_width) >= current_row_width - break total - else - total = [total.first + gc.bytesize, total.last + mbchar_width] - total - end + break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg + [total_byte_size + gc.bytesize, total_width + mbchar_width] } end @@ -2625,7 +2630,4 @@ def finish @mark_pointer = new_pointer end alias_method :exchange_point_and_mark, :em_exchange_mark - - private def em_meta_next(key) - end end diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb index 2cfa32b9f7a9e0..6885a0c6be9242 100644 --- a/lib/reline/terminfo.rb +++ b/lib/reline/terminfo.rb @@ -80,23 +80,11 @@ module Reline::Terminfo def self.setupterm(term, fildes) errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT) ret = @setupterm.(term, fildes, errret_int) - errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i') case ret when 0 # OK - 0 + @term_supported = true when -1 # ERR - case errret - when 1 - raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.') - when 0 - raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.') - when -1 - raise TerminfoError.new('The terminfo database could not be found.') - else # unknown - -1 - end - else # unknown - -2 + @term_supported = false end end @@ -148,9 +136,14 @@ def self.tigetnum(capname) num end + # NOTE: This means Fiddle and curses are enabled. def self.enabled? true end + + def self.term_supported? + @term_supported + end end if Reline::Terminfo.curses_dl module Reline::Terminfo diff --git a/lib/reline/version.rb b/lib/reline/version.rb index 06bb3acc8454e9..acb5e279b8cc77 100644 --- a/lib/reline/version.rb +++ b/lib/reline/version.rb @@ -1,3 +1,3 @@ module Reline - VERSION = '0.5.0' + VERSION = '0.5.1' end diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index 65c86e9b90e4ef..fe3e0e19d1f00c 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -107,9 +107,10 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options) yield path.dup ensure unless base - stat = File.stat(File.dirname(path)) + base = File.dirname(path) + stat = File.stat(base) if stat.world_writable? and !stat.sticky? - raise ArgumentError, "parent directory is world writable but not sticky" + raise ArgumentError, "parent directory is world writable but not sticky: #{base}" end end FileUtils.remove_entry path diff --git a/load.c b/load.c index a9c08c62a8de94..dcf50f5b7a91c8 100644 --- a/load.c +++ b/load.c @@ -1532,10 +1532,22 @@ rb_f_autoload(VALUE obj, VALUE sym, VALUE file) * autoload?(name, inherit=true) -> String or nil * * Returns _filename_ to be loaded if _name_ is registered as - * +autoload+. + * +autoload+ in the current namespace or one of its ancestors. * * autoload(:B, "b") * autoload?(:B) #=> "b" + * + * module C + * autoload(:D, "d") + * autoload?(:D) #=> "d" + * autoload?(:B) #=> nil + * end + * + * class E + * autoload(:F, "f") + * autoload?(:F) #=> "f" + * autoload?(:B) #=> "b" + * end */ static VALUE diff --git a/mini_builtin.c b/mini_builtin.c index dce822a86c176d..38b0ca8d818948 100644 --- a/mini_builtin.c +++ b/mini_builtin.c @@ -39,7 +39,7 @@ builtin_iseq_load(const char *feature_name, const struct rb_builtin_function *ta .coverage_enabled = FALSE, .debug_level = 0, }; - const rb_iseq_t *iseq = rb_iseq_new_with_opt(&ast->body, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization); + const rb_iseq_t *iseq = rb_iseq_new_with_opt(&ast->body, name_str, name_str, Qnil, 0, NULL, 0, ISEQ_TYPE_TOP, &optimization, Qnil); GET_VM()->builtin_function_table = NULL; rb_ast_dispose(ast); diff --git a/node.c b/node.c index 8ef7bb7c46a77a..8a6b55b0b5808a 100644 --- a/node.c +++ b/node.c @@ -15,18 +15,18 @@ #include "node.h" #include "rubyparser.h" #include "internal/parse.h" -#define T_NODE 0x1b #else #include "internal.h" #include "internal/hash.h" -#include "internal/variable.h" #include "ruby/ruby.h" #include "vm_core.h" #endif +#include "internal/variable.h" + #define NODE_BUF_DEFAULT_SIZE (sizeof(struct RNode) * 16) static void @@ -87,16 +87,10 @@ rb_node_buffer_new(void) typedef void node_itr_t(rb_ast_t *ast, void *ctx, NODE *node); static void iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, void *ctx); -/* Setup NODE structure. - * NODE is not an object managed by GC, but it imitates an object - * so that it can work with `RB_TYPE_P(obj, T_NODE)`. - * This dirty hack is needed because Ripper jumbles NODEs and other type - * objects. - */ void rb_node_init(NODE *n, enum node_type type) { - RNODE(n)->flags = T_NODE; + RNODE(n)->flags = 0; nd_init_type(RNODE(n), type); RNODE(n)->nd_loc.beg_pos.lineno = 0; RNODE(n)->nd_loc.beg_pos.column = 0; @@ -351,18 +345,24 @@ iterate_node_values(rb_ast_t *ast, node_buffer_list_t *nb, node_itr_t * func, vo } } -void -rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating) +static void +script_lines_free(rb_ast_t *ast, rb_parser_ary_t *script_lines) { - if (ast->node_buffer) { - if (ast->body.script_lines) rb_gc_mark_and_move(&ast->body.script_lines); + for (long i = 0; i < script_lines->len; i++) { + parser_string_free(ast, (rb_parser_string_t *)script_lines->data[i]); } + xfree(script_lines->data); + xfree(script_lines); } void rb_ast_free(rb_ast_t *ast) { if (ast->node_buffer) { + if (ast->body.script_lines && !FIXNUM_P((VALUE)ast->body.script_lines)) { + script_lines_free(ast, ast->body.script_lines); + ast->body.script_lines = NULL; + } rb_node_buffer_free(ast, ast->node_buffer); ast->node_buffer = 0; } diff --git a/node.h b/node.h index d5522c82eca355..bcc7e451d2c5ee 100644 --- a/node.h +++ b/node.h @@ -56,7 +56,6 @@ void rb_ast_dispose(rb_ast_t*); const char *ruby_node_name(int node); void rb_node_init(NODE *n, enum node_type type); -void rb_ast_mark_and_move(rb_ast_t *ast, bool reference_updating); void rb_ast_update_references(rb_ast_t*); void rb_ast_free(rb_ast_t*); NODE *rb_ast_newnode(rb_ast_t*, enum node_type type, size_t size, size_t alignment); diff --git a/numeric.c b/numeric.c index f0a0e3c279e9d1..f613d4120d7e70 100644 --- a/numeric.c +++ b/numeric.c @@ -5453,11 +5453,12 @@ rb_fix_digits(VALUE fix, long base) return rb_ary_new_from_args(1, INT2FIX(0)); digits = rb_ary_new(); - while (x > 0) { + while (x >= base) { long q = x % base; rb_ary_push(digits, LONG2NUM(q)); x /= base; } + rb_ary_push(digits, LONG2NUM(x)); return digits; } diff --git a/object.c b/object.c index ce8f1fbc5ed0b2..22753ff45f0ea5 100644 --- a/object.c +++ b/object.c @@ -870,7 +870,7 @@ rb_obj_is_instance_of(VALUE obj, VALUE c) return RBOOL(rb_obj_class(obj) == c); } -// Returns whether c is a proper (c != cl) subclass of cl +// Returns whether c is a proper (c != cl) superclass of cl // Both c and cl must be T_CLASS static VALUE class_search_class_ancestor(VALUE cl, VALUE c) @@ -883,7 +883,7 @@ class_search_class_ancestor(VALUE cl, VALUE c) VALUE *classes = RCLASS_SUPERCLASSES(cl); // If c's inheritance chain is longer, it cannot be an ancestor - // We are checking for a proper subclass so don't check if they are equal + // We are checking for a proper superclass so don't check if they are equal if (cl_depth <= c_depth) return Qfalse; diff --git a/pack.rb b/pack.rb index d505eaee3570ca..b6e29c3eab4f12 100644 --- a/pack.rb +++ b/pack.rb @@ -17,6 +17,7 @@ class String # returns that array. # See {Packed Data}[rdoc-ref:packed_data.rdoc]. def unpack(fmt, offset: 0) + Primitive.attr! :use_block Primitive.pack_unpack(fmt, offset) end diff --git a/parse.y b/parse.y index 3824321731b145..a87be73e3cfc61 100644 --- a/parse.y +++ b/parse.y @@ -84,9 +84,12 @@ static NODE *reg_named_capture_assign(struct parser_params* p, VALUE regexp, con VALUE rb_io_gets_internal(VALUE io); #endif /* !UNIVERSAL_PARSER */ -#ifndef RIPPER static int rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2); +#ifndef RIPPER +static rb_parser_string_t *rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *original); +#endif + static int node_integer_cmp(rb_node_integer_t *n1, rb_node_integer_t *n2) { @@ -210,7 +213,6 @@ literal_hash(st_data_t a) #endif } } -#endif /* !RIPPER */ static inline int parse_isascii(int c) @@ -334,8 +336,6 @@ RBIMPL_WARNING_POP() #define NO_LEX_CTXT (struct lex_context){0} -#define AREF(ary, i) RARRAY_AREF(ary, i) - #ifndef WARN_PAST_SCOPE # define WARN_PAST_SCOPE 0 #endif @@ -535,7 +535,7 @@ struct parser_params { VALUE debug_output; struct { - VALUE token; + rb_parser_string_t *token; int beg_line; int beg_col; int end_line; @@ -586,7 +586,7 @@ struct parser_params { unsigned int keep_tokens: 1; VALUE error_buffer; - VALUE debug_lines; + rb_parser_ary_t *debug_lines; /* * Store specific keyword locations to generate dummy end token. * Refer to the tail of list element. @@ -711,11 +711,13 @@ after_pop_stack(int len, struct parser_params *p) #define TOK_INTERN() intern_cstr(tok(p), toklen(p), p->enc) #define VALID_SYMNAME_P(s, l, enc, type) (rb_enc_symname_type(s, l, enc, (1U<<(type))) == (int)(type)) +#ifndef RIPPER static inline bool end_with_newline_p(struct parser_params *p, VALUE str) { return RSTRING_LEN(str) > 0 && RSTRING_END(str)[-1] == '\n'; } +#endif static void pop_pvtbl(struct parser_params *p, st_table *tbl) @@ -1491,6 +1493,8 @@ int reg_fragment_check(struct parser_params*, rb_parser_string_t*, int); static int literal_concat0(struct parser_params *p, rb_parser_string_t *head, rb_parser_string_t *tail); static NODE *heredoc_dedent(struct parser_params*,NODE*); +static void check_literal_when(struct parser_params *p, NODE *args, const YYLTYPE *loc); + #ifdef RIPPER static VALUE var_field(struct parser_params *p, VALUE a); #define get_value(idx) (rb_ary_entry(p->s_value_stack, idx)) @@ -1807,7 +1811,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) STR_NEW2(s) # define WARN_I(i) INT2NUM(i) # define WARN_ID(i) rb_id2str(i) -# define WARN_IVAL(i) i # define PRIsWARN PRIsVALUE # define rb_warn0L_experimental(l,fmt) WARN_CALL(WARN_ARGS_L(l, fmt, 1)) # define WARN_ARGS(fmt,n) p->value, id_warn, n, rb_usascii_str_new_lit(fmt) @@ -1830,7 +1833,6 @@ extern const ID id_warn, id_warning, id_gets, id_assoc; # define WARN_S(s) s # define WARN_I(i) i # define WARN_ID(i) rb_id2name(i) -# define WARN_IVAL(i) NUM2INT(i) # define PRIsWARN PRIsVALUE # define WARN_ARGS(fmt,n) WARN_ARGS_L(p->ruby_sourceline,fmt,n) # define WARN_ARGS_L(l,fmt,n) p->ruby_sourcefile, (l), (fmt) @@ -2038,7 +2040,6 @@ get_nd_args(struct parser_params *p, NODE *node) } } -#ifndef RIPPER static st_index_t djb2(const uint8_t *str, size_t len) { @@ -2056,10 +2057,10 @@ parser_memhash(const void *ptr, long len) { return djb2(ptr, len); } -#endif #define PARSER_STRING_PTR(str) (str->ptr) #define PARSER_STRING_LEN(str) (str->len) +#define PARSER_STRING_END(str) (&str->ptr[str->len]) #define STRING_SIZE(str) ((size_t)str->len + 1) #define STRING_TERM_LEN(str) (1) #define STRING_TERM_FILL(str) (str->ptr[str->len] = '\0') @@ -2074,6 +2075,12 @@ parser_memhash(const void *ptr, long len) ((ptrvar) = str->ptr, \ (lenvar) = str->len) +static inline bool +parser_string_end_with_newline_p(struct parser_params *p, rb_parser_string_t *str) +{ + return PARSER_STRING_LEN(str) > 0 && PARSER_STRING_END(str)[-1] == '\n'; +} + static rb_parser_string_t * rb_parser_string_new(rb_parser_t *p, const char *ptr, long len) { @@ -2120,7 +2127,6 @@ rb_parser_string_free(rb_parser_t *p, rb_parser_string_t *str) xfree(str); } -#ifndef RIPPER static st_index_t rb_parser_str_hash(rb_parser_string_t *str) { @@ -2132,7 +2138,6 @@ rb_char_p_hash(const char *c) { return parser_memhash((const void *)c, strlen(c)); } -#endif static size_t rb_parser_str_capacity(rb_parser_string_t *str, const int termlen) @@ -2193,13 +2198,11 @@ PARSER_ENC_CODERANGE_CLEAR(rb_parser_string_t *str) str->coderange = RB_PARSER_ENC_CODERANGE_UNKNOWN; } -#ifndef RIPPER static bool PARSER_ENC_CODERANGE_ASCIIONLY(rb_parser_string_t *str) { return PARSER_ENC_CODERANGE(str) == RB_PARSER_ENC_CODERANGE_7BIT; } -#endif static bool PARSER_ENC_CODERANGE_CLEAN_P(int cr) @@ -2264,7 +2267,6 @@ rb_parser_enc_str_coderange(struct parser_params *p, rb_parser_string_t *str) return cr; } -#ifndef RIPPER static rb_parser_string_t * rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_encoding *enc) { @@ -2277,7 +2279,6 @@ rb_parser_enc_associate(struct parser_params *p, rb_parser_string_t *str, rb_enc rb_parser_string_set_encoding(str, enc); return str; } -#endif static bool rb_parser_is_ascii_string(struct parser_params *p, rb_parser_string_t *str) @@ -2488,6 +2489,13 @@ rb_parser_enc_cr_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, c } +static rb_parser_string_t * +rb_parser_enc_str_buf_cat(struct parser_params *p, rb_parser_string_t *str, const char *ptr, long len, + rb_encoding *ptr_enc) +{ + return rb_parser_enc_cr_str_buf_cat(p, str, ptr, len, ptr_enc, RB_PARSER_ENC_CODERANGE_UNKNOWN, NULL); +} + static rb_parser_string_t * rb_parser_str_buf_append(struct parser_params *p, rb_parser_string_t *str, rb_parser_string_t *str2) { @@ -2528,7 +2536,6 @@ rb_parser_str_resize(struct parser_params *p, rb_parser_string_t *str, long len) return str; } -#ifndef RIPPER # define PARSER_ENC_STRING_GETMEM(str, ptrvar, lenvar, encvar) \ ((ptrvar) = str->ptr, \ (lenvar) = str->len, \ @@ -2549,21 +2556,26 @@ rb_parser_string_hash_cmp(rb_parser_string_t *str1, rb_parser_string_t *str2) memcmp(ptr1, ptr2, len1) != 0); } +#ifndef RIPPER static void rb_parser_ary_extend(rb_parser_t *p, rb_parser_ary_t *ary, long len) { long i; if (ary->capa < len) { ary->capa = len; - ary->data = xrealloc(ary->data, sizeof(void *) * len); + ary->data = (rb_parser_ary_data *)xrealloc(ary->data, sizeof(rb_parser_ary_data) * len); for (i = ary->len; i < len; i++) { ary->data[i] = 0; } } } +/* + * Do not call this directly. + * Use rb_parser_ary_new_capa_for_script_line() or rb_parser_ary_new_capa_for_ast_token() instead. + */ static rb_parser_ary_t * -rb_parser_ary_new_capa(rb_parser_t *p, long len) +parser_ary_new_capa(rb_parser_t *p, long len) { if (len < 0) { rb_bug("negative array size (or size too big): %ld", len); @@ -2572,17 +2584,36 @@ rb_parser_ary_new_capa(rb_parser_t *p, long len) ary->len = 0; ary->capa = len; if (0 < len) { - ary->data = (rb_parser_ast_token_t **)xcalloc(len, sizeof(rb_parser_ast_token_t *)); + ary->data = (rb_parser_ary_data *)xcalloc(len, sizeof(rb_parser_ary_data)); } else { ary->data = NULL; } return ary; } -#define rb_parser_ary_new2 rb_parser_ary_new_capa static rb_parser_ary_t * -rb_parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +rb_parser_ary_new_capa_for_script_line(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_SCRIPT_LINE; + return ary; +} + +static rb_parser_ary_t * +rb_parser_ary_new_capa_for_ast_token(rb_parser_t *p, long len) +{ + rb_parser_ary_t *ary = parser_ary_new_capa(p, len); + ary->data_type = PARSER_ARY_DATA_AST_TOKEN; + return ary; +} + +/* + * Do not call this directly. + * Use rb_parser_ary_push_script_line() or rb_parser_ary_push_ast_token() instead. + */ +static rb_parser_ary_t * +parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ary_data val) { if (ary->len == ary->capa) { rb_parser_ary_extend(p, ary, ary->len == 0 ? 1 : ary->len * 2); @@ -2591,6 +2622,24 @@ rb_parser_ary_push(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t * return ary; } +static rb_parser_ary_t * +rb_parser_ary_push_ast_token(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_ast_token_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_AST_TOKEN) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + +static rb_parser_ary_t * +rb_parser_ary_push_script_line(rb_parser_t *p, rb_parser_ary_t *ary, rb_parser_string_t *val) +{ + if (ary->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + } + return parser_ary_push(p, ary, val); +} + static void rb_parser_ast_token_free(rb_parser_t *p, rb_parser_ast_token_t *token) { @@ -2600,12 +2649,24 @@ rb_parser_ast_token_free(rb_parser_t *p, rb_parser_ast_token_t *token) } static void -rb_parser_tokens_free(rb_parser_t *p, rb_parser_ary_t *tokens) +rb_parser_ary_free(rb_parser_t *p, rb_parser_ary_t *ary) { - for (long i = 0; i < tokens->len; i++) { - rb_parser_ast_token_free(p, tokens->data[i]); + void (*free_func)(rb_parser_t *, rb_parser_ary_data) = NULL; + switch (ary->data_type) { + case PARSER_ARY_DATA_AST_TOKEN: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_ast_token_free; + break; + case PARSER_ARY_DATA_SCRIPT_LINE: + free_func = (void (*)(rb_parser_t *, rb_parser_ary_data))rb_parser_string_free; + break; + default: + rb_bug("unexpected rb_parser_ary_data_type: %d", ary->data_type); + break; + } + for (long i = 0; i < ary->len; i++) { + free_func(p, ary->data[i]); } - xfree(tokens); + xfree(ary); } #endif /* !RIPPER */ @@ -3453,7 +3514,7 @@ command : fcall command_args %prec tLOWEST { set_embraced_location($5, &@4, &@6); $$ = new_command_qcall(p, idCOLON2, $1, $3, Qnull, $5, &@3, &@$); - /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qundef), $:5) %*/ + /*% ripper: method_add_block!(command_call!($:1, $:2, $:3, Qnil), $:5) %*/ } | keyword_super command_args { @@ -4271,7 +4332,7 @@ block_arg : tAMPER arg_value } | tAMPER { - forwarding_arg_check(p, idFWD_BLOCK, 0, "block"); + forwarding_arg_check(p, idFWD_BLOCK, idFWD_ALL, "block"); $$ = NEW_BLOCK_PASS(NEW_LVAR(idFWD_BLOCK, &@1), &@$); /*% ripper: Qnil %*/ } @@ -5376,7 +5437,7 @@ do_body : { case_args : arg_value { - rb_parser_check_literal_when(p, $1, &@1); + check_literal_when(p, $1, &@1); $$ = NEW_LIST($1, &@$); /*% ripper: args_add!(args_new!, $:1) %*/ } @@ -5387,7 +5448,7 @@ case_args : arg_value } | case_args ',' arg_value { - rb_parser_check_literal_when(p, $3, &@3); + check_literal_when(p, $3, &@3); $$ = last_arg_append(p, $1, $3, &@$); /*% ripper: args_add!($:1, $:3) %*/ } @@ -7038,7 +7099,7 @@ do { \ # define yylval_id() (yylval.id) #define set_yylval_noname() set_yylval_id(keyword_nil) -#define has_delayed_token(p) (!NIL_P(p->delayed.token)) +#define has_delayed_token(p) (p->delayed.token != NULL) #ifndef RIPPER #define literal_flush(p, ptr) ((p)->lex.ptok = (ptr)) @@ -7140,7 +7201,7 @@ parser_append_tokens(struct parser_params *p, rb_parser_string_t *str, enum yyto token->str = str; token->loc.beg_pos = p->yylloc->beg_pos; token->loc.end_pos = p->yylloc->end_pos; - rb_parser_ary_push(p, p->tokens, token); + rb_parser_ary_push_ast_token(p, p->tokens, token); p->token_id++; if (p->debug) { @@ -7181,11 +7242,13 @@ parser_dispatch_delayed_token(struct parser_params *p, enum yytokentype t, int l RUBY_SET_YYLLOC_OF_DELAYED_TOKEN(*p->yylloc); if (p->keep_tokens) { - rb_parser_string_t *str = rb_str_to_parser_string(p, p->delayed.token); - parser_append_tokens(p, str, t, line); + /* p->delayed.token is freed by rb_parser_tokens_free */ + parser_append_tokens(p, p->delayed.token, t, line); + } else { + rb_parser_string_free(p, p->delayed.token); } - p->delayed.token = Qnil; + p->delayed.token = NULL; } #else #define literal_flush(p, ptr) ((void)(ptr)) @@ -7222,14 +7285,16 @@ ripper_dispatch_delayed_token(struct parser_params *p, enum yytokentype t) /* save and adjust the location to delayed token for callbacks */ int saved_line = p->ruby_sourceline; const char *saved_tokp = p->lex.ptok; - VALUE s_value; + VALUE s_value, str; if (!has_delayed_token(p)) return; p->ruby_sourceline = p->delayed.beg_line; p->lex.ptok = p->lex.pbeg + p->delayed.beg_col; - s_value = ripper_dispatch1(p, ripper_token2eventid(t), p->delayed.token); + str = rb_str_new_mutable_parser_string(p->delayed.token); + rb_parser_string_free(p, p->delayed.token); + s_value = ripper_dispatch1(p, ripper_token2eventid(t), str); set_parser_s_value(s_value); - p->delayed.token = Qnil; + p->delayed.token = NULL; p->ruby_sourceline = saved_line; p->lex.ptok = saved_tokp; } @@ -7648,22 +7713,12 @@ yycompile0(VALUE arg) struct parser_params *p = (struct parser_params *)arg; int cov = FALSE; - if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string)) { - if (p->debug_lines && p->ruby_sourceline > 0) { - VALUE str = rb_default_rs; - n = p->ruby_sourceline; - do { - rb_ary_push(p->debug_lines, str); - } while (--n); - } - - if (!e_option_supplied(p)) { - cov = TRUE; - } + if (!compile_for_eval && !NIL_P(p->ruby_sourcefile_string) && !e_option_supplied(p)) { + cov = TRUE; } if (p->debug_lines) { - RB_OBJ_WRITE(p->ast, &p->ast->body.script_lines, p->debug_lines); + p->ast->body.script_lines = p->debug_lines; } parser_prepare(p); @@ -7674,6 +7729,8 @@ yycompile0(VALUE arg) RUBY_DTRACE_PARSE_HOOK(BEGIN); n = yyparse(p); RUBY_DTRACE_PARSE_HOOK(END); + + rb_parser_aset_script_lines_for(p->ruby_sourcefile_string, p->debug_lines); p->debug_lines = 0; xfree(p->lex.strterm); @@ -7707,7 +7764,7 @@ yycompile0(VALUE arg) } } p->ast->body.root = tree; - if (!p->ast->body.script_lines) p->ast->body.script_lines = INT2FIX(p->line_count); + if (!p->ast->body.script_lines) p->ast->body.script_lines = (rb_parser_ary_t *)INT2FIX(p->line_count); return TRUE; } @@ -7918,7 +7975,7 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int if (tok < end) { if (has_delayed_token(p)) { - bool next_line = end_with_newline_p(p, p->delayed.token); + bool next_line = parser_string_end_with_newline_p(p, p->delayed.token); int end_line = (next_line ? 1 : 0) + p->delayed.end_line; int end_col = (next_line ? 0 : p->delayed.end_col); if (end_line != p->ruby_sourceline || end_col != tok - p->lex.pbeg) { @@ -7926,12 +7983,12 @@ add_delayed_token(struct parser_params *p, const char *tok, const char *end, int } } if (!has_delayed_token(p)) { - p->delayed.token = rb_str_buf_new(end - tok); - rb_enc_associate(p->delayed.token, p->enc); + p->delayed.token = rb_parser_string_new(p, 0, 0); + rb_parser_enc_associate(p, p->delayed.token, p->enc); p->delayed.beg_line = p->ruby_sourceline; p->delayed.beg_col = rb_long2int(tok - p->lex.pbeg); } - rb_str_buf_cat(p->delayed.token, tok, end - tok); + rb_parser_str_buf_cat(p, p->delayed.token, tok, end - tok); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(end - p->lex.pbeg); p->lex.ptok = end; @@ -7967,9 +8024,9 @@ nextline(struct parser_params *p, int set_encoding) } #ifndef RIPPER if (p->debug_lines) { - VALUE v = rb_str_new_parser_string(str); - if (set_encoding) rb_enc_associate(v, p->enc); - rb_ary_push(p->debug_lines, v); + if (set_encoding) rb_parser_enc_associate(p, str, p->enc); + rb_parser_string_t *copy = rb_parser_string_deep_copy(p, str); + rb_parser_ary_push_script_line(p, p->debug_lines, copy); } #endif p->cr_seen = FALSE; @@ -8129,7 +8186,7 @@ escaped_control_code(int c) } #define WARN_SPACE_CHAR(c, prefix) \ - rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c2)) + rb_warn1("invalid character syntax; use "prefix"\\%c", WARN_I(c)) static int tokadd_codepoint(struct parser_params *p, rb_encoding **encp, @@ -8800,7 +8857,7 @@ flush_string_content(struct parser_params *p, rb_encoding *enc) if (has_delayed_token(p)) { ptrdiff_t len = p->lex.pcur - p->lex.ptok; if (len > 0) { - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); p->delayed.end_line = p->ruby_sourceline; p->delayed.end_col = rb_long2int(p->lex.pcur - p->lex.pbeg); } @@ -9363,7 +9420,7 @@ here_document(struct parser_params *p, rb_strterm_heredoc_t *here) enc = rb_ascii8bit_encoding(); } } - rb_enc_str_buf_cat(p->delayed.token, p->lex.ptok, len, enc); + rb_parser_enc_str_buf_cat(p, p->delayed.token, p->lex.ptok, len, enc); } dispatch_delayed_token(p, tSTRING_CONTENT); } @@ -9645,10 +9702,9 @@ parser_set_encode(struct parser_params *p, const char *name) p->enc = enc; #ifndef RIPPER if (p->debug_lines) { - VALUE lines = p->debug_lines; - long i, n = RARRAY_LEN(lines); - for (i = 0; i < n; ++i) { - rb_enc_associate_index(RARRAY_AREF(lines, i), idx); + long i; + for (i = 0; i < p->debug_lines->len; i++) { + rb_parser_enc_associate(p, p->debug_lines->data[i], enc); } } #endif @@ -12862,6 +12918,19 @@ string_literal_head(struct parser_params *p, enum node_type htype, NODE *head) return lit; } +#ifndef RIPPER +static rb_parser_string_t * +rb_parser_string_deep_copy(struct parser_params *p, const rb_parser_string_t *orig) +{ + rb_parser_string_t *copy; + if (!orig) return NULL; + copy = rb_parser_string_new(p, PARSER_STRING_PTR(orig), PARSER_STRING_LEN(orig)); + copy->coderange = orig->coderange; + copy->enc = orig->enc; + return copy; +} +#endif + /* concat two string literals */ static NODE * literal_concat(struct parser_params *p, NODE *head, NODE *tail, const YYLTYPE *loc) @@ -13439,7 +13508,6 @@ new_xstring(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER static const struct st_hash_type literal_type = { literal_cmp, @@ -13448,8 +13516,8 @@ struct st_hash_type literal_type = { static int nd_type_st_key_enable_p(NODE *node); -void -rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) +static void +check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE *loc) { /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ if (!arg || !p->case_labels) return; @@ -13462,13 +13530,12 @@ rb_parser_check_literal_when(struct parser_params *p, NODE *arg, const YYLTYPE * st_data_t line; if (st_lookup(p->case_labels, (st_data_t)arg, &line)) { rb_warning1("duplicated 'when' clause with line %d is ignored", - WARN_IVAL(INT2NUM((int)line))); + WARN_I((int)line)); return; } } st_insert(p->case_labels, (st_data_t)arg, (st_data_t)p->ruby_sourceline); } -#endif #ifdef RIPPER static int @@ -13824,6 +13891,10 @@ new_bv(struct parser_params *p, ID name) } if (!shadowing_lvar_0(p, name)) return; dyna_var(p, name); + ID *vidp = 0; + if (dvar_defined_ref(p, name, &vidp)) { + if (vidp) *vidp |= LVAR_USED; + } } static void @@ -14860,7 +14931,6 @@ dsym_node(struct parser_params *p, NODE *node, const YYLTYPE *loc) return node; } -#ifndef RIPPER static int nd_type_st_key_enable_p(NODE *node) { @@ -14911,8 +14981,8 @@ nd_value(struct parser_params *p, NODE *node) } } -void -rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) +static void +warn_duplicate_keys(struct parser_params *p, NODE *hash) { /* See https://bugs.ruby-lang.org/issues/20331 for discussion about what is warned. */ st_table *literal_keys = st_init_table_with_size(&literal_type, RNODE_LIST(hash)->as.nd_alen / 2); @@ -14932,9 +15002,9 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) key = (st_data_t)head; if (st_delete(literal_keys, &key, &data)) { - rb_compile_warn(p->ruby_sourcefile, nd_line((NODE *)data), - "key %+"PRIsVALUE" is duplicated and overwritten on line %d", - nd_value(p, head), nd_line(head)); + rb_warn2L(nd_line((NODE *)data), + "key %+"PRIsWARN" is duplicated and overwritten on line %d", + nd_value(p, head), WARN_I(nd_line(head))); } st_insert(literal_keys, (st_data_t)key, (st_data_t)hash); } @@ -14942,12 +15012,11 @@ rb_parser_warn_duplicate_keys(struct parser_params *p, NODE *hash) } st_free_table(literal_keys); } -#endif static NODE * new_hash(struct parser_params *p, NODE *hash, const YYLTYPE *loc) { - if (hash) rb_parser_warn_duplicate_keys(p, hash); + if (hash) warn_duplicate_keys(p, hash); return NEW_HASH(hash, loc); } @@ -15783,7 +15852,7 @@ parser_initialize(struct parser_params *p) p->lex.lpar_beg = -1; /* make lambda_beginning_p() == FALSE at first */ string_buffer_init(p); p->node_id = 0; - p->delayed.token = Qnil; + p->delayed.token = NULL; p->frozen_string_literal = -1; /* not specified */ #ifndef RIPPER p->error_buffer = Qfalse; @@ -15817,9 +15886,7 @@ rb_ruby_parser_mark(void *ptr) rb_gc_mark(p->lex.input); rb_gc_mark(p->ruby_sourcefile_string); rb_gc_mark((VALUE)p->ast); - rb_gc_mark(p->delayed.token); #ifndef RIPPER - rb_gc_mark(p->debug_lines); rb_gc_mark(p->error_buffer); #else rb_gc_mark(p->value); @@ -15841,7 +15908,7 @@ rb_ruby_parser_free(void *ptr) #ifndef RIPPER if (p->tokens) { - rb_parser_tokens_free(p, p->tokens); + rb_parser_ary_free(p, p->tokens); } #endif @@ -15941,19 +16008,9 @@ rb_ruby_parser_set_context(rb_parser_t *p, const struct rb_iseq_struct *base, in } void -rb_ruby_parser_set_script_lines(rb_parser_t *p, VALUE lines) +rb_ruby_parser_set_script_lines(rb_parser_t *p) { - if (!RTEST(lines)) { - lines = Qfalse; - } - else if (lines == Qtrue) { - lines = rb_ary_new(); - } - else { - Check_Type(lines, T_ARRAY); - rb_ary_modify(lines); - } - p->debug_lines = lines; + p->debug_lines = rb_parser_ary_new_capa_for_script_line(p, 10); } void @@ -15966,7 +16023,7 @@ void rb_ruby_parser_keep_tokens(rb_parser_t *p) { p->keep_tokens = 1; - p->tokens = rb_parser_ary_new_capa(p, 10); + p->tokens = rb_parser_ary_new_capa_for_ast_token(p, 10); } #ifndef UNIVERSAL_PARSER @@ -16038,12 +16095,12 @@ rb_parser_error_tolerant(VALUE vparser) } void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) +rb_parser_set_script_lines(VALUE vparser) { struct parser_params *p; TypedData_Get_Struct(vparser, struct parser_params, &parser_data_type, p); - rb_ruby_parser_set_script_lines(p, lines); + rb_ruby_parser_set_script_lines(p); } void @@ -16093,6 +16150,22 @@ rb_parser_set_yydebug(VALUE self, VALUE flag) rb_ruby_parser_set_yydebug(p, RTEST(flag)); return flag; } + +void +rb_set_script_lines_for(VALUE self, VALUE path) +{ + struct parser_params *p; + VALUE hash; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return; + hash = rb_const_get_at(rb_cObject, script_lines); + if (RB_TYPE_P(hash, T_HASH)) { + rb_hash_aset(hash, path, Qtrue); + TypedData_Get_Struct(self, struct parser_params, &parser_data_type, p); + rb_ruby_parser_set_script_lines(p); + } +} #endif /* !UNIVERSAL_PARSER */ VALUE diff --git a/prism/config.yml b/prism/config.yml index 6341233f83ec42..a0abb98ef7e3a8 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -60,7 +60,6 @@ errors: - DEF_ENDLESS - DEF_ENDLESS_SETTER - DEF_NAME - - DEF_NAME_AFTER_RECEIVER - DEF_PARAMS_TERM - DEF_PARAMS_TERM_PAREN - DEF_RECEIVER @@ -97,6 +96,7 @@ errors: - EXPECT_EXPRESSION_AFTER_STAR - EXPECT_IDENT_REQ_PARAMETER - EXPECT_LPAREN_REQ_PARAMETER + - EXPECT_MESSAGE - EXPECT_RBRACKET - EXPECT_RPAREN - EXPECT_RPAREN_AFTER_MULTI @@ -235,6 +235,7 @@ errors: - TERNARY_EXPRESSION_TRUE - UNARY_RECEIVER - UNDEF_ARGUMENT + - UNEXPECTED_BLOCK_ARGUMENT - UNEXPECTED_TOKEN_CLOSE_CONTEXT - UNEXPECTED_TOKEN_IGNORE - UNTIL_TERM @@ -269,7 +270,9 @@ warnings: - LITERAL_IN_CONDITION_VERBOSE - SHEBANG_CARRIAGE_RETURN - UNEXPECTED_CARRIAGE_RETURN + - UNREACHABLE_STATEMENT - UNUSED_LOCAL_VARIABLE + - VOID_STATEMENT tokens: - name: EOF value: 1 diff --git a/prism/extension.h b/prism/extension.h index 13a9aabde3e5c3..59a3f0232ac9e6 100644 --- a/prism/extension.h +++ b/prism/extension.h @@ -1,7 +1,7 @@ #ifndef PRISM_EXT_NODE_H #define PRISM_EXT_NODE_H -#define EXPECTED_PRISM_VERSION "0.24.0" +#define EXPECTED_PRISM_VERSION "0.25.0" #include #include diff --git a/prism/prism.c b/prism/prism.c index 086716e92bf581..91d27f566d4e00 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -1005,7 +1005,7 @@ pm_locals_reads(pm_locals_t *locals, pm_constant_id_t name) { * written but not read in certain contexts. */ static void -pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, pm_constant_id_list_t *list, bool warn_unused) { +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); // If we're still below the threshold for switching to a hash, then we only @@ -1013,6 +1013,10 @@ pm_locals_order(PRISM_ATTRIBUTE_UNUSED pm_parser_t *parser, pm_locals_t *locals, // stored in a list. uint32_t capacity = locals->capacity < PM_LOCALS_HASH_THRESHOLD ? locals->size : locals->capacity; + // We will only warn for unused variables if we're not at the top level, or + // if we're parsing a file outside of eval or -e. + bool warn_unused = !toplevel || (!parser->parsing_eval && !PM_PARSER_COMMAND_LINE_OPTION_E(parser)); + for (uint32_t index = 0; index < capacity; index++) { pm_local_t *local = &locals->locals[index]; @@ -1178,6 +1182,156 @@ pm_assert_value_expression(pm_parser_t *parser, pm_node_t *node) { } } +/** + * Warn if the given node is a "void" statement. + */ +static void +pm_void_statement_check(pm_parser_t *parser, const pm_node_t *node) { + const char *type = NULL; + int length = 0; + + switch (PM_NODE_TYPE(node)) { + case PM_BACK_REFERENCE_READ_NODE: + case PM_CLASS_VARIABLE_READ_NODE: + case PM_GLOBAL_VARIABLE_READ_NODE: + case PM_INSTANCE_VARIABLE_READ_NODE: + case PM_LOCAL_VARIABLE_READ_NODE: + case PM_NUMBERED_REFERENCE_READ_NODE: + type = "a variable"; + length = 10; + break; + case PM_CALL_NODE: { + const pm_call_node_t *cast = (const pm_call_node_t *) node; + if (cast->call_operator_loc.start != NULL || cast->message_loc.start == NULL) break; + + const pm_constant_t *message = pm_constant_pool_id_to_constant(&parser->constant_pool, cast->name); + switch (message->length) { + case 1: + switch (message->start[0]) { + case '+': + case '-': + case '*': + case '/': + case '%': + case '|': + case '^': + case '&': + case '>': + case '<': + type = (const char *) message->start; + length = 1; + break; + } + break; + case 2: + switch (message->start[1]) { + case '=': + if (message->start[0] == '<' || message->start[0] == '>' || message->start[0] == '!' || message->start[0] == '=') { + type = (const char *) message->start; + length = 2; + } + break; + case '@': + if (message->start[0] == '+' || message->start[0] == '-') { + type = (const char *) message->start; + length = 2; + } + break; + case '*': + if (message->start[0] == '*') { + type = (const char *) message->start; + length = 2; + } + break; + } + break; + case 3: + if (memcmp(message->start, "<=>", 3) == 0) { + type = "<=>"; + length = 3; + } + break; + } + + break; + } + case PM_CONSTANT_PATH_NODE: + type = "::"; + length = 2; + break; + case PM_CONSTANT_READ_NODE: + type = "a constant"; + length = 10; + break; + case PM_DEFINED_NODE: + type = "defined?"; + length = 8; + break; + case PM_FALSE_NODE: + type = "false"; + length = 5; + break; + case PM_FLOAT_NODE: + case PM_IMAGINARY_NODE: + case PM_INTEGER_NODE: + case PM_INTERPOLATED_REGULAR_EXPRESSION_NODE: + case PM_INTERPOLATED_STRING_NODE: + case PM_RATIONAL_NODE: + case PM_REGULAR_EXPRESSION_NODE: + case PM_SOURCE_ENCODING_NODE: + case PM_SOURCE_FILE_NODE: + case PM_SOURCE_LINE_NODE: + case PM_STRING_NODE: + case PM_SYMBOL_NODE: + type = "a literal"; + length = 9; + break; + case PM_NIL_NODE: + type = "nil"; + length = 3; + break; + case PM_RANGE_NODE: { + const pm_range_node_t *cast = (const pm_range_node_t *) node; + + if (PM_NODE_FLAG_P(cast, PM_RANGE_FLAGS_EXCLUDE_END)) { + type = "..."; + length = 3; + } else { + type = ".."; + length = 2; + } + + break; + } + case PM_SELF_NODE: + type = "self"; + length = 4; + break; + case PM_TRUE_NODE: + type = "true"; + length = 4; + break; + default: + break; + } + + if (type != NULL) { + PM_PARSER_WARN_NODE_FORMAT(parser, node, PM_WARN_VOID_STATEMENT, length, type); + } +} + +/** + * Warn if any of the statements that are not the last statement in the list are + * a "void" statement. + */ +static void +pm_void_statements_check(pm_parser_t *parser, const pm_statements_node_t *node) { + assert(node->body.size > 0); + for (size_t index = 0; index < node->body.size - 1; index++) { + pm_void_statement_check(parser, node->body.nodes[index]); + } +} + /** * When we're handling the predicate of a conditional, we need to know our * context in order to determine the kind of warning we should deliver to the @@ -1741,7 +1895,7 @@ static pm_statements_node_t * pm_statements_node_create(pm_parser_t *parser); static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement); +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement); static size_t pm_statements_node_body_length(pm_statements_node_t *node); @@ -4400,7 +4554,7 @@ pm_if_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_t pm_if_node_t *node = PM_ALLOC_NODE(parser, pm_if_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_if_node_t) { { @@ -4431,10 +4585,10 @@ pm_if_node_ternary_create(pm_parser_t *parser, pm_node_t *predicate, const pm_to pm_conditional_predicate(parser, predicate, PM_CONDITIONAL_PREDICATE_TYPE_CONDITIONAL); pm_statements_node_t *if_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(if_statements, true_expression); + pm_statements_node_body_append(parser, if_statements, true_expression); pm_statements_node_t *else_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(else_statements, false_expression); + pm_statements_node_body_append(parser, else_statements, false_expression); pm_token_t end_keyword = not_provided(parser); pm_else_node_t *else_node = pm_else_node_create(parser, colon, else_statements, &end_keyword); @@ -6455,8 +6609,25 @@ pm_statements_node_body_update(pm_statements_node_t *node, pm_node_t *statement) * Append a new node to the given StatementsNode node's body. */ static void -pm_statements_node_body_append(pm_statements_node_t *node, pm_node_t *statement) { +pm_statements_node_body_append(pm_parser_t *parser, pm_statements_node_t *node, pm_node_t *statement) { pm_statements_node_body_update(node, statement); + + if (node->body.size > 0) { + const pm_node_t *previous = node->body.nodes[node->body.size - 1]; + + switch (PM_NODE_TYPE(previous)) { + case PM_BREAK_NODE: + case PM_NEXT_NODE: + case PM_REDO_NODE: + case PM_RETRY_NODE: + case PM_RETURN_NODE: + pm_parser_warn_node(parser, previous, PM_WARN_UNREACHABLE_STATEMENT); + break; + default: + break; + } + } + pm_node_list_append(&node->body, statement); pm_node_flag_set(statement, PM_NODE_FLAG_NEWLINE); } @@ -7019,7 +7190,7 @@ pm_unless_node_modifier_create(pm_parser_t *parser, pm_node_t *statement, const pm_unless_node_t *node = PM_ALLOC_NODE(parser, pm_unless_node_t); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); *node = (pm_unless_node_t) { { @@ -8400,6 +8571,10 @@ lex_global_variable(pm_parser_t *parser) { do { parser->current.end += width; } while (parser->current.end < parser->end && (width = char_is_identifier(parser, parser->current.end)) > 0); + } else if (pm_char_is_whitespace(peek(parser))) { + // If we get here, then we have a $ followed by whitespace, + // which is not allowed. + pm_parser_err_token(parser, &parser->current, PM_ERR_GLOBAL_VARIABLE_BARE); } else { // If we get here, then we have a $ followed by something that // isn't recognized as a global variable. @@ -9430,15 +9605,23 @@ lex_embdoc(pm_parser_t *parser) { pm_comment_t *comment = parser_comment(parser, PM_COMMENT_EMBDOC); if (comment == NULL) return PM_TOKEN_EOF; - // Now, loop until we find the end of the embedded documentation or the end of - // the file. + // Now, loop until we find the end of the embedded documentation or the end + // of the file. while (parser->current.end + 4 <= parser->end) { parser->current.start = parser->current.end; - // If we've hit the end of the embedded documentation then we'll return that - // token here. - if (memcmp(parser->current.end, "=end", 4) == 0 && - (parser->current.end + 4 == parser->end || pm_char_is_whitespace(parser->current.end[4]))) { + // If we've hit the end of the embedded documentation then we'll return + // that token here. + if ( + (memcmp(parser->current.end, "=end", 4) == 0) && + ( + (parser->current.end + 4 == parser->end) || // end of file + pm_char_is_whitespace(parser->current.end[4]) || // whitespace + (parser->current.end[4] == '\0') || // NUL or end of script + (parser->current.end[4] == '\004') || // ^D + (parser->current.end[4] == '\032') // ^Z + ) + ) { const uint8_t *newline = next_newline(parser->current.end, parser->end - parser->current.end); if (newline == NULL) { @@ -10250,9 +10433,13 @@ parser_lex(pm_parser_t *parser) { // = => =~ == === =begin case '=': - if (current_token_starts_line(parser) && (parser->current.end + 5 <= parser->end) && memcmp(parser->current.end, "begin", 5) == 0 && pm_char_is_whitespace(peek_offset(parser, 5))) { + if ( + current_token_starts_line(parser) && + (parser->current.end + 5 <= parser->end) && + memcmp(parser->current.end, "begin", 5) == 0 && + (pm_char_is_whitespace(peek_offset(parser, 5)) || (peek_offset(parser, 5) == '\0')) + ) { pm_token_type_t type = lex_embdoc(parser); - if (type == PM_TOKEN_EOF) { LEX(type); } @@ -12329,7 +12516,8 @@ static pm_node_t * parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id); /** - * This is a wrapper of parse_expression, which also checks whether the resulting node is value expression. + * This is a wrapper of parse_expression, which also checks whether the + * resulting node is a value expression. */ static pm_node_t * parse_value_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { @@ -12848,7 +13036,7 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop parsing the // statements now. @@ -12902,6 +13090,8 @@ parse_statements(pm_parser_t *parser, pm_context_t context) { } context_pop(parser); + pm_void_statements_check(parser, statements); + return statements; } @@ -13134,7 +13324,6 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for if (token_begins_expression_p(parser->current.type)) { expression = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_EXPECT_ARGUMENT); } else { - // A block forwarding in a method having `...` parameter (e.g. `def foo(...); bar(&); end`) is available. pm_parser_scope_forwarding_block_check(parser, &operator); } @@ -13217,7 +13406,7 @@ parse_arguments(pm_parser_t *parser, pm_arguments_t *arguments, bool accepts_for pm_static_literals_t literals = { 0 }; pm_hash_key_static_literals_add(parser, &literals, argument); - // Finish parsing the one we are part way through + // Finish parsing the one we are part way through. pm_node_t *value = parse_value_expression(parser, PM_BINDING_POWER_DEFINED, false, PM_ERR_HASH_VALUE); argument = (pm_node_t *) pm_assoc_node_create(parser, argument, &operator, value); @@ -13477,7 +13666,6 @@ parse_parameters( update_parameter_state(parser, &parser->current, &order); parser_lex(parser); - parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_BLOCK; parser->current_scope->parameters |= PM_SCOPE_PARAMETERS_FORWARDING_ALL; pm_forwarding_parameter_node_t *param = pm_forwarding_parameter_node_create(parser, &parser->previous); @@ -13850,6 +14038,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_RESCUE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_RESCUE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_RESCUE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } pm_statements_node_t *statements = parse_statements(parser, context); @@ -13897,6 +14086,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ELSE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ELSE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ELSE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } else_statements = parse_statements(parser, context); @@ -13926,6 +14116,7 @@ parse_rescues(pm_parser_t *parser, pm_begin_node_t *parent_node, pm_rescues_type case PM_RESCUES_LAMBDA: context = PM_CONTEXT_LAMBDA_ENSURE; break; case PM_RESCUES_MODULE: context = PM_CONTEXT_MODULE_ENSURE; break; case PM_RESCUES_SCLASS: context = PM_CONTEXT_SCLASS_ENSURE; break; + default: assert(false && "unreachable"); context = PM_CONTEXT_BEGIN_RESCUE; break; } ensure_statements = parse_statements(parser, context); @@ -14014,9 +14205,8 @@ parse_block_parameters( pm_parser_local_add_token(parser, &parser->previous, 1); pm_block_local_variable_node_t *local = pm_block_local_variable_node_create(parser, &parser->previous); - if (repeated) { - pm_node_flag_set_repeated_parameter((pm_node_t *)local); - } + if (repeated) pm_node_flag_set_repeated_parameter((pm_node_t *) local); + pm_block_parameters_node_append_local(block_parameters, local); } while (accept1(parser, PM_TOKEN_COMMA)); } @@ -14118,7 +14308,7 @@ parse_block(pm_parser_t *parser) { } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser)); + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &opening, &parser->previous); pm_parser_scope_pop(parser); @@ -14145,9 +14335,14 @@ parse_arguments_list(pm_parser_t *parser, pm_arguments_t *arguments, bool accept } else { pm_accepts_block_stack_push(parser, true); parse_arguments(parser, arguments, true, PM_TOKEN_PARENTHESIS_RIGHT); - expect1(parser, PM_TOKEN_PARENTHESIS_RIGHT, PM_ERR_ARGUMENT_TERM_PAREN); - pm_accepts_block_stack_pop(parser); + if (!accept1(parser, PM_TOKEN_PARENTHESIS_RIGHT)) { + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_ARGUMENT_TERM_PAREN, pm_token_type_human(parser->current.type)); + parser->previous.start = parser->previous.end; + parser->previous.type = PM_TOKEN_MISSING; + } + + pm_accepts_block_stack_pop(parser); arguments->closing_loc = PM_LOCATION_TOKEN_VALUE(&parser->previous); } } 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)) { @@ -15091,6 +15286,7 @@ parse_method_definition_name(pm_parser_t *parser) { parser_lex(parser); return parser->previous; default: + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_DEF_NAME, pm_token_type_human(parser->current.type)); return (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->current.start, .end = parser->current.end }; } } @@ -16601,7 +16797,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // and we didn't return a multiple assignment node, then we can return a // regular parentheses node now. pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } @@ -16611,7 +16807,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // We'll do that here. context_push(parser, PM_CONTEXT_PARENS); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, statement); + pm_statements_node_body_append(parser, statements, statement); // If we didn't find a terminator and we didn't find a right // parenthesis, then this is a syntax error. @@ -16622,7 +16818,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b // Parse each statement within the parentheses. while (true) { pm_node_t *node = parse_expression(parser, PM_BINDING_POWER_STATEMENT, true, PM_ERR_CANNOT_PARSE_EXPRESSION); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); // If we're recovering from a syntax error, then we need to stop // parsing the statements now. @@ -16656,6 +16852,7 @@ 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); return (pm_node_t *) pm_parentheses_node_create(parser, &opening, (pm_node_t *) statements, &parser->previous); } case PM_TOKEN_BRACE_LEFT: { @@ -17410,6 +17607,16 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_arguments_t arguments = { 0 }; parse_arguments_list(parser, &arguments, false, accepts_command_call); + // It's possible that we've parsed a block argument through our + // call to parse_arguments_list. If we found one, we should mark it + // as invalid and destroy it, as we don't have a place for it on the + // yield node. + if (arguments.block != NULL) { + pm_parser_err_node(parser, arguments.block, PM_ERR_UNEXPECTED_BLOCK_ARGUMENT); + pm_node_destroy(parser, arguments.block); + arguments.block = NULL; + } + pm_node_t *node = (pm_node_t *) pm_yield_node_create(parser, &keyword, &arguments.opening_loc, arguments.arguments, &arguments.closing_loc); if (!parser->parsing_eval) parse_yield(parser, node); @@ -17442,7 +17649,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_CLASS_TERM); pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); @@ -17502,7 +17709,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); pm_do_loop_stack_pop(parser); @@ -17521,7 +17728,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_node_t *receiver = NULL; pm_token_t operator = not_provided(parser); - pm_token_t name = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = def_keyword.end, .end = def_keyword.end }; + pm_token_t name; // This context is necessary for lexing `...` in a bare params // correctly. It must be pushed before lexing the first param, so it @@ -17603,7 +17810,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b receiver = (pm_node_t *) pm_true_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD_FALSE: - receiver = (pm_node_t *)pm_false_node_create(parser, &identifier); + receiver = (pm_node_t *) pm_false_node_create(parser, &identifier); break; case PM_TOKEN_KEYWORD___FILE__: receiver = (pm_node_t *) pm_source_file_node_create(parser, &identifier); @@ -17625,9 +17832,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b break; } case PM_TOKEN_PARENTHESIS_LEFT: { - // The current context is `PM_CONTEXT_DEF_PARAMS`, however the inner expression - // of this parenthesis should not be processed under this context. - // Thus, the context is popped here. + // The current context is `PM_CONTEXT_DEF_PARAMS`, however + // the inner expression of this parenthesis should not be + // processed under this context. Thus, the context is popped + // here. context_pop(parser); parser_lex(parser); @@ -17644,7 +17852,8 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b operator = parser->previous; receiver = (pm_node_t *) pm_parentheses_node_create(parser, &lparen, expression, &rparen); - // To push `PM_CONTEXT_DEF_PARAMS` again is for the same reason as described the above. + // To push `PM_CONTEXT_DEF_PARAMS` again is for the same + // reason as described the above. pm_parser_scope_push(parser, true); context_push(parser, PM_CONTEXT_DEF_PARAMS); name = parse_method_definition_name(parser); @@ -17656,12 +17865,6 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b break; } - // If, after all that, we were unable to find a method name, add an - // error to the error list. - if (name.type == PM_TOKEN_MISSING) { - pm_parser_err_previous(parser, PM_ERR_DEF_NAME); - } - pm_token_t lparen; pm_token_t rparen; pm_parameters_node_t *params; @@ -17731,7 +17934,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b statement = (pm_node_t *) pm_rescue_modifier_node_create(parser, statement, &rescue_keyword, value); } - pm_statements_node_body_append((pm_statements_node_t *) statements, statement); + pm_statements_node_body_append(parser, (pm_statements_node_t *) statements, statement); pm_do_loop_stack_pop(parser); context_pop(parser); end_keyword = not_provided(parser); @@ -17767,7 +17970,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); /** @@ -18029,7 +18232,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, true); + pm_locals_order(parser, &parser->current_scope->locals, &locals, false); pm_parser_scope_pop(parser); expect1(parser, PM_TOKEN_KEYWORD_END, PM_ERR_MODULE_TERM); @@ -18795,7 +18998,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, !pm_parser_scope_toplevel_p(parser)); + pm_locals_order(parser, &parser->current_scope->locals, &locals, pm_parser_scope_toplevel_p(parser)); pm_node_t *parameters = parse_blocklike_parameters(parser, (pm_node_t *) block_parameters, &operator, &parser->previous); pm_parser_scope_pop(parser); @@ -18851,11 +19054,21 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b } } -static inline pm_node_t * +/** + * Parse a value that is going to be written to some kind of variable or method + * call. We need to handle this separately because the rescue modifier is + * permitted on the end of the these expressions, which is a deviation from its + * normal binding power. + * + * Note that this will only be called after an operator write, as in &&=, ||=, + * or any of the binary operators that can be written to a variable. + */ +static pm_node_t * parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_value_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - // Contradicting binding powers, the right-hand-side value of rthe assignment allows the `rescue` modifier. + // Contradicting binding powers, the right-hand-side value of the assignment + // allows the `rescue` modifier. if (match1(parser, PM_TOKEN_KEYWORD_RESCUE_MODIFIER)) { context_push(parser, PM_CONTEXT_RESCUE_MODIFIER); @@ -18871,14 +19084,63 @@ parse_assignment_value(pm_parser_t *parser, pm_binding_power_t previous_binding_ return value; } +/** + * When a local variable write node is the value being written in a different + * write, the local variable is considered "used". + */ +static void +parse_assignment_value_local(pm_parser_t *parser, const pm_node_t *node) { + switch (PM_NODE_TYPE(node)) { + case PM_BEGIN_NODE: { + const pm_begin_node_t *cast = (const pm_begin_node_t *) node; + if (cast->statements != NULL) parse_assignment_value_local(parser, (const pm_node_t *) cast->statements); + break; + } + case PM_LOCAL_VARIABLE_WRITE_NODE: { + const pm_local_variable_write_node_t *cast = (const pm_local_variable_write_node_t *) node; + pm_locals_read(&pm_parser_scope_find(parser, cast->depth)->locals, cast->name); + break; + } + case PM_PARENTHESES_NODE: { + const pm_parentheses_node_t *cast = (const pm_parentheses_node_t *) node; + if (cast->body != NULL) parse_assignment_value_local(parser, cast->body); + break; + } + case PM_STATEMENTS_NODE: { + const pm_statements_node_t *cast = (const pm_statements_node_t *) node; + const pm_node_t *statement; -static inline pm_node_t * + PM_NODE_LIST_FOREACH(&cast->body, index, statement) { + parse_assignment_value_local(parser, statement); + } + break; + } + default: + break; + } +} + +/** + * Parse the value (or values, through an implicit array) that is going to be + * written to some kind of variable or method call. We need to handle this + * separately because the rescue modifier is permitted on the end of the these + * expressions, which is a deviation from its normal binding power. + * + * Additionally, if the value is a local variable write node (e.g., a = a = 1), + * the "a" is marked as being used so the parser should not warn on it. + * + * Note that this will only be called after an = operator, as that is the only + * operator that allows multiple values after it. + */ +static pm_node_t * parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding_power, pm_binding_power_t binding_power, bool accepts_command_call, pm_diagnostic_id_t diag_id) { pm_node_t *value = parse_starred_expression(parser, binding_power, previous_binding_power == PM_BINDING_POWER_ASSIGNMENT ? accepts_command_call : previous_binding_power < PM_BINDING_POWER_MATCH, diag_id); - bool single_value = true; + parse_assignment_value_local(parser, value); + bool single_value = true; if (previous_binding_power == PM_BINDING_POWER_STATEMENT && (PM_NODE_TYPE_P(value, PM_SPLAT_NODE) || match1(parser, PM_TOKEN_COMMA))) { single_value = false; + pm_token_t opening = not_provided(parser); pm_array_node_t *array = pm_array_node_create(parser, &opening); @@ -18887,8 +19149,11 @@ parse_assignment_values(pm_parser_t *parser, pm_binding_power_t previous_binding while (accept1(parser, PM_TOKEN_COMMA)) { pm_node_t *element = parse_starred_expression(parser, binding_power, false, PM_ERR_ARRAY_ELEMENT); + pm_array_node_elements_append(array, element); if (PM_NODE_TYPE_P(element, PM_MISSING_NODE)) break; + + parse_assignment_value_local(parser, element); } } @@ -19173,7 +19438,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_and_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -19286,7 +19551,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ); pm_node_t *result = (pm_node_t *) pm_local_variable_or_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -19409,7 +19674,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t pm_location_t *message_loc = &cast->message_loc; pm_refute_numbered_parameter(parser, message_loc->start, message_loc->end); - pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 0); + pm_constant_id_t constant_id = pm_parser_local_add_location(parser, message_loc->start, message_loc->end, 1); pm_node_t *value = parse_assignment_value(parser, previous_binding_power, binding_power, accepts_command_call, PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR); pm_node_t *result = (pm_node_t *) pm_local_variable_operator_write_node_create(parser, (pm_node_t *) cast, &token, value, constant_id, 0); @@ -19593,7 +19858,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t break; } default: { - pm_parser_err_current(parser, PM_ERR_DEF_NAME); + PM_PARSER_ERR_TOKEN_FORMAT(parser, parser->current, PM_ERR_EXPECT_MESSAGE, pm_token_type_human(parser->current.type)); message = (pm_token_t) { .type = PM_TOKEN_MISSING, .start = parser->previous.end, .end = parser->previous.end }; } } @@ -19640,7 +19905,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_UNTIL_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_UNTIL_PREDICATE); return (pm_node_t *) pm_until_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); @@ -19648,7 +19913,7 @@ parse_expression_infix(pm_parser_t *parser, pm_node_t *node, pm_binding_power_t case PM_TOKEN_KEYWORD_WHILE_MODIFIER: { parser_lex(parser); pm_statements_node_t *statements = pm_statements_node_create(parser); - pm_statements_node_body_append(statements, node); + pm_statements_node_body_append(parser, statements, node); pm_node_t *predicate = parse_value_expression(parser, binding_power, true, PM_ERR_CONDITIONAL_WHILE_PREDICATE); return (pm_node_t *) pm_while_node_modifier_create(parser, &token, predicate, statements, PM_NODE_TYPE_P(node, PM_BEGIN_NODE) ? PM_LOOP_FLAGS_BEGIN_MODIFIER : 0); @@ -19993,7 +20258,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { (pm_node_t *) pm_global_variable_read_node_synthesized_create(parser, pm_parser_constant_id_constant(parser, "$_", 2)) ); - pm_statements_node_body_append(statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( + pm_statements_node_body_append(parser, statements, (pm_node_t *) pm_call_node_fcall_synthesized_create( parser, arguments, pm_parser_constant_id_constant(parser, "print", 5) @@ -20039,7 +20304,7 @@ wrap_statements(pm_parser_t *parser, pm_statements_node_t *statements) { } pm_statements_node_t *wrapped_statements = pm_statements_node_create(parser); - pm_statements_node_body_append(wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( + pm_statements_node_body_append(parser, wrapped_statements, (pm_node_t *) pm_while_node_synthesized_create( parser, (pm_node_t *) pm_call_node_fcall_synthesized_create(parser, arguments, pm_parser_constant_id_constant(parser, "gets", 4)), statements @@ -20065,12 +20330,19 @@ parse_program(pm_parser_t *parser) { parser_lex(parser); pm_statements_node_t *statements = parse_statements(parser, PM_CONTEXT_MAIN); - if (!statements) { + + if (statements == NULL) { statements = pm_statements_node_create(parser); + } else if (!parser->parsing_eval) { + // If we have statements, then the top-level statement should be + // explicitly checked as well. We have to do this here because + // everywhere else we check all but the last statement. + assert(statements->body.size > 0); + pm_void_statement_check(parser, statements->body.nodes[statements->body.size - 1]); } pm_constant_id_list_t locals; - pm_locals_order(parser, &parser->current_scope->locals, &locals, false); + pm_locals_order(parser, &parser->current_scope->locals, &locals, true); pm_parser_scope_pop(parser); // If this is an empty file, then we're still going to parse all of the @@ -20517,7 +20789,7 @@ pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { pm_node_t *node = pm_parse(&parser); pm_node_destroy(&parser, node); - bool result = parser.error_list.size == 0 && parser.warning_list.size == 0; + bool result = parser.error_list.size == 0; pm_parser_free(&parser); pm_options_free(&options); diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index ac7ab4fff3c65d..36d5d5432df696 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -10,7 +10,7 @@ module Prism # The minor version of prism that we are expecting to find in the serialized # strings. - MINOR_VERSION = 24 + MINOR_VERSION = 25 # The patch version of prism that we are expecting to find in the serialized # strings. diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index a3aa7902879466..97ad451890a2a2 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -99,13 +99,13 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_IN] = { "unexpected `in` keyword in arguments", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&` when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_NO_FORWARDING_AMP] = { "unexpected `&`; no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_ARGUMENT_TERM_PAREN] = { "expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_ARGUMENT_TERM_PAREN] = { "unexpected %s; expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected `{` after a method call without parenthesis", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX }, @@ -144,15 +144,14 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_DEF_NAME] = { "expected a method name", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_DEF_NAME_AFTER_RECEIVER] = { "expected a method name after the receiver", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_PARAMS_TERM_PAREN] = { "expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_SYNTAX }, - [PM_ERR_EMBDOC_TERM] = { "could not find a terminator for the embedded document", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EMBDOC_TERM] = { "embedded document meets end of file", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_SYNTAX }, @@ -181,6 +180,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_SYNTAX }, @@ -317,6 +317,7 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_SYNTAX }, + [PM_ERR_UNEXPECTED_BLOCK_ARGUMENT] = { "block argument should not be given", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_SYNTAX }, [PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_SYNTAX }, @@ -352,7 +353,9 @@ static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = { [PM_WARN_LITERAL_IN_CONDITION_VERBOSE] = { "%sliteral in %s", PM_WARNING_LEVEL_VERBOSE }, [PM_WARN_SHEBANG_CARRIAGE_RETURN] = { "shebang line ending with \\r may cause problems", PM_WARNING_LEVEL_DEFAULT }, [PM_WARN_UNEXPECTED_CARRIAGE_RETURN] = { "encountered \\r in middle of line, treated as a mere space", PM_WARNING_LEVEL_DEFAULT }, - [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE } + [PM_WARN_UNREACHABLE_STATEMENT] = { "statement not reached", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE }, + [PM_WARN_VOID_STATEMENT] = { "possibly useless use of %.*s in void context", PM_WARNING_LEVEL_VERBOSE } }; /** diff --git a/prism/templates/src/token_type.c.erb b/prism/templates/src/token_type.c.erb index 493d46f1880652..1aeecd72b24acb 100644 --- a/prism/templates/src/token_type.c.erb +++ b/prism/templates/src/token_type.c.erb @@ -208,7 +208,7 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_KEYWORD_RESCUE: return "'rescue'"; case PM_TOKEN_KEYWORD_RESCUE_MODIFIER: - return "'rescue'"; + return "'rescue' modifier"; case PM_TOKEN_KEYWORD_RETRY: return "'retry'"; case PM_TOKEN_KEYWORD_RETURN: @@ -326,13 +326,13 @@ pm_token_type_human(pm_token_type_t token_type) { case PM_TOKEN_STAR_STAR_EQUAL: return "'**='"; case PM_TOKEN_STRING_BEGIN: - return "string beginning"; + return "string literal"; case PM_TOKEN_STRING_CONTENT: return "string content"; case PM_TOKEN_STRING_END: return "string ending"; case PM_TOKEN_SYMBOL_BEGIN: - return "symbol beginning"; + return "symbol literal"; case PM_TOKEN_TILDE: return "'~'"; case PM_TOKEN_UAMPERSAND: diff --git a/prism/version.h b/prism/version.h index 237796815f95a7..c96fe6882f16a7 100644 --- a/prism/version.h +++ b/prism/version.h @@ -14,7 +14,7 @@ /** * The minor version of the Prism library as an int. */ -#define PRISM_VERSION_MINOR 24 +#define PRISM_VERSION_MINOR 25 /** * The patch version of the Prism library as an int. @@ -24,6 +24,6 @@ /** * The version of the Prism library as a constant string. */ -#define PRISM_VERSION "0.24.0" +#define PRISM_VERSION "0.25.0" #endif diff --git a/prism_compile.c b/prism_compile.c index a26962cb942427..26d94e979d2458 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -289,6 +289,35 @@ parse_string_encoded(const pm_scope_node_t *scope_node, const pm_node_t *node, c return rb_enc_str_new((const char *) pm_string_source(string), pm_string_length(string), encoding); } +static inline VALUE +parse_static_literal_string(rb_iseq_t *iseq, const pm_scope_node_t *scope_node, const pm_node_t *node, const pm_string_t *string) +{ + rb_encoding *encoding; + + if (node->flags & PM_ENCODING_FLAGS_FORCED_BINARY_ENCODING) { + encoding = rb_ascii8bit_encoding(); + } + else if (node->flags & PM_ENCODING_FLAGS_FORCED_UTF8_ENCODING) { + encoding = rb_utf8_encoding(); + } + else { + encoding = scope_node->encoding; + } + + VALUE value = rb_enc_interned_str((const char *) pm_string_source(string), pm_string_length(string), encoding); + rb_enc_str_coderange(value); + + if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) { + int line_number = pm_node_line_number(scope_node->parser, node); + VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX(line_number)); + value = rb_str_dup(value); + rb_ivar_set(value, id_debug_created_info, rb_obj_freeze(debug_info)); + rb_str_freeze(value); + } + + return value; +} + static inline ID parse_string_symbol(const pm_scope_node_t *scope_node, const pm_symbol_node_t *symbol) { @@ -572,7 +601,7 @@ pm_source_file_value(const pm_source_file_node_t *node, const pm_scope_node_t *s if (length > 0) { rb_encoding *filepath_encoding = scope_node->filepath_encoding != NULL ? scope_node->filepath_encoding : rb_utf8_encoding(); - return rb_fstring(rb_enc_str_new((const char *) pm_string_source(filepath), length, filepath_encoding)); + return rb_enc_interned_str((const char *) pm_string_source(filepath), length, filepath_encoding); } else { return rb_fstring_lit(""); @@ -688,9 +717,8 @@ pm_static_literal_value(rb_iseq_t *iseq, const pm_node_t *node, const pm_scope_n case PM_SOURCE_LINE_NODE: return INT2FIX(pm_node_line_number(scope_node->parser, node)); case PM_STRING_NODE: { - VALUE string = parse_string_encoded(scope_node, node, &((const pm_string_node_t *) node)->unescaped); - int line_number = pm_node_line_number(scope_node->parser, node); - return pm_static_literal_string(iseq, string, line_number); + const pm_string_node_t *cast = (const pm_string_node_t *) node; + return parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); } case PM_SYMBOL_NODE: return ID2SYM(parse_string_symbol(scope_node, (const pm_symbol_node_t *) node)); @@ -4290,10 +4318,7 @@ pm_compile_case_node_dispatch(rb_iseq_t *iseq, VALUE dispatch, const pm_node_t * break; case PM_STRING_NODE: { const pm_string_node_t *cast = (const pm_string_node_t *) node; - VALUE string = parse_string_encoded(scope_node, node, &cast->unescaped); - - int line_number = pm_node_line_number(scope_node->parser, node); - key = pm_static_literal_string(iseq, string, line_number); + key = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); break; } default: @@ -4684,7 +4709,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, switch (method_id) { case idUMinus: { if (pm_opt_str_freeze_p(iseq, cast)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); PUSH_INSN2(ret, location, opt_str_uminus, value, new_callinfo(iseq, idUMinus, 0, 0, NULL, FALSE)); return; } @@ -4692,7 +4717,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } case idFreeze: { if (pm_opt_str_freeze_p(iseq, cast)) { - VALUE value = rb_fstring(parse_string_encoded(scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, cast->receiver, &((const pm_string_node_t * ) cast->receiver)->unescaped); PUSH_INSN2(ret, location, opt_str_freeze, value, new_callinfo(iseq, idFreeze, 0, 0, NULL, FALSE)); return; } @@ -4701,7 +4726,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, case idAREF: { if (pm_opt_aref_with_p(iseq, cast)) { const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); PM_COMPILE_NOT_POPPED(cast->receiver); PUSH_INSN2(ret, location, opt_aref_with, value, new_callinfo(iseq, idAREF, 1, 0, NULL, FALSE)); @@ -4717,7 +4742,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, case idASET: { if (pm_opt_aset_with_p(iseq, cast)) { const pm_string_node_t *string = (const pm_string_node_t *) ((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[0]; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, (const pm_node_t *) string, &string->unescaped); PM_COMPILE_NOT_POPPED(cast->receiver); PM_COMPILE_NOT_POPPED(((const pm_arguments_node_t *) cast->arguments)->arguments.nodes[1]); @@ -4942,7 +4967,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, else { if (PM_NODE_TYPE_P(condition, PM_STRING_NODE)) { const pm_string_node_t *string = (const pm_string_node_t *) condition; - VALUE value = rb_fstring(parse_string_encoded(scope_node, (const pm_node_t *) string, &string->unescaped)); + VALUE value = parse_static_literal_string(iseq, scope_node, condition, &string->unescaped); PUSH_INSN1(cond_seq, location, putobject, value); } else { @@ -5766,6 +5791,20 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, const pm_forwarding_super_node_t *cast = (const pm_forwarding_super_node_t *) node; const rb_iseq_t *block = NULL; + const rb_iseq_t *previous_block = NULL; + LABEL *retry_label = NULL; + LABEL *retry_end_l = NULL; + + if (cast->block != NULL) { + previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + + retry_label = NEW_LABEL(location.line); + retry_end_l = NEW_LABEL(location.line); + + PUSH_LABEL(ret, retry_label); + } + PUSH_INSN(ret, location, putself); int flag = VM_CALL_ZSUPER | VM_CALL_SUPER | VM_CALL_FCALL; @@ -5773,7 +5812,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, pm_scope_node_t next_scope_node; pm_scope_node_init((const pm_node_t *) cast->block, &next_scope_node, scope_node); - block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); + ISEQ_COMPILE_DATA(iseq)->current_block = block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, location.line); pm_scope_node_destroy(&next_scope_node); RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) block); } @@ -5873,8 +5912,14 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PUSH_SEQ(ret, args); PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flag, NULL, block != NULL), block); - if (popped) PUSH_INSN(ret, location, pop); + if (cast->block != NULL) { + PUSH_LABEL(ret, retry_end_l); + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, block, retry_end_l); + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + } + + if (popped) PUSH_INSN(ret, location, pop); return; } case PM_GLOBAL_VARIABLE_AND_WRITE_NODE: { @@ -6356,8 +6401,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, PM_COMPILE_NOT_POPPED(cast->value); ID method_id = pm_constant_id_lookup(scope_node, cast->operator); - int flags = VM_CALL_ARGS_SIMPLE | VM_CALL_FCALL | VM_CALL_VCALL; - PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(flags)); + PUSH_SEND_WITH_FLAG(ret, location, method_id, INT2NUM(1), INT2FIX(VM_CALL_ARGS_SIMPLE)); if (!popped) PUSH_INSN(ret, location, dup); PUSH_SETLOCAL(ret, location, local_index.index, local_index.level); @@ -6755,7 +6799,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, if (popped) PUSH_INSN(ret, location, pop); } else { - rb_raise(rb_eArgError, "Invalid next"); + COMPILE_ERROR(ERROR_ARGS "Invalid next"); return; } } @@ -8226,8 +8270,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // ^^^^^ if (!popped) { const pm_string_node_t *cast = (const pm_string_node_t *) node; - VALUE value = parse_string_encoded(scope_node, node, &cast->unescaped); - value = pm_static_literal_string(iseq, value, location.line); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); if (PM_NODE_FLAG_P(node, PM_STRING_FLAGS_FROZEN)) { PUSH_INSN1(ret, location, putobject, value); @@ -8248,8 +8291,15 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, DECL_ANCHOR(args); INIT_ANCHOR(args); - ISEQ_COMPILE_DATA(iseq)->current_block = NULL; + LABEL *retry_label = NEW_LABEL(location.line); + LABEL *retry_end_l = NEW_LABEL(location.line); + + const rb_iseq_t *previous_block = ISEQ_COMPILE_DATA(iseq)->current_block; + const rb_iseq_t *current_block; + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NULL; + + PUSH_LABEL(ret, retry_label); PUSH_INSN(ret, location, putself); int flags = 0; @@ -8257,11 +8307,11 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, int argc = pm_setup_args(cast->arguments, cast->block, &flags, &keywords, iseq, ret, scope_node, &location); flags |= VM_CALL_SUPER | VM_CALL_FCALL; - const rb_iseq_t *parent_block = ISEQ_COMPILE_DATA(iseq)->current_block; if (cast->block && PM_NODE_TYPE_P(cast->block, PM_BLOCK_NODE)) { pm_scope_node_t next_scope_node; pm_scope_node_init(cast->block, &next_scope_node, scope_node); - parent_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); + + ISEQ_COMPILE_DATA(iseq)->current_block = current_block = NEW_CHILD_ISEQ(&next_scope_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, lineno); pm_scope_node_destroy(&next_scope_node); } @@ -8270,9 +8320,13 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, } PUSH_SEQ(ret, args); - PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, parent_block != NULL), parent_block); - + PUSH_INSN2(ret, location, invokesuper, new_callinfo(iseq, 0, argc, flags, keywords, current_block != NULL), current_block); + PUSH_LABEL(ret, retry_end_l); if (popped) PUSH_INSN(ret, location, pop); + + ISEQ_COMPILE_DATA(iseq)->current_block = previous_block; + PUSH_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, current_block, retry_end_l); + return; } case PM_SYMBOL_NODE: { @@ -8352,7 +8406,7 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret, // `foo` // ^^^^^ const pm_x_string_node_t *cast = (const pm_x_string_node_t *) node; - VALUE value = parse_string_encoded(scope_node, node, &cast->unescaped); + VALUE value = parse_static_literal_string(iseq, scope_node, node, &cast->unescaped); PUSH_INSN(ret, location, putself); PUSH_INSN1(ret, location, putobject, value); @@ -8618,15 +8672,6 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) pm_scope_node_init(node, scope_node, NULL); scope_node->filepath_encoding = filepath_encoding; - // If there are errors, raise an appropriate error and free the result. - if (parser->error_list.size > 0) { - VALUE error = pm_parse_process_error(result); - - // TODO: We need to set the backtrace. - // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); - return error; - } - // Emit all of the various warnings from the parse. const pm_diagnostic_t *warning; const char *warning_filepath = (const char *) pm_string_source(&parser->filepath); @@ -8642,6 +8687,15 @@ pm_parse_process(pm_parse_result_t *result, pm_node_t *node) } } + // If there are errors, raise an appropriate error and free the result. + if (parser->error_list.size > 0) { + VALUE error = pm_parse_process_error(result); + + // TODO: We need to set the backtrace. + // rb_funcallv(error, rb_intern("set_backtrace"), 1, &path); + return error; + } + // Now set up the constant pool and intern all of the various constants into // their corresponding IDs. scope_node->encoding = rb_enc_find(parser->encoding->name); diff --git a/process.c b/process.c index 72483815d68f1b..b1f9931f06d477 100644 --- a/process.c +++ b/process.c @@ -3140,9 +3140,13 @@ f_exec(int c, const VALUE *a, VALUE _) UNREACHABLE_RETURN(Qnil); } -#define ERRMSG(str) do { if (errmsg && 0 < errmsg_buflen) strlcpy(errmsg, (str), errmsg_buflen); } while (0) -#define ERRMSG1(str, a) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a)); } while (0) -#define ERRMSG2(str, a, b) do { if (errmsg && 0 < errmsg_buflen) snprintf(errmsg, errmsg_buflen, (str), (a), (b)); } while (0) +#define ERRMSG(str) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)strlcpy(errmsg, (str), errmsg_buflen) : (void)0) + +#define ERRMSG_FMT(...) \ + ((errmsg && 0 < errmsg_buflen) ? \ + (void)snprintf(errmsg, errmsg_buflen, __VA_ARGS__) : (void)0) static int fd_get_cloexec(int fd, char *errmsg, size_t errmsg_buflen); static int fd_set_cloexec(int fd, char *errmsg, size_t errmsg_buflen); @@ -5254,7 +5258,7 @@ static rb_pid_t ruby_setsid(void) { rb_pid_t pid; - int ret; + int ret, fd; pid = getpid(); #if defined(SETPGRP_VOID) diff --git a/range.c b/range.c index 02e38d6ac94e6a..bc4c5dde9aa88b 100644 --- a/range.c +++ b/range.c @@ -828,7 +828,12 @@ sym_each_i(VALUE v, VALUE arg) * (1..4).size # => 4 * (1...4).size # => 3 * (1..).size # => Infinity - * ('a'..'z').size #=> nil + * ('a'..'z').size # => nil + * + * If +self+ is not iterable, raises an exception: + * + * (0.5..2.5).size # TypeError + * (..1).size # TypeError * * Related: Range#count. */ @@ -837,7 +842,8 @@ static VALUE range_size(VALUE range) { VALUE b = RANGE_BEG(range), e = RANGE_END(range); - if (rb_obj_is_kind_of(b, rb_cNumeric)) { + + if (RB_INTEGER_TYPE_P(b)) { if (rb_obj_is_kind_of(e, rb_cNumeric)) { return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range)); } @@ -845,10 +851,10 @@ range_size(VALUE range) return DBL2NUM(HUGE_VAL); } } - else if (NIL_P(b)) { - if (rb_obj_is_kind_of(e, rb_cNumeric)) { - return DBL2NUM(HUGE_VAL); - } + + if (!discrete_object_p(b)) { + rb_raise(rb_eTypeError, "can't iterate from %s", + rb_obj_classname(b)); } return Qnil; diff --git a/rjit_c.rb b/rjit_c.rb index 0ba33b40bf904d..b9a5bb0b5532bc 100644 --- a/rjit_c.rb +++ b/rjit_c.rb @@ -1093,6 +1093,7 @@ def C.rb_iseq_constant_body ruby2_keywords: [CType::BitField.new(1, 1), 9], anon_rest: [CType::BitField.new(1, 2), 10], anon_kwrest: [CType::BitField.new(1, 3), 11], + use_block: [CType::BitField.new(1, 4), 12], ), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, flags)")], size: [CType::Immediate.parse("unsigned int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, size)")], lead_num: [CType::Immediate.parse("int"), Primitive.cexpr!("OFFSETOF(((struct rb_iseq_constant_body *)NULL)->param, lead_num)")], diff --git a/ruby.c b/ruby.c index b7f0f5dedff9fb..fb60551c3fc050 100644 --- a/ruby.c +++ b/ruby.c @@ -151,22 +151,19 @@ enum feature_flag_bits { SEP \ X(parsetree) \ SEP \ - X(parsetree_with_comment) \ - SEP \ X(insns) \ - SEP \ - X(insns_without_opt) \ /* END OF DUMPS */ enum dump_flag_bits { dump_version_v, - dump_error_tolerant, + dump_opt_error_tolerant, + dump_opt_comment, + dump_opt_optimize, EACH_DUMPS(DEFINE_DUMP, COMMA), - dump_error_tolerant_bits = (DUMP_BIT(yydebug) | - DUMP_BIT(parsetree) | - DUMP_BIT(parsetree_with_comment)), dump_exit_bits = (DUMP_BIT(yydebug) | DUMP_BIT(syntax) | - DUMP_BIT(parsetree) | DUMP_BIT(parsetree_with_comment) | - DUMP_BIT(insns) | DUMP_BIT(insns_without_opt)) + DUMP_BIT(parsetree) | DUMP_BIT(insns)), + dump_optional_bits = (DUMP_BIT(opt_error_tolerant) | + DUMP_BIT(opt_comment) | + DUMP_BIT(opt_optimize)) }; static inline void @@ -222,6 +219,7 @@ cmdline_options_init(ruby_cmdline_options_t *opt) #elif defined(YJIT_FORCE_ENABLE) opt->features.set |= FEATURE_BIT(yjit); #endif + opt->dump |= DUMP_BIT(opt_optimize); opt->backtrace_length_limit = LONG_MIN; return opt; @@ -372,11 +370,12 @@ usage(const char *name, int help, int highlight, int columns) M("-y", ", --yydebug", "Print parser log; backward compatibility not guaranteed."), }; static const struct ruby_opt_message dumps[] = { - M("insns", "", "Instruction sequences."), - M("insns_without_opt", "", "Instruction sequences compiled with no optimization."), - M("yydebug(+error-tolerant)", "", "yydebug of yacc parser generator."), - M("parsetree(+error-tolerant)", "", "Abstract syntax tree (AST)."), - M("parsetree_with_comment(+error-tolerant)", "", "AST with comments."), + M("insns", "", "Instruction sequences."), + M("yydebug", "", "yydebug of yacc parser generator."), + M("parsetree", "", "Abstract syntax tree (AST)."), + M("-optimize", "", "Disable optimization (affects insns)."), + M("+error-tolerant", "", "Error-tolerant parsing (affects yydebug, parsetree)."), + M("+comment", "", "Add comments to AST (affects parsetree)."), }; static const struct ruby_opt_message features[] = { M("gems", "", "Rubygems (only for debugging, default: "DEFAULT_RUBYGEMS_ENABLED")."), @@ -440,39 +439,31 @@ usage(const char *name, int help, int highlight, int columns) #define rubylib_path_new rb_str_new static void -push_include(const char *path, VALUE (*filter)(VALUE)) +ruby_push_include(const char *path, VALUE (*filter)(VALUE)) { const char sep = PATH_SEP_CHAR; const char *p, *s; VALUE load_path = GET_VM()->load_path; - - p = path; - while (*p) { - while (*p == sep) - p++; - if (!*p) break; - for (s = p; *s && *s != sep; s = CharNext(s)); - rb_ary_push(load_path, (*filter)(rubylib_path_new(p, s - p))); - p = s; - } -} - #ifdef __CYGWIN__ -static void -push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) -{ - const char *p, *s; char rubylib[FILENAME_MAX]; VALUE buf = 0; +# define is_path_sep(c) ((c) == sep || (c) == ';') +#else +# define is_path_sep(c) ((c) == sep) +#endif + if (path == 0) return; p = path; while (*p) { - unsigned int len; - while (*p == ';') + long len; + while (is_path_sep(*p)) p++; if (!*p) break; - for (s = p; *s && *s != ';'; s = CharNext(s)); + for (s = p; *s && !is_path_sep(*s); s = CharNext(s)); len = s - p; +#undef is_path_sep + +#ifdef __CYGWIN__ if (*s) { if (!buf) { buf = rb_str_new(p, len); @@ -489,23 +480,14 @@ push_include_cygwin(const char *path, VALUE (*filter)(VALUE)) #else # error no cygwin_conv_path #endif - if (CONV_TO_POSIX_PATH(p, rubylib) == 0) + if (CONV_TO_POSIX_PATH(p, rubylib) == 0) { p = rubylib; - push_include(p, filter); - if (!*s) break; - p = s + 1; - } -} - -#define push_include push_include_cygwin + len = strlen(p); + } #endif - -void -ruby_push_include(const char *path, VALUE (*filter)(VALUE)) -{ - if (path == 0) - return; - push_include(path, filter); + rb_ary_push(load_path, (*filter)(rubylib_path_new(p, len))); + p = s; + } } static VALUE @@ -513,6 +495,7 @@ identical_path(VALUE path) { return path; } + static VALUE locale_path(VALUE path) { @@ -978,7 +961,7 @@ name_match_p(const char *name, const char *str, size_t len) if (*str != '-' && *str != '_') return 0; while (ISALNUM(*name)) name++; if (*name != '-' && *name != '_') return 0; - ++name; + if (!*++name) return 1; ++str; if (--len == 0) return 1; } @@ -1098,21 +1081,45 @@ memtermspn(const char *str, char term, int len) static const char additional_opt_sep = '+'; static unsigned int -dump_additional_option(const char *str, int len, unsigned int bits, const char *name) +dump_additional_option_flag(const char *str, int len, unsigned int bits, bool set) +{ +#define SET_DUMP_OPT(bit) if (NAME_MATCH_P(#bit, str, len)) { \ + return set ? (bits | DUMP_BIT(opt_ ## bit)) : (bits & ~DUMP_BIT(opt_ ## bit)); \ + } + SET_DUMP_OPT(error_tolerant); + SET_DUMP_OPT(comment); + SET_DUMP_OPT(optimize); +#undef SET_DUMP_OPT + rb_warn("don't know how to dump with%s '%.*s'", set ? "" : "out", len, str); + return bits; +} + +static unsigned int +dump_additional_option(const char *str, int len, unsigned int bits) { int w; for (; len-- > 0 && *str++ == additional_opt_sep; len -= w, str += w) { w = memtermspn(str, additional_opt_sep, len); -#define SET_ADDITIONAL(bit) if (NAME_MATCH_P(#bit, str, w)) { \ - if (bits & DUMP_BIT(bit)) \ - rb_warn("duplicate option to dump %s: '%.*s'", name, w, str); \ - bits |= DUMP_BIT(bit); \ - continue; \ + bool set = true; + if (*str == '-' || *str == '+') { + set = *str++ == '+'; + --w; } - if (dump_error_tolerant_bits & bits) { - SET_ADDITIONAL(error_tolerant); + else { + int n = memtermspn(str, '-', w); + if (str[n] == '-') { + if (NAME_MATCH_P("with", str, n)) { + str += n; + w -= n; + } + else if (NAME_MATCH_P("without", str, n)) { + set = false; + str += n; + w -= n; + } + } } - rb_warn("don't know how to dump %s with '%.*s'", name, w, str); + bits = dump_additional_option_flag(str, w, bits, set); } return bits; } @@ -1121,12 +1128,17 @@ static void dump_option(const char *str, int len, void *arg) { static const char list[] = EACH_DUMPS(LITERAL_NAME_ELEMENT, ", "); + unsigned int *bits_ptr = (unsigned int *)arg; + if (*str == '+' || *str == '-') { + bool set = *str++ == '+'; + *bits_ptr = dump_additional_option_flag(str, --len, *bits_ptr, set); + return; + } int w = memtermspn(str, additional_opt_sep, len); #define SET_WHEN_DUMP(bit) \ - if (NAME_MATCH_P(#bit, (str), (w))) { \ - *(unsigned int *)arg |= \ - dump_additional_option(str + w, len - w, DUMP_BIT(bit), #bit); \ + if (NAME_MATCH_P(#bit "-", (str), (w))) { \ + *bits_ptr = dump_additional_option(str + w, len - w, *bits_ptr | DUMP_BIT(bit)); \ return; \ } EACH_DUMPS(SET_WHEN_DUMP, ;); @@ -2049,12 +2061,13 @@ process_script(ruby_cmdline_options_t *opt) { rb_ast_t *ast; VALUE parser = rb_parser_new(); + const unsigned int dump = opt->dump; - if (opt->dump & DUMP_BIT(yydebug)) { + if (dump & DUMP_BIT(yydebug)) { rb_parser_set_yydebug(parser, Qtrue); } - if (opt->dump & DUMP_BIT(error_tolerant)) { + if ((dump & dump_exit_bits) && (dump & DUMP_BIT(opt_error_tolerant))) { rb_parser_error_tolerant(parser); } @@ -2338,8 +2351,6 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) #ifdef _WIN32 translit_char_bin(RSTRING_PTR(opt->script_name), '\\', '/'); -#elif defined DOSISH - translit_char(RSTRING_PTR(opt->script_name), '\\', '/'); #endif ruby_gc_set_params(); @@ -2497,10 +2508,10 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) if (!dump) return Qtrue; } - if (dump & (DUMP_BIT(parsetree)|DUMP_BIT(parsetree_with_comment))) { + if (dump & DUMP_BIT(parsetree)) { VALUE tree; if (result.ast) { - int comment = dump & DUMP_BIT(parsetree_with_comment); + int comment = opt->dump & DUMP_BIT(opt_comment); tree = rb_parser_dump_tree(result.ast->body.root, comment); } else { @@ -2508,7 +2519,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } rb_io_write(rb_stdout, tree); rb_io_flush(rb_stdout); - dump &= ~DUMP_BIT(parsetree)&~DUMP_BIT(parsetree_with_comment); + dump &= ~DUMP_BIT(parsetree); if (!dump) { dispose_result(); return Qtrue; @@ -2533,7 +2544,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) GetBindingPtr(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), toplevel_binding); const struct rb_block *base_block = toplevel_context(toplevel_binding); const rb_iseq_t *parent = vm_block_iseq(base_block); - bool optimize = !(dump & DUMP_BIT(insns_without_opt)); + bool optimize = (opt->dump & DUMP_BIT(opt_optimize)) != 0; if (!result.ast) { pm_parse_result_t *pm = &result.prism; @@ -2547,7 +2558,7 @@ process_options(int argc, char **argv, ruby_cmdline_options_t *opt) } } - if (dump & (DUMP_BIT(insns) | DUMP_BIT(insns_without_opt))) { + if (dump & DUMP_BIT(insns)) { rb_io_write(rb_stdout, rb_iseq_disasm((const rb_iseq_t *)iseq)); rb_io_flush(rb_stdout); dump &= ~DUMP_BIT(insns); @@ -2581,7 +2592,7 @@ struct load_file_arg { VALUE f; }; -VALUE rb_script_lines_for(VALUE path); +void rb_set_script_lines_for(VALUE vparser, VALUE path); static VALUE load_file_internal(VALUE argp_v) @@ -2686,10 +2697,7 @@ load_file_internal(VALUE argp_v) rb_parser_set_options(parser, opt->do_print, opt->do_loop, opt->do_line, opt->do_split); - VALUE lines = rb_script_lines_for(orig_fname); - if (!NIL_P(lines)) { - rb_parser_set_script_lines(parser, lines); - } + rb_set_script_lines_for(parser, orig_fname); if (NIL_P(f)) { f = rb_str_new(0, 0); diff --git a/ruby_parser.c b/ruby_parser.c index eabf2cf3e0fefe..5d9c6c938f35db 100644 --- a/ruby_parser.c +++ b/ruby_parser.c @@ -195,12 +195,6 @@ enc_codelen(int c, void *enc) return rb_enc_codelen(c, (rb_encoding *)enc); } -static VALUE -enc_str_buf_cat(VALUE str, const char *ptr, long len, void *enc) -{ - return rb_enc_str_buf_cat(str, ptr, len, (rb_encoding *)enc); -} - static int enc_mbcput(unsigned int c, void *buf, void *enc) { @@ -297,12 +291,6 @@ rbool(VALUE v) return RBOOL(v); } -static int -undef_p(VALUE v) -{ - return RB_UNDEF_P(v); -} - static int rtest(VALUE obj) { @@ -357,18 +345,6 @@ rb_errno_ptr2(void) return rb_errno_ptr(); } -static int -fixnum_p(VALUE obj) -{ - return (int)RB_FIXNUM_P(obj); -} - -static int -symbol_p(VALUE obj) -{ - return (int)RB_SYMBOL_P(obj); -} - static void * zalloc(size_t elemsiz) { @@ -447,17 +423,12 @@ static const rb_parser_config_t rb_global_parser_config = { .compile_callback = rb_suppress_tracing, .reg_named_capture_assign = reg_named_capture_assign, - .fixnum_p = fixnum_p, - .symbol_p = symbol_p, - .attr_get = rb_attr_get, .ary_new = rb_ary_new, .ary_push = rb_ary_push, .ary_new_from_args = rb_ary_new_from_args, .ary_unshift = rb_ary_unshift, - .ary_new2 = rb_ary_new2, - .ary_clear = rb_ary_clear, .ary_modify = rb_ary_modify, .array_len = rb_array_len, .array_aref = RARRAY_AREF, @@ -483,8 +454,6 @@ static const rb_parser_config_t rb_global_parser_config = { .str_cat_cstr = rb_str_cat_cstr, .str_subseq = rb_str_subseq, .str_new_frozen = rb_str_new_frozen, - .str_buf_new = rb_str_buf_new, - .str_buf_cat = rb_str_buf_cat, .str_modify = rb_str_modify, .str_set_len = rb_str_set_len, .str_cat = rb_str_cat, @@ -494,8 +463,6 @@ static const rb_parser_config_t rb_global_parser_config = { .str_to_interned_str = rb_str_to_interned_str, .is_ascii_string = is_ascii_string2, .enc_str_new = enc_str_new, - .enc_str_buf_cat = enc_str_buf_cat, - .str_buf_append = rb_str_buf_append, .str_vcatf = rb_str_vcatf, .string_value_cstr = rb_string_value_cstr, .rb_sprintf = rb_sprintf, @@ -505,7 +472,6 @@ static const rb_parser_config_t rb_global_parser_config = { .filesystem_str_new_cstr = rb_filesystem_str_new_cstr, .obj_as_string = rb_obj_as_string, - .num2int = rb_num2int_inline, .int2num = rb_int2num_inline, .stderr_tty_p = rb_stderr_tty_p, @@ -586,13 +552,11 @@ static const rb_parser_config_t rb_global_parser_config = { .strtod = ruby_strtod, .rbool = rbool, - .undef_p = undef_p, .rtest = rtest, .nil_p = nil_p, .qnil = Qnil, .qtrue = Qtrue, .qfalse = Qfalse, - .qundef = Qundef, .eArgError = arg_error, .long2int = rb_long2int, @@ -658,12 +622,12 @@ rb_parser_set_context(VALUE vparser, const struct rb_iseq_struct *base, int main } void -rb_parser_set_script_lines(VALUE vparser, VALUE lines) +rb_parser_set_script_lines(VALUE vparser) { struct ruby_parser *parser; TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); - rb_ruby_parser_set_script_lines(parser->parser_params, lines); + rb_ruby_parser_set_script_lines(parser->parser_params); } void @@ -763,10 +727,49 @@ rb_parser_set_yydebug(VALUE vparser, VALUE flag) rb_ruby_parser_set_yydebug(parser->parser_params, RTEST(flag)); return flag; } + +void +rb_set_script_lines_for(VALUE vparser, VALUE path) +{ + struct ruby_parser *parser; + VALUE hash; + ID script_lines; + CONST_ID(script_lines, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines)) return; + hash = rb_const_get_at(rb_cObject, script_lines); + if (RB_TYPE_P(hash, T_HASH)) { + rb_hash_aset(hash, path, Qtrue); + TypedData_Get_Struct(vparser, struct ruby_parser, &ruby_parser_data_type, parser); + rb_ruby_parser_set_script_lines(parser->parser_params); + } +} #endif +VALUE +rb_parser_build_script_lines_from(rb_parser_ary_t *lines) +{ + int i; + if (lines->data_type != PARSER_ARY_DATA_SCRIPT_LINE) { + rb_bug("unexpected rb_parser_ary_data_type (%d) for script lines", lines->data_type); + } + VALUE script_lines = rb_ary_new_capa(lines->len); + for (i = 0; i < lines->len; i++) { + rb_parser_string_t *str = (rb_parser_string_t *)lines->data[i]; + rb_ary_push(script_lines, rb_enc_str_new(str->ptr, str->len, str->enc)); + } + return script_lines; +} + VALUE rb_str_new_parser_string(rb_parser_string_t *str) +{ + VALUE string = rb_enc_interned_str(str->ptr, str->len, str->enc); + rb_enc_str_coderange(string); + return string; +} + +VALUE +rb_str_new_mutable_parser_string(rb_parser_string_t *str) { return rb_enc_str_new(str->ptr, str->len, str->enc); } @@ -963,15 +966,17 @@ rb_node_encoding_val(const NODE *node) return rb_enc_from_encoding(RNODE_ENCODING(node)->enc); } -VALUE -rb_script_lines_for(VALUE path) -{ - VALUE hash, lines; - ID script_lines; - CONST_ID(script_lines, "SCRIPT_LINES__"); - if (!rb_const_defined_at(rb_cObject, script_lines)) return Qnil; - hash = rb_const_get_at(rb_cObject, script_lines); - if (!RB_TYPE_P(hash, T_HASH)) return Qnil; - rb_hash_aset(hash, path, lines = rb_ary_new()); - return lines; +void +rb_parser_aset_script_lines_for(VALUE path, rb_parser_ary_t *lines) +{ + VALUE hash, script_lines; + ID script_lines_id; + if (NIL_P(path) || !lines || FIXNUM_P((VALUE)lines)) return; + CONST_ID(script_lines_id, "SCRIPT_LINES__"); + if (!rb_const_defined_at(rb_cObject, script_lines_id)) return; + hash = rb_const_get_at(rb_cObject, script_lines_id); + if (!RB_TYPE_P(hash, T_HASH)) return; + if (rb_hash_lookup(hash, path) == Qnil) return; + script_lines = rb_parser_build_script_lines_from(lines); + rb_hash_aset(hash, path, script_lines); } diff --git a/rubyparser.h b/rubyparser.h index 2b47d08490355f..d36e8dcede032f 100644 --- a/rubyparser.h +++ b/rubyparser.h @@ -219,8 +219,16 @@ typedef struct rb_parser_ast_token { /* * Array-like object for parser */ +typedef void* rb_parser_ary_data; + +enum rb_parser_ary_data_type { + PARSER_ARY_DATA_AST_TOKEN, + PARSER_ARY_DATA_SCRIPT_LINE +}; + typedef struct rb_parser_ary { - rb_parser_ast_token_t **data; + enum rb_parser_ary_data_type data_type; + rb_parser_ary_data *data; long len; // current size long capa; // capacity } rb_parser_ary_t; @@ -1178,7 +1186,7 @@ typedef struct RNode_ERROR { #define RNODE_ENCODING(node) ((struct RNode_ENCODING *)(node)) /* FL : 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: FINALIZE, 8: UNUSED, 9: UNUSED, 10: EXIVAR, 11: FREEZE */ -/* NODE_FL: 0..4: T_TYPES, 5: KEEP_WB, 6: PROMOTED, 7: NODE_FL_NEWLINE, +/* NODE_FL: 0..4: UNUSED, 5: UNUSED, 6: UNUSED, 7: NODE_FL_NEWLINE, * 8..14: nd_type, * 15..: nd_line */ @@ -1201,10 +1209,10 @@ typedef struct node_buffer_struct node_buffer_t; /* T_IMEMO/ast */ typedef struct rb_ast_body_struct { const NODE *root; - VALUE script_lines; + rb_parser_ary_t *script_lines; // script_lines is either: // - a Fixnum that represents the line count of the original source, or - // - an Array that contains the lines of the original source + // - an rb_parser_ary_t* that contains the lines of the original source signed int frozen_string_literal:2; /* -1: not specified, 0: false, 1: true */ signed int coverage_enabled:2; /* -1: not specified, 0: false, 1: true */ } rb_ast_body_t; @@ -1248,9 +1256,6 @@ typedef struct rb_parser_config_struct { VALUE (*compile_callback)(VALUE (*func)(VALUE), VALUE arg); NODE *(*reg_named_capture_assign)(struct parser_params* p, VALUE regexp, const rb_code_location_t *loc); - int (*fixnum_p)(VALUE); - int (*symbol_p)(VALUE); - /* Variable */ VALUE (*attr_get)(VALUE obj, ID id); @@ -1259,8 +1264,6 @@ typedef struct rb_parser_config_struct { VALUE (*ary_push)(VALUE ary, VALUE elem); VALUE (*ary_new_from_args)(long n, ...); VALUE (*ary_unshift)(VALUE ary, VALUE item); - VALUE (*ary_new2)(long capa); // ary_new_capa - VALUE (*ary_clear)(VALUE ary); void (*ary_modify)(VALUE ary); long (*array_len)(VALUE a); VALUE (*array_aref)(VALUE, long); @@ -1289,8 +1292,6 @@ typedef struct rb_parser_config_struct { VALUE (*str_cat_cstr)(VALUE str, const char *ptr); VALUE (*str_subseq)(VALUE str, long beg, long len); VALUE (*str_new_frozen)(VALUE orig); - VALUE (*str_buf_new)(long capa); - VALUE (*str_buf_cat)(VALUE, const char*, long); void (*str_modify)(VALUE str); void (*str_set_len)(VALUE str, long len); VALUE (*str_cat)(VALUE str, const char *ptr, long len); @@ -1300,8 +1301,6 @@ typedef struct rb_parser_config_struct { VALUE (*str_to_interned_str)(VALUE); int (*is_ascii_string)(VALUE str); VALUE (*enc_str_new)(const char *ptr, long len, rb_encoding *enc); - VALUE (*enc_str_buf_cat)(VALUE str, const char *ptr, long len, rb_encoding *enc); - VALUE (*str_buf_append)(VALUE str, VALUE str2); RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 0) VALUE (*str_vcatf)(VALUE str, const char *fmt, va_list ap); char *(*string_value_cstr)(volatile VALUE *ptr); @@ -1314,7 +1313,6 @@ typedef struct rb_parser_config_struct { VALUE (*obj_as_string)(VALUE); /* Numeric */ - int (*num2int)(VALUE val); VALUE (*int2num)(int v); /* IO */ @@ -1409,13 +1407,11 @@ typedef struct rb_parser_config_struct { /* Misc */ VALUE (*rbool)(VALUE); - int (*undef_p)(VALUE); int (*rtest)(VALUE obj); int (*nil_p)(VALUE obj); VALUE qnil; VALUE qtrue; VALUE qfalse; - VALUE qundef; VALUE (*eArgError)(void); int (*long2int)(long); diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index e2f5bbf42fad0e..36e286793b6d73 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -175,6 +175,61 @@ end end + describe "with --git and --glob" do + it "adds dependency with specified git source" do + bundle "add foo --git=#{lib_path("foo-2.0")} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --branch and --glob" do + before do + update_git "foo", "2.0", branch: "test" + end + + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --branch=test --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2.0", :git => "#{lib_path("foo-2.0")}", :branch => "test", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --git and --ref and --glob" do + it "adds dependency with specified git source and branch" do + bundle "add foo --git=#{lib_path("foo-2.0")} --ref=#{revision_for(lib_path("foo-2.0"))} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "foo", "~> 2\.0", :git => "#{lib_path("foo-2.0")}", :ref => "#{revision_for(lib_path("foo-2.0"))}", :glob => "\./\*\.gemspec"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --github and --glob" do + it "adds dependency with specified github source", :realworld do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --branch --and glob" do + it "adds dependency with specified github source and branch", :realworld do + bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :branch => "master", :glob => "\.\/\*\.gemspec"}) + end + end + + describe "with --github and --ref and --glob" do + it "adds dependency with specified github source and ref", :realworld do + bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", :github => "ruby\/rake", :ref => "5c60da8", :glob => "\.\/\*\.gemspec"}) + end + end + describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 61c513ed723d76..20c2f1fd2641f3 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -212,14 +212,42 @@ def exec(command, args) end end - it "installs from a path source" do - build_lib "path_plugin" do |s| - s.write "plugins.rb" + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") end - bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" - expect(out).to include("Installed plugin path_plugin") - plugin_should_be_installed("path_plugin") + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" + + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory + end end context "Gemfile eval" do @@ -291,6 +319,21 @@ def exec(command, args) plugin_should_be_installed("ga-plugin") end + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "#{file_uri_for(gem_repo1)}" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + context "in deployment mode" do it "installs plugins" do install_gemfile <<-G diff --git a/spec/prism.mspec b/spec/prism.mspec index 9f5e2b2a5fdaa5..16508bd547a5b0 100644 --- a/spec/prism.mspec +++ b/spec/prism.mspec @@ -1,14 +1,15 @@ # frozen_string_literal: true +# This is turned off because when we run with --parser=prism we explicitly turn +# off experimental warnings to make sure the output is consistent. +MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") + ## Language -MSpec.register(:exclude, "Executing break from within a block works when passing through a super call") -MSpec.register(:exclude, "The defined? keyword when called with a method name in a void context warns about the void context when parsing it") MSpec.register(:exclude, "Hash literal expands an '**{}' or '**obj' element with the last key/value pair taking precedence") MSpec.register(:exclude, "Hash literal expands an '**{}' and warns when finding an additional duplicate key afterwards") MSpec.register(:exclude, "Hash literal merges multiple nested '**obj' in Hash literals") MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes") MSpec.register(:exclude, "Hash literal raises a SyntaxError at parse time when Symbol key with invalid bytes and 'key: value' syntax used") -MSpec.register(:exclude, "The next statement in a method is invalid and raises a SyntaxError") MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation") MSpec.register(:exclude, "Regexps with encoding modifiers supports /e (EUC encoding) with interpolation /o") MSpec.register(:exclude, "Regexps with encoding modifiers preserves EUC-JP as /e encoding through interpolation") @@ -23,14 +24,9 @@ MSpec.register(:exclude, "A Symbol literal raises an SyntaxError at parse time w ## Core MSpec.register(:exclude, "IO.popen with a leading Array argument accepts a trailing Hash of Process.exec options") MSpec.register(:exclude, "IO.popen with a leading Array argument accepts an IO mode argument following the Array") -MSpec.register(:exclude, "TracePoint#eval_script is the evald source code") -MSpec.register(:exclude, "TracePoint#event returns the type of event") MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, method, path and line for a :return event") -MSpec.register(:exclude, "TracePoint#inspect returns a String showing the event, path and line for a :class event") MSpec.register(:exclude, "TracePoint.new includes multiple events when multiple event names are passed as params") MSpec.register(:exclude, "TracePoint#path equals \"(eval at __FILE__:__LINE__)\" inside an eval for :end event") -MSpec.register(:exclude, "TracePoint#self return the class object from a class event") -MSpec.register(:exclude, "Warning.[] returns default values for categories :deprecated and :experimental") ## Library MSpec.register(:exclude, "Coverage.peek_result returns the result so far") diff --git a/spec/ruby/core/range/size_spec.rb b/spec/ruby/core/range/size_spec.rb index 81ea5a3846ab37..a1fe3ce17de3d7 100644 --- a/spec/ruby/core/range/size_spec.rb +++ b/spec/ruby/core/range/size_spec.rb @@ -4,34 +4,22 @@ it "returns the number of elements in the range" do (1..16).size.should == 16 (1...16).size.should == 15 - - (1.0..16.0).size.should == 16 - (1.0...16.0).size.should == 15 - (1.0..15.9).size.should == 15 - (1.1..16.0).size.should == 15 - (1.1..15.9).size.should == 15 end it "returns 0 if last is less than first" do (16..0).size.should == 0 - (16.0..0.0).size.should == 0 - (Float::INFINITY..0).size.should == 0 end it 'returns Float::INFINITY for increasing, infinite ranges' do (0..Float::INFINITY).size.should == Float::INFINITY - (-Float::INFINITY..0).size.should == Float::INFINITY - (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY end it 'returns Float::INFINITY for endless ranges if the start is numeric' do eval("(1..)").size.should == Float::INFINITY - eval("(0.5...)").size.should == Float::INFINITY end it 'returns nil for endless ranges if the start is not numeric' do eval("('z'..)").size.should == nil - eval("([]...)").size.should == nil end ruby_version_is ""..."3.2" do @@ -43,7 +31,7 @@ end end - ruby_version_is "3.2" do + ruby_version_is "3.2"..."3.4" do it 'returns Float::INFINITY for all beginless ranges if the end is numeric' do (..1).size.should == Float::INFINITY (...0.5).size.should == Float::INFINITY @@ -58,6 +46,54 @@ end end + ruby_version_is ""..."3.4" do + it "returns the number of elements in the range" do + (1.0..16.0).size.should == 16 + (1.0...16.0).size.should == 15 + (1.0..15.9).size.should == 15 + (1.1..16.0).size.should == 15 + (1.1..15.9).size.should == 15 + end + + it "returns 0 if last is less than first" do + (16.0..0.0).size.should == 0 + (Float::INFINITY..0).size.should == 0 + end + + it 'returns Float::INFINITY for increasing, infinite ranges' do + (-Float::INFINITY..0).size.should == Float::INFINITY + (-Float::INFINITY..Float::INFINITY).size.should == Float::INFINITY + end + + it 'returns Float::INFINITY for endless ranges if the start is numeric' do + eval("(0.5...)").size.should == Float::INFINITY + end + + it 'returns nil for endless ranges if the start is not numeric' do + eval("([]...)").size.should == nil + end + end + + ruby_version_is "3.4" do + it 'raises TypeError if a range is not iterable' do + -> { (1.0..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0...16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.0..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..16.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (1.1..15.9).size }.should raise_error(TypeError, /can't iterate from/) + -> { (16.0..0.0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..0).size }.should raise_error(TypeError, /can't iterate from/) + -> { (-Float::INFINITY..Float::INFINITY).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..1).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...0.5).size }.should raise_error(TypeError, /can't iterate from/) + -> { (..nil).size }.should raise_error(TypeError, /can't iterate from/) + -> { (...'o').size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("(0.5...)").size }.should raise_error(TypeError, /can't iterate from/) + -> { eval("([]...)").size }.should raise_error(TypeError, /can't iterate from/) + end + end + it "returns nil if first and last are not Numeric" do (:a..:z).size.should be_nil ('a'..'z').size.should be_nil diff --git a/spec/ruby/language/execution_spec.rb b/spec/ruby/language/execution_spec.rb index 4e0310946dc3c5..ef1de38899809d 100644 --- a/spec/ruby/language/execution_spec.rb +++ b/spec/ruby/language/execution_spec.rb @@ -5,6 +5,45 @@ ip = 'world' `echo disc #{ip}`.should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + `test command` + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + `test #{:command}` + end + end + + called.should == true + end end describe "%x" do @@ -12,4 +51,43 @@ ip = 'world' %x(echo disc #{ip}).should == "disc world\n" end + + it "can be redefined and receive a frozen string as argument" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == true + end + + runner.instance_exec do + %x{test command} + end + + called.should == true + end + + it "the argument isn't frozen if it contains interpolation" do + called = false + runner = Object.new + + runner.singleton_class.define_method(:`) do |str| + called = true + + str.should == "test command" + str.frozen?.should == false + str << "mutated" + end + + 2.times do + runner.instance_exec do + %x{test #{:command}} + end + end + + called.should == true + end end diff --git a/string.c b/string.c index 5f61ac2b75cf87..98bf0dcbb82d11 100644 --- a/string.c +++ b/string.c @@ -88,6 +88,8 @@ VALUE rb_cSymbol; * 2: STR_SHARED (equal to ELTS_SHARED) * The string is shared. The buffer this string points to is owned by * another string (the shared root). + * 3: STR_CHILLED (will be frozen in a future version) + * The string appears frozen but can be mutated with a warning. * 5: STR_SHARED_ROOT * Other strings may point to the contents of this string. When this * flag is set, STR_SHARED must not be set. @@ -11774,7 +11776,7 @@ sym_inspect(VALUE sym) } dest[0] = ':'; - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); return str; } diff --git a/symbol.c b/symbol.c index fb32d1f840c9ae..77fa501f64ad48 100644 --- a/symbol.c +++ b/symbol.c @@ -442,8 +442,8 @@ static void set_id_entry(rb_symbols_t *symbols, rb_id_serial_t num, VALUE str, VALUE sym) { ASSERT_global_symbols_locking(symbols); - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); - RUBY_ASSERT(SYMBOL_P(sym)); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(sym, T_SYMBOL); size_t idx = num / ID_ENTRY_UNIT; @@ -505,10 +505,10 @@ get_id_serial_entry(rb_id_serial_t num, ID id, const enum id_entry_type t) if (result) { switch (t) { case ID_ENTRY_STR: - RUBY_ASSERT(BUILTIN_TYPE(result) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(result, T_STRING); break; case ID_ENTRY_SYM: - RUBY_ASSERT(SYMBOL_P(result)); + RUBY_ASSERT_BUILTIN_TYPE(result, T_SYMBOL); break; default: break; @@ -1011,11 +1011,11 @@ rb_sym2str(VALUE sym) VALUE str; if (DYNAMIC_SYM_P(sym)) { str = RSYMBOL(sym)->fstr; - RUBY_ASSERT(BUILTIN_TYPE(str) == T_STRING); + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); } else { str = rb_id2str(STATIC_SYM2ID(sym)); - RUBY_ASSERT(str == 0 || BUILTIN_TYPE(str) == T_STRING); + if (str) RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); } return str; diff --git a/template/Makefile.in b/template/Makefile.in index e0dcee1320ec67..d9a3cbc0650206 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -518,9 +518,9 @@ ext/realclean:: ext/realclean.sub .bundle/distclean:: .bundle/distclean.sub .bundle/realclean:: .bundle/realclean.sub -ext/clean.sub .bundle/clean.sub:: ext/clean.mk -ext/distclean.sub .bundle/distclean.sub:: ext/distclean.mk -ext/realclean.sub .bundle/realclean.sub:: ext/realclean.mk +ext/clean.sub:: ext/clean.mk +ext/distclean.sub:: ext/distclean.mk +ext/realclean.sub:: ext/realclean.mk ext/clean.sub ext/distclean.sub ext/realclean.sub \ .bundle/clean.sub .bundle/distclean.sub .bundle/realclean.sub:: @@ -546,6 +546,9 @@ ext/distclean ext/realclean .bundle/distclean .bundle/realclean:: find "$$@" -type d -empty -exec $(RMDIRS) {} + 2> /dev/null || true $(Q) $(RMDIRS) $(@D) 2> /dev/null || true +.bundle/realclean:: + @$(RMALL) $(tooldir)/bunlder/*.lock $(srcdir)/.bundle + clean-enc distclean-enc realclean-enc: @test -f "$(ENC_MK)" || exit 0; \ echo $(@:-enc=ing) encodings; \ diff --git a/template/prelude.c.tmpl b/template/prelude.c.tmpl index 74f6c08da7973e..dc0a143004c0a5 100644 --- a/template/prelude.c.tmpl +++ b/template/prelude.c.tmpl @@ -198,7 +198,8 @@ prelude_eval(VALUE code, VALUE name, int line) rb_ast_t *ast = prelude_ast(name, code, line); rb_iseq_eval(rb_iseq_new_with_opt(&ast->body, name, name, Qnil, line, - NULL, 0, ISEQ_TYPE_TOP, &optimization)); + NULL, 0, ISEQ_TYPE_TOP, &optimization, + Qnil)); rb_ast_dispose(ast); } COMPILER_WARNING_POP diff --git a/test/.excludes-prism/TestParse.rb b/test/.excludes-prism/TestParse.rb index 490a18d035e191..83af593b158bb6 100644 --- a/test/.excludes-prism/TestParse.rb +++ b/test/.excludes-prism/TestParse.rb @@ -1,8 +1,5 @@ -exclude(:test_disallowed_gloal_variable, "unknown") exclude(:test_dynamic_constant_assignment, "unknown") exclude(:test_else_without_rescue, "unknown") -exclude(:test_embedded_rd_error, "unknown") -exclude(:test_embedded_rd, "unknown") exclude(:test_error_def_in_argument, "unknown") exclude(:test_float, "unknown") exclude(:test_global_variable, "unknown") @@ -10,7 +7,6 @@ exclude(:test_heredoc_unterminated_interpolation, "unknown") exclude(:test_invalid_char, "unknown") exclude(:test_location_of_invalid_token, "unknown") -exclude(:test_no_blockarg, "unknown") exclude(:test_op_asgn1_with_block, "unknown") exclude(:test_parse_string, "unknown") exclude(:test_percent, "unknown") @@ -28,6 +24,5 @@ exclude(:test_unexpected_token_after_numeric, "unknown") exclude(:test_unterminated_regexp_error, "unknown") exclude(:test_unused_variable, "missing warning") -exclude(:test_void_expr_stmts_value, "missing warning") exclude(:test_void_value_in_rhs, "unknown") exclude(:test_words, "unknown") diff --git a/test/.excludes-prism/TestSyntax.rb b/test/.excludes-prism/TestSyntax.rb index b32eb1823c17a0..f8ae776930c90d 100644 --- a/test/.excludes-prism/TestSyntax.rb +++ b/test/.excludes-prism/TestSyntax.rb @@ -13,7 +13,6 @@ exclude(:test_heredoc_cr, "unknown") exclude(:test_heredoc_no_terminator, "unknown") exclude(:test_invalid_encoding_symbol, "unknown") -exclude(:test_invalid_literal_message, "unknown") exclude(:test_it, "https://github.com/ruby/prism/issues/2323") exclude(:test_keyword_invalid_name, "unknown") exclude(:test_keyword_self_reference, "unknown") @@ -21,8 +20,6 @@ exclude(:test_methoddef_endless_command, "unknown") exclude(:test_numbered_parameter, "unknown") exclude(:test_optional_self_reference, "unknown") -exclude(:test_parenthesised_statement_argument, "unknown") -exclude(:test_range_at_eol, "unknown") exclude(:test_safe_call_in_massign_lhs, "unknown") exclude(:test_syntax_error_at_newline, "unknown") exclude(:test_unassignable, "unknown") diff --git a/test/fiber/scheduler.rb b/test/fiber/scheduler.rb index 5090271db157db..3926226ca334d9 100644 --- a/test/fiber/scheduler.rb +++ b/test/fiber/scheduler.rb @@ -27,7 +27,9 @@ class Scheduler Warning[:experimental] = experimental end - def initialize + def initialize(fiber = Fiber.current) + @fiber = fiber + @readable = {} @writable = {} @waiting = {} @@ -45,6 +47,10 @@ def initialize attr :writable attr :waiting + def transfer + @fiber.transfer + end + def next_timeout _fiber, timeout = @waiting.min_by{|key, value| value} @@ -88,7 +94,7 @@ def run end selected.each do |fiber, events| - fiber.resume(events) + fiber.transfer(events) end if @waiting.any? @@ -98,7 +104,7 @@ def run waiting.each do |fiber, timeout| if fiber.alive? if timeout <= time - fiber.resume + fiber.transfer else @waiting[fiber] = timeout end @@ -114,7 +120,7 @@ def run end ready.each do |fiber| - fiber.resume + fiber.transfer end end end @@ -217,7 +223,7 @@ def io_wait(io, events, duration) @waiting[fiber] = current_time + duration end - Fiber.yield + @fiber.transfer ensure @waiting.delete(fiber) if duration @readable.delete(io) if readable @@ -254,7 +260,7 @@ def block(blocker, timeout = nil) if timeout @waiting[fiber] = current_time + timeout begin - Fiber.yield + @fiber.transfer ensure # Remove from @waiting in the case #unblock was called before the timeout expired: @waiting.delete(fiber) @@ -262,7 +268,7 @@ def block(blocker, timeout = nil) else @blocking[fiber] = true begin - Fiber.yield + @fiber.transfer ensure @blocking.delete(fiber) end @@ -290,7 +296,7 @@ def unblock(blocker, fiber) def fiber(&block) fiber = Fiber.new(blocking: false, &block) - fiber.resume + fiber.transfer return fiber end diff --git a/test/fiber/test_enumerator.rb b/test/fiber/test_enumerator.rb index 40f7d0172531dc..e9410f925c3b3a 100644 --- a/test/fiber/test_enumerator.rb +++ b/test/fiber/test_enumerator.rb @@ -42,4 +42,12 @@ def test_read_characters assert_predicate(i, :closed?) assert_predicate(o, :closed?) end + + def enumerator_fiber_is_nonblocking + enumerator = Enumerator.new do |yielder| + yielder << Fiber.current.blocking? + end + + assert_equal(false, enumerator.next) + end end diff --git a/test/fiber/test_mutex.rb b/test/fiber/test_mutex.rb index 449c49f38bc81e..2cee2cc235684b 100644 --- a/test/fiber/test_mutex.rb +++ b/test/fiber/test_mutex.rb @@ -207,7 +207,7 @@ def test_mutex_deadlock Fiber.schedule do mutex.synchronize do puts 'in synchronize' - Fiber.yield + scheduler.transfer end end diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 40551286d0397b..0a113ebd2f82d8 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -15,6 +15,7 @@ class TestIO_Console < Test::Unit::TestCase raise end PATHS.uniq! + INCLUDE_OPTS = "-I#{PATHS.join(File::PATH_SEPARATOR)}" # FreeBSD seems to hang on TTOU when running parallel tests # tested on FreeBSD 11.x. @@ -457,7 +458,7 @@ def helper def run_pty(src, n = 1) pend("PTY.spawn cannot control terminal on JRuby") if RUBY_ENGINE == 'jruby' - args = ["-I#{TestIO_Console::PATHS.join(File::PATH_SEPARATOR)}", "-rio/console", "-e", src] + args = [TestIO_Console::INCLUDE_OPTS, "-rio/console", "-e", src] args.shift if args.first == "-I" # statically linked r, w, pid = PTY.spawn(EnvUtil.rubybin, *args) rescue RuntimeError @@ -551,6 +552,7 @@ def test_noctty t2 = Tempfile.new("noctty_run") t2.close cmd = [*NOCTTY[1..-1], + TestIO_Console::INCLUDE_OPTS, '-e', 'open(ARGV[0], "w") {|f|', '-e', 'STDOUT.reopen(f)', '-e', 'STDERR.reopen(f)', diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index a8af0762a8006f..03fdd378556bbf 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -210,6 +210,76 @@ def test_irb_info_lang end end + class CustomCommandTestCase < CommandTestCase + def setup + @commands_backup = IRB::Command.commands + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + end + + def teardown + IRB::ExtendCommandBundle.class_variable_set(:@@command_override_policies, nil) + IRB::Command.instance_variable_set(:@commands, @commands_backup) + end + end + + class CommandArgTest < CustomCommandTestCase + class PrintArgCommand < IRB::Command::Base + category 'CommandTest' + description 'print_command_arg' + def execute(arg) + puts "arg=#{arg.inspect}" + end + end + + def test_arg + IRB::Command._register_with_aliases(:print_arg, PrintArgCommand, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + out, err = execute_lines("print_arg\n") + assert_empty err + assert_include(out, 'arg=""') + + out, err = execute_lines("print_arg \n") + assert_empty err + assert_include(out, 'arg=""') + + out, err = execute_lines("print_arg a r g\n") + assert_empty err + assert_include(out, 'arg="a r g"') + + out, err = execute_lines("print_arg a r g \n") + assert_empty err + assert_include(out, 'arg="a r g"') + + out, err = execute_lines("pa a r g \n") + assert_empty err + assert_include(out, 'arg="a r g"') + end + end + + class ExtendCommandBundleCompatibilityTest < CustomCommandTestCase + class FooBarCommand < IRB::Command::Base + category 'FooBarCategory' + description 'foobar_description' + def execute(_arg) + puts "FooBar executed" + end + end + + def test_def_extend_command + IRB::Command._register_with_aliases(:foobar, FooBarCommand, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + out, err = execute_lines("foobar\n") + assert_empty err + assert_include(out, "FooBar executed") + + out, err = execute_lines("fbalias\n") + assert_empty err + assert_include(out, "FooBar executed") + + out, err = execute_lines("show_cmds\n") + assert_include(out, "FooBarCategory") + assert_include(out, "foobar_description") + end + end + class MeasureTest < CommandTestCase def test_measure conf = { @@ -373,17 +443,19 @@ def test_measure_toggle } } out, err = execute_lines( - "measure :foo", - "measure :on, :bar", - "3\n", + "measure :foo\n", + "1\n", + "measure :on, :bar\n", + "2\n", "measure :off, :foo\n", - "measure :off, :bar\n", "3\n", + "measure :off, :bar\n", + "4\n", conf: conf ) assert_empty err - assert_match(/\AFOO is added\.\n=> nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out) + assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out) end def test_measure_with_proc_warning @@ -402,7 +474,6 @@ def test_measure_with_proc_warning out, err = execute_lines( "3\n", "measure do\n", - "end\n", "3\n", conf: conf, main: c @@ -554,7 +625,8 @@ def test_popws_replaces_the_current_workspace_with_the_previous_one out, err = execute_lines( "pushws Foo.new\n", "popws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -573,7 +645,8 @@ class ChwsTest < WorkspaceCommandTestCase def test_chws_replaces_the_current_workspace out, err = execute_lines( "chws #{self.class}::Foo.new\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}::Foo") @@ -582,7 +655,8 @@ def test_chws_replaces_the_current_workspace def test_chws_does_nothing_when_receiving_no_argument out, err = execute_lines( "chws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -730,18 +804,18 @@ def test_ls_grep def test_ls_grep_empty out, err = execute_lines("ls\n") assert_empty err - assert_match(/whereami/, out) - assert_match(/show_source/, out) + assert_match(/assert/, out) + assert_match(/refute/, out) [ - "ls grep: /whereami/\n", - "ls -g whereami\n", - "ls -G whereami\n", + "ls grep: /assert/\n", + "ls -g assert\n", + "ls -G assert\n", ].each do |line| out, err = execute_lines(line) assert_empty err - assert_match(/whereami/, out) - assert_not_match(/show_source/, out) + assert_match(/assert/, out) + assert_not_match(/refute/, out) end end @@ -951,4 +1025,19 @@ def test_history_grep end + class HelperMethodInsallTest < CommandTestCase + def test_helper_method_install + IRB::ExtendCommandBundle.module_eval do + def foobar + "test_helper_method_foobar" + end + end + + out, err = execute_lines("foobar.upcase") + assert_empty err + assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"') + ensure + IRB::ExtendCommandBundle.remove_method :foobar + end + end end diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 8194463931ef70..5fe7952b3d70e6 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -14,6 +14,13 @@ def doc_namespace(target, bind) IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind) end + class CommandCompletionTest < CompletionTest + def test_command_completion + assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end + end + class MethodCompletionTest < CompletionTest def test_complete_string assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase") diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index 5812ea041edb63..aff4b5b67c0eaa 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -28,35 +28,6 @@ def teardown restore_encodings end - def test_last_value - assert_nil(@context.last_value) - assert_nil(@context.evaluate('_', 1)) - obj = Object.new - @context.set_last_value(obj) - assert_same(obj, @context.last_value) - assert_same(obj, @context.evaluate('_', 1)) - end - - def test_evaluate_with_encoding_error_without_lineno - if RUBY_ENGINE == 'truffleruby' - omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" - end - - if RUBY_VERSION >= "3.4." - omit "Now raises SyntaxError" - end - - assert_raise_with_message(EncodingError, /invalid symbol/) { - @context.evaluate(%q[:"\xAE"], 1) - # The backtrace of this invalid encoding hash doesn't contain lineno. - } - end - - def test_evaluate_still_emits_warning - assert_warning("(irb):1: warning: END in method; use at_exit\n") do - @context.evaluate(%q[def foo; END {}; end], 1) - end - end def test_eval_input verbose, $VERBOSE = $VERBOSE, nil @@ -382,7 +353,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -392,7 +363,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> #{value_first_line[0..(input.winsize.last - 9)]}...\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = true @@ -402,7 +373,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("=> \n#{value}\n=> \n#{value}\n", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -412,7 +383,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -422,7 +393,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) input.reset irb.context.echo = false @@ -432,7 +403,7 @@ def test_omit_multiline_on_assignment end assert_empty err assert_equal("", out) - irb.context.evaluate('A.remove_method(:inspect)', 0) + irb.context.evaluate_expression('A.remove_method(:inspect)', 0) end end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index 966c840135c74f..84b9ee36441d21 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -42,6 +42,56 @@ class Foo assert_include output, "From: #{@ruby_file.path}:1" end + def test_underscore_stores_last_result + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "1 + 1" + type "_ + 10" + type "exit!" + end + + assert_include output, "=> 12" + end + + def test_evaluate_with_encoding_error_without_lineno + if RUBY_ENGINE == 'truffleruby' + omit "Remove me after https://github.com/ruby/prism/issues/2129 is addressed and adopted in TruffleRuby" + end + + if RUBY_VERSION >= "3.4." + omit "Now raises SyntaxError" + end + + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[:"\xAE"] + type "exit!" + end + + assert_include output, 'invalid symbol in encoding UTF-8 :"\xAE"' + # EncodingError would be wrapped with ANSI escape sequences, so we assert it separately + assert_include output, "EncodingError" + end + + def test_evaluate_still_emits_warning + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type %q[def foo; END {}; end] + type "exit!" + end + + assert_include output, '(irb):1: warning: END in method; use at_exit' + end + def test_symbol_aliases_dont_affect_ruby_syntax write_ruby <<~'RUBY' $foo = "It's a foo" diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index cf4fc12c9f4fb0..5ed8988b34928e 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -9,7 +9,7 @@ return end -require 'irb/completion' +require 'irb' require 'tempfile' require_relative './helper' @@ -54,6 +54,11 @@ def test_empty_completion assert_equal [], candidates assert_doc_namespace('(', ')', nil) end + + def test_command_completion + assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end end class TypeCompletorIntegrationTest < IntegrationTestCase diff --git a/test/prism/errors_test.rb b/test/prism/errors_test.rb index 79a38b41d068ed..679f6ed0a24d87 100644 --- a/test/prism/errors_test.rb +++ b/test/prism/errors_test.rb @@ -105,9 +105,14 @@ def test_pre_execution_context end def test_unterminated_embdoc - assert_errors expression("1"), "1\n=begin\n", [ - ["could not find a terminator for the embedded document", 2..9] - ] + message = "embedded document meets end of file" + assert_error_messages "=begin", [message] + assert_error_messages "=begin\n", [message] + + refute_error_messages "=begin\n=end" + refute_error_messages "=begin\n=end\0" + refute_error_messages "=begin\n=end\C-d" + refute_error_messages "=begin\n=end\C-z" end def test_unterminated_i_list @@ -336,7 +341,7 @@ def test_aliasing_global_variable_with_global_number_variable def test_def_with_expression_receiver_and_no_identifier assert_errors expression("def (a); end"), "def (a); end", [ ["expected a `.` or `::` after the receiver in a method definition", 7..7], - ["expected a method name", 7..7] + ["unexpected ';'; expected a method name", 7..8] ] end @@ -406,7 +411,7 @@ def test_arguments_after_block def test_arguments_binding_power_for_and assert_error_messages "foo(*bar and baz)", [ - "expected a `)` to close the arguments", + "unexpected 'and'; expected a `)` to close the arguments", "unexpected ')', expecting end-of-input", "unexpected ')', ignoring it" ] @@ -1257,9 +1262,10 @@ def test_invalid_operator_write_dot end def test_unterminated_global_variable - assert_errors expression("$"), "$", [ - ["'$' without identifiers is not allowed as a global variable name", 0..1] - ] + message = "'$' without identifiers is not allowed as a global variable name" + + assert_errors expression("$"), "$", [[message, 0..1]] + assert_errors expression("$ "), "$ ", [[message, 0..1]] end def test_invalid_global_variable_write @@ -2196,6 +2202,10 @@ def test_duplicate_pattern_hash_key refute_error_messages "case (); in [{a:1}, {a:2}]; end" end + def test_unexpected_block + assert_error_messages "def foo = yield(&:+)", ["block argument should not be given"] + end + private def assert_errors(expected, source, errors, check_valid_syntax: true) @@ -2216,7 +2226,7 @@ def assert_error_messages(source, errors) def refute_error_messages(source) assert_valid_syntax(source) - assert Prism.parse_success?(source) + assert Prism.parse_success?(source), "Expected #{source.inspect} to parse successfully" end def assert_warning_messages(source, warnings) diff --git a/test/prism/fixtures/if.txt b/test/prism/fixtures/if.txt index cede644a4c2149..4139bae5edbbd2 100644 --- a/test/prism/fixtures/if.txt +++ b/test/prism/fixtures/if.txt @@ -30,10 +30,10 @@ if type in 1 elsif type in B end -if 1 +if f1 lambda do |_| end -elsif 2 +elsif f2 lambda do |_| end else diff --git a/test/prism/fixtures/methods.txt b/test/prism/fixtures/methods.txt index 0d2286056fe30d..4bfd976edaa741 100644 --- a/test/prism/fixtures/methods.txt +++ b/test/prism/fixtures/methods.txt @@ -175,7 +175,7 @@ def f x:!a; end def foo x:%(xx); end def foo(...) - bar(&) + bar(...) end def foo(bar = (def baz(bar) = bar; 1)) = 2 diff --git a/test/prism/locals_test.rb b/test/prism/locals_test.rb index 1453e7aecddedd..0eb73f1b9c76b0 100644 --- a/test/prism/locals_test.rb +++ b/test/prism/locals_test.rb @@ -17,51 +17,11 @@ module Prism class LocalsTest < TestCase - invalid = [] - todos = [] - - # Invalid break - invalid << "break.txt" - invalid << "if.txt" - invalid << "rescue.txt" - invalid << "seattlerb/block_break.txt" - invalid << "unless.txt" - invalid << "whitequark/break.txt" - invalid << "whitequark/break_block.txt" - - # Invalid next - invalid << "next.txt" - invalid << "seattlerb/block_next.txt" - invalid << "unparser/corpus/literal/control.txt" - invalid << "whitequark/next.txt" - invalid << "whitequark/next_block.txt" - - # Invalid redo - invalid << "keywords.txt" - invalid << "whitequark/redo.txt" - - # Invalid retry - invalid << "whitequark/retry.txt" - - # Invalid yield - invalid << "seattlerb/dasgn_icky2.txt" - invalid << "seattlerb/yield_arg.txt" - invalid << "seattlerb/yield_call_assocs.txt" - invalid << "seattlerb/yield_empty_parens.txt" - invalid << "unparser/corpus/literal/yield.txt" - invalid << "whitequark/args_assocs.txt" - invalid << "whitequark/args_assocs_legacy.txt" - invalid << "whitequark/yield.txt" - invalid << "yield.txt" - - # Dead code eliminated - invalid << "whitequark/ruby_bug_10653.txt" - base = File.join(__dir__, "fixtures") - skips = invalid | todos - Dir["**/*.txt", base: base].each do |relative| - next if skips.include?(relative) + # Skip this fixture because it has a different number of locals because + # CRuby is eliminating dead code. + next if relative == "whitequark/ruby_bug_10653.txt" filepath = File.join(base, relative) define_method("test_#{relative}") { assert_locals(filepath) } diff --git a/test/prism/parser_test.rb b/test/prism/parser_test.rb index 237a4397cae7ae..79b65cf75b41cc 100644 --- a/test/prism/parser_test.rb +++ b/test/prism/parser_test.rb @@ -59,7 +59,6 @@ class ParserTest < TestCase "regex_char_width.txt", "spanning_heredoc.txt", "spanning_heredoc_newlines.txt", - "tilde_heredocs.txt", "unescaping.txt" ] @@ -74,7 +73,9 @@ class ParserTest < TestCase "comments.txt", "heredoc_with_comment.txt", "indented_file_end.txt", + "methods.txt", "strings.txt", + "tilde_heredocs.txt", "xstring_with_backslash.txt" ] diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb index 49296117bf5488..6418887147e875 100644 --- a/test/prism/ruby_api_test.rb +++ b/test/prism/ruby_api_test.rb @@ -198,6 +198,17 @@ def test_location_code_units assert_equal 7, location.end_code_units_column(Encoding::UTF_32LE) end + def test_location_chop + location = Prism.parse("foo").value.location + + assert_equal "fo", location.chop.slice + assert_equal "", location.chop.chop.chop.slice + + # Check that we don't go negative. + 10.times { location = location.chop } + assert_equal "", location.slice + end + def test_heredoc? refute parse_expression("\"foo\"").heredoc? refute parse_expression("\"foo \#{1}\"").heredoc? diff --git a/test/prism/snapshots/if.txt b/test/prism/snapshots/if.txt index b618659756a215..eb33d1699d55f8 100644 --- a/test/prism/snapshots/if.txt +++ b/test/prism/snapshots/if.txt @@ -391,9 +391,16 @@ └── @ IfNode (location: (33,0)-(42,3)) ├── if_keyword_loc: (33,0)-(33,2) = "if" ├── predicate: - │ @ IntegerNode (location: (33,3)-(33,4)) - │ ├── flags: decimal - │ └── value: 1 + │ @ CallNode (location: (33,3)-(33,5)) + │ ├── flags: variable_call, ignore_visibility + │ ├── receiver: ∅ + │ ├── call_operator_loc: ∅ + │ ├── name: :f1 + │ ├── message_loc: (33,3)-(33,5) = "f1" + │ ├── opening_loc: ∅ + │ ├── arguments: ∅ + │ ├── closing_loc: ∅ + │ └── block: ∅ ├── then_keyword_loc: ∅ ├── statements: │ @ StatementsNode (location: (34,2)-(35,5)) @@ -434,9 +441,16 @@ │ @ IfNode (location: (36,0)-(42,3)) │ ├── if_keyword_loc: (36,0)-(36,5) = "elsif" │ ├── predicate: - │ │ @ IntegerNode (location: (36,6)-(36,7)) - │ │ ├── flags: decimal - │ │ └── value: 2 + │ │ @ CallNode (location: (36,6)-(36,8)) + │ │ ├── flags: variable_call, ignore_visibility + │ │ ├── receiver: ∅ + │ │ ├── call_operator_loc: ∅ + │ │ ├── name: :f2 + │ │ ├── message_loc: (36,6)-(36,8) = "f2" + │ │ ├── opening_loc: ∅ + │ │ ├── arguments: ∅ + │ │ ├── closing_loc: ∅ + │ │ └── block: ∅ │ ├── then_keyword_loc: ∅ │ ├── statements: │ │ @ StatementsNode (location: (37,2)-(38,5)) diff --git a/test/prism/snapshots/methods.txt b/test/prism/snapshots/methods.txt index 24567d24dfaba7..22580494a46924 100644 --- a/test/prism/snapshots/methods.txt +++ b/test/prism/snapshots/methods.txt @@ -1908,21 +1908,22 @@ │ │ │ @ ForwardingParameterNode (location: (177,8)-(177,11)) │ │ └── block: ∅ │ ├── body: - │ │ @ StatementsNode (location: (178,2)-(178,7)) + │ │ @ StatementsNode (location: (178,2)-(178,10)) │ │ └── body: (length: 1) - │ │ └── @ CallNode (location: (178,2)-(178,7)) + │ │ └── @ CallNode (location: (178,2)-(178,10)) │ │ ├── flags: ignore_visibility │ │ ├── receiver: ∅ │ │ ├── call_operator_loc: ∅ │ │ ├── name: :bar │ │ ├── message_loc: (178,2)-(178,5) = "bar" │ │ ├── opening_loc: (178,5)-(178,6) = "(" - │ │ ├── arguments: ∅ - │ │ ├── closing_loc: (178,7)-(178,8) = ")" - │ │ └── block: - │ │ @ BlockArgumentNode (location: (178,6)-(178,7)) - │ │ ├── expression: ∅ - │ │ └── operator_loc: (178,6)-(178,7) = "&" + │ │ ├── arguments: + │ │ │ @ ArgumentsNode (location: (178,6)-(178,9)) + │ │ │ ├── flags: ∅ + │ │ │ └── arguments: (length: 1) + │ │ │ └── @ ForwardingArgumentsNode (location: (178,6)-(178,9)) + │ │ ├── closing_loc: (178,9)-(178,10) = ")" + │ │ └── block: ∅ │ ├── locals: [] │ ├── def_keyword_loc: (177,0)-(177,3) = "def" │ ├── operator_loc: ∅ diff --git a/test/prism/warnings_test.rb b/test/prism/warnings_test.rb index 4da2af626a29a4..7eb1bbd2e16ec3 100644 --- a/test/prism/warnings_test.rb +++ b/test/prism/warnings_test.rb @@ -24,15 +24,15 @@ def test_ambiguous_regexp end def test_equal_in_conditional - assert_warning("if a = 1; end", "should be ==") + assert_warning("if a = 1; end; a = a", "should be ==") end def test_dot_dot_dot_eol - assert_warning("foo...", "... at EOL") + assert_warning("_ = foo...", "... at EOL") assert_warning("def foo(...) = bar ...", "... at EOL") - assert_warning("foo... #", "... at EOL") - assert_warning("foo... \t\v\f\n", "... at EOL") + assert_warning("_ = foo... #", "... at EOL") + assert_warning("_ = foo... \t\v\f\n", "... at EOL") refute_warning("p foo...bar") refute_warning("p foo... bar") @@ -51,7 +51,7 @@ def test_duplicated_when_clause end def test_float_out_of_range - assert_warning("1.0e100000", "out of range") + assert_warning("_ = 1.0e100000", "out of range") end def test_integer_in_flip_flop @@ -88,6 +88,119 @@ def test_regexp_in_predicate assert_warning("if /foo\#{bar}/; end", "regex") end + def test_unused_local_variables + assert_warning("foo = 1", "unused") + + refute_warning("foo = 1", compare: false, command_line: "e") + refute_warning("foo = 1", compare: false, scopes: [[]]) + + assert_warning("def foo; bar = 1; end", "unused") + assert_warning("def foo; bar, = 1; end", "unused") + + refute_warning("def foo; bar &&= 1; end") + refute_warning("def foo; bar ||= 1; end") + refute_warning("def foo; bar += 1; end") + + refute_warning("def foo; bar = bar; end") + refute_warning("def foo; bar = bar = 1; end") + refute_warning("def foo; bar = (bar = 1); end") + refute_warning("def foo; bar = begin; bar = 1; end; end") + refute_warning("def foo; bar = (qux; bar = 1); end") + refute_warning("def foo; bar, = bar = 1; end") + refute_warning("def foo; bar, = 1, bar = 1; end") + + refute_warning("def foo(bar); end") + refute_warning("def foo(bar = 1); end") + refute_warning("def foo((bar)); end") + refute_warning("def foo(*bar); end") + refute_warning("def foo(*, bar); end") + refute_warning("def foo(*, (bar)); end") + refute_warning("def foo(bar:); end") + refute_warning("def foo(**bar); end") + refute_warning("def foo(&bar); end") + refute_warning("->(bar) {}") + refute_warning("->(; bar) {}", compare: false) + + refute_warning("def foo; bar = 1; tap { bar }; end") + refute_warning("def foo; bar = 1; tap { baz = bar; baz }; end") + end + + def test_void_statements + assert_warning("foo = 1; foo", "a variable in void") + assert_warning("@foo", "a variable in void") + assert_warning("@@foo", "a variable in void") + assert_warning("$foo", "a variable in void") + assert_warning("$+", "a variable in void") + assert_warning("$1", "a variable in void") + + assert_warning("self", "self in void") + assert_warning("nil", "nil in void") + assert_warning("true", "true in void") + assert_warning("false", "false in void") + + assert_warning("1", "literal in void") + assert_warning("1.0", "literal in void") + assert_warning("1r", "literal in void") + assert_warning("1i", "literal in void") + assert_warning(":foo", "literal in void") + assert_warning("\"foo\"", "literal in void") + assert_warning("\"foo\#{1}\"", "literal in void") + assert_warning("/foo/", "literal in void") + assert_warning("/foo\#{1}/", "literal in void") + + assert_warning("Foo", "constant in void") + assert_warning("::Foo", ":: in void") + assert_warning("Foo::Bar", ":: in void") + + assert_warning("1..2", ".. in void") + assert_warning("1..", ".. in void") + assert_warning("..2", ".. in void") + assert_warning("1...2", "... in void") + assert_warning("1...;", "... in void") + assert_warning("...2", "... in void") + + assert_warning("defined?(foo)", "defined? in void") + + assert_warning("1 + 1", "+ in void") + assert_warning("1 - 1", "- in void") + assert_warning("1 * 1", "* in void") + assert_warning("1 / 1", "/ in void") + assert_warning("1 % 1", "% in void") + assert_warning("1 | 1", "| in void") + assert_warning("1 ^ 1", "^ in void") + assert_warning("1 & 1", "& in void") + assert_warning("1 > 1", "> in void") + assert_warning("1 < 1", "< in void") + + assert_warning("1 ** 1", "** in void") + assert_warning("1 <= 1", "<= in void") + assert_warning("1 >= 1", ">= in void") + assert_warning("1 != 1", "!= in void") + assert_warning("1 == 1", "== in void") + assert_warning("1 <=> 1", "<=> in void") + + assert_warning("+foo", "+@ in void") + assert_warning("-foo", "-@ in void") + + assert_warning("def foo; @bar; @baz; end", "variable in void") + refute_warning("def foo; @bar; end") + refute_warning("@foo", compare: false, scopes: [[]]) + end + + def test_unreachable_statement + assert_warning("begin; rescue; retry; foo; end", "statement not reached") + + assert_warning("return; foo", "statement not reached") + + assert_warning("tap { break; foo }", "statement not reached") + assert_warning("tap { break 1; foo }", "statement not reached") + + assert_warning("tap { next; foo }", "statement not reached") + assert_warning("tap { next 1; foo }", "statement not reached") + + assert_warning("tap { redo; foo }", "statement not reached") + end + private def assert_warning(source, message) @@ -101,10 +214,10 @@ def assert_warning(source, message) end end - def refute_warning(source) - assert_empty Prism.parse(source).warnings + def refute_warning(source, compare: true, **options) + assert_empty Prism.parse(source, **options).warnings - if defined?(RubyVM::AbstractSyntaxTree) + if compare && defined?(RubyVM::AbstractSyntaxTree) assert_empty capture_warning { RubyVM::AbstractSyntaxTree.parse(source) } end end diff --git a/test/reline/test_ansi_with_terminfo.rb b/test/reline/test_ansi_with_terminfo.rb index 13874606598246..e1c56b9ee12b23 100644 --- a/test/reline/test_ansi_with_terminfo.rb +++ b/test/reline/test_ansi_with_terminfo.rb @@ -109,4 +109,4 @@ def test_more_emacs assert_key_binding("\e ", :em_set_mark, [:emacs]) assert_key_binding("\C-x\C-x", :em_exchange_mark, [:emacs]) end -end if Reline::Terminfo.enabled? +end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index 0dff275f6ec4ee..8f5676a1d4f170 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -789,6 +789,52 @@ def test_completion assert_line_around_cursor('foo_ba', '') end + def test_autocompletion_with_upward_navigation + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:completion_journey_up, :completion_journey_up, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + + def test_autocompletion_with_upward_navigation_and_menu_complete_backward + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:menu_complete_backward, :menu_complete_backward, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + def test_completion_with_indent @line_editor.completion_proc = proc { |word| %w{ @@ -1263,6 +1309,14 @@ def test_ed_search_next_history_with_empty assert_line_around_cursor('', '') end + def test_incremental_search_history_cancel_by_symbol_key + # ed_prev_char should move cursor left and cancel incremental search + input_keys("abc\C-r") + input_key_by_symbol(:ed_prev_char) + input_keys('d') + assert_line_around_cursor('abd', 'c') + end + # Unicode emoji test def test_ed_insert_for_include_zwj_emoji omit "This test is for UTF-8 but the locale is #{Reline.core.encoding}" if Reline.core.encoding != Encoding::UTF_8 diff --git a/test/reline/test_key_actor_vi.rb b/test/reline/test_key_actor_vi.rb index 91cbd49d74bfd5..cf3943ae379bce 100644 --- a/test/reline/test_key_actor_vi.rb +++ b/test/reline/test_key_actor_vi.rb @@ -627,6 +627,52 @@ def test_completion assert_line_around_cursor('foo_bar', '') end + def test_autocompletion_with_upward_navigation + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:completion_journey_up, :completion_journey_up, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + + def test_autocompletion_with_upward_navigation_and_menu_complete_backward + @config.autocompletion = true + @line_editor.completion_proc = proc { |word| + %w{ + Readline + Regexp + RegexpError + }.map { |i| + i.encode(@encoding) + } + } + input_keys('Re') + assert_line_around_cursor('Re', '') + input_keys("\C-i", false) + assert_line_around_cursor('Readline', '') + input_keys("\C-i", false) + assert_line_around_cursor('Regexp', '') + @line_editor.input_key(Reline::Key.new(:menu_complete_backward, :menu_complete_backward, false)) + assert_line_around_cursor('Readline', '') + ensure + @config.autocompletion = false + end + def test_completion_with_disable_completion @config.disable_completion = true @line_editor.completion_proc = proc { |word| @@ -665,6 +711,20 @@ def test_ed_move_to_beg assert_line_around_cursor('', ' abcde ABCDE ') end + def test_vi_to_column + input_keys("a一二三\C-[0") + input_keys('1|') + assert_line_around_cursor('', 'a一二三') + input_keys('2|') + assert_line_around_cursor('a', '一二三') + input_keys('3|') + assert_line_around_cursor('a', '一二三') + input_keys('4|') + assert_line_around_cursor('a一', '二三') + input_keys('9|') + assert_line_around_cursor('a一二', '三') + end + def test_vi_delete_meta input_keys("aaa bbb ccc ddd eee\C-[02w") assert_line_around_cursor('aaa bbb ', 'ccc ddd eee') @@ -693,10 +753,16 @@ def test_vi_delete_meta_with_vi_next_char end def test_vi_delete_meta_with_arg - input_keys("aaa bbb ccc\C-[02w") - assert_line_around_cursor('aaa bbb ', 'ccc') + input_keys("aaa bbb ccc ddd\C-[03w") + assert_line_around_cursor('aaa bbb ccc ', 'ddd') input_keys('2dl') - assert_line_around_cursor('aaa bbb ', 'c') + assert_line_around_cursor('aaa bbb ccc ', 'd') + input_keys('d2h') + assert_line_around_cursor('aaa bbb cc', 'd') + input_keys('2d3h') + assert_line_around_cursor('aaa ', 'd') + input_keys('dd') + assert_line_around_cursor('', '') end def test_vi_change_meta @@ -719,6 +785,45 @@ def test_vi_change_meta_with_vi_next_word assert_line_around_cursor('foo hog', 'e baz') end + def test_vi_waiting_operator_with_waiting_proc + input_keys("foo foo foo foo foo\C-[0") + input_keys('2d3fo') + assert_line_around_cursor('', ' foo foo') + input_keys('fo') + assert_line_around_cursor(' f', 'oo foo') + end + + def test_vi_waiting_operator_cancel + input_keys("aaa bbb ccc\C-[02w") + assert_line_around_cursor('aaa bbb ', 'ccc') + # dc dy should cancel delete_meta + input_keys('dch') + input_keys('dyh') + # cd cy should cancel change_meta + input_keys('cdh') + input_keys('cyh') + # yd yc should cancel yank_meta + # P should not paste yanked text because yank_meta is canceled + input_keys('ydhP') + input_keys('ychP') + assert_line_around_cursor('aa', 'a bbb ccc') + end + + def test_cancel_waiting_with_symbol_key + input_keys("aaa bbb lll\C-[0") + assert_line_around_cursor('', 'aaa bbb lll') + # ed_next_char should move cursor right and cancel vi_next_char + input_keys('f') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aa', 'a bbb lll') + # ed_next_char should move cursor right and cancel delete_meta + input_keys('d') + input_key_by_symbol(:ed_next_char) + input_keys('l') + assert_line_around_cursor('aaa ', 'bbb lll') + end + def test_unimplemented_vi_command_should_be_no_op input_keys("abc\C-[h") assert_line_around_cursor('a', 'bc') @@ -727,12 +832,16 @@ def test_unimplemented_vi_command_should_be_no_op end def test_vi_yank - input_keys("foo bar\C-[0") - assert_line_around_cursor('', 'foo bar') + input_keys("foo bar\C-[2h") + assert_line_around_cursor('foo ', 'bar') input_keys('y3l') - assert_line_around_cursor('', 'foo bar') + assert_line_around_cursor('foo ', 'bar') input_keys('P') - assert_line_around_cursor('fo', 'ofoo bar') + assert_line_around_cursor('foo ba', 'rbar') + input_keys('3h3yhP') + assert_line_around_cursor('foofo', 'o barbar') + input_keys('yyP') + assert_line_around_cursor('foofofoofoo barba', 'ro barbar') end def test_vi_end_word_with_operator diff --git a/test/reline/test_terminfo.rb b/test/reline/test_terminfo.rb index dda9b324954715..4e59c5483864f9 100644 --- a/test/reline/test_terminfo.rb +++ b/test/reline/test_terminfo.rb @@ -58,4 +58,4 @@ def test_tigetnum_with_error assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum('unknown') } assert_raise(Reline::Terminfo::TerminfoError) { Reline::Terminfo.tigetnum(nil) } end -end if Reline::Terminfo.enabled? +end if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? diff --git a/test/ripper/test_parser_events.rb b/test/ripper/test_parser_events.rb index 9283a4d3434391..cbae6e7ed59b0a 100644 --- a/test/ripper/test_parser_events.rb +++ b/test/ripper/test_parser_events.rb @@ -1672,6 +1672,26 @@ def test_warning_invalid_magic_comment assert_equal(%w"frozen_string_literal nottrue", args) end + def test_warning_duplicated_when_clause + fmt, *args = warning(<<~STR) + a = 1 + case a + when 1 + when 1 + when 2 + else + end + STR + assert_match(/duplicated 'when' clause/, fmt) + assert_equal([3], args) + end + + def test_warn_duplicated_hash_keys + fmt, *args = warn("{ a: 1, a: 2 }") + assert_match(/is duplicated and overwritten on line/, fmt) + assert_equal([:a, 1], args) + end + def test_warn_cr_in_middle fmt = nil assert_warn("") {fmt, = warn("\r;")} diff --git a/test/ripper/test_sexp.rb b/test/ripper/test_sexp.rb index 32285be9abac0e..3ebcf3062ee243 100644 --- a/test/ripper/test_sexp.rb +++ b/test/ripper/test_sexp.rb @@ -123,6 +123,21 @@ def test_named_with_default assert_equal(exp, named) end + def test_command + sexp = Ripper.sexp("a::C {}") + assert_equal( + [:program, + [ + [:method_add_block, + [:command_call, + [:vcall, [:@ident, "a", [1, 0]]], + [:@op, "::", [1, 1]], + [:@const, "C", [1, 3]], + nil], + [:brace_block, nil, [[:void_stmt]]]]]], + sexp) + end + def search_sexp(sym, sexp) return sexp if !sexp or sexp[0] == sym sexp.find do |e| diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 6e84bdbd02dc25..9560fca9580267 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -3556,6 +3556,15 @@ def test_big_array_literal_with_kwsplat assert_equal(10000, eval(lit).size) end + def test_array_safely_modified_by_sort_block + var_0 = (1..70).to_a + var_0.sort! do |var_0_block_129, var_1_block_129| + var_0.pop + var_1_block_129 <=> var_0_block_129 + end.shift(3) + assert_equal((1..67).to_a.reverse, var_0) + end + private def need_continuation unless respond_to?(:callcc, true) diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 90d19c3d68eb74..29da607fc5daa1 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -1232,6 +1232,12 @@ def test_with_bom EXP end + def test_unused_block_local_variable + assert_warning('') do + RubyVM::AbstractSyntaxTree.parse(%{->(; foo) {}}) + end + end + def assert_error_tolerant(src, expected, keep_tokens: false) begin verbose_bak, $VERBOSE = $VERBOSE, false diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb index eef909eb07f27b..d13b150f93e3ac 100644 --- a/test/ruby/test_compile_prism.rb +++ b/test/ruby/test_compile_prism.rb @@ -1839,15 +1839,6 @@ def o.bar(&) = foo(&) o.bar { :ok } RUBY - - # Test anonymous block forwarding from argument forwarding - assert_prism_eval(<<~RUBY) - o = Object.new - def o.foo = yield - def o.bar(...) = foo(&) - - o.bar { :ok } - RUBY end def test_BlockLocalVariableNode diff --git a/test/ruby/test_method.rb b/test/ruby/test_method.rb index ec06f4c50a150d..a41704cf069055 100644 --- a/test/ruby/test_method.rb +++ b/test/ruby/test_method.rb @@ -1623,4 +1623,74 @@ def test_kwarg_eval_memory_leak end RUBY end + + def test_warn_unused_block + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + foo{} # warn + send(:foo){} # warn + b = Proc.new{} + foo(&b) # warn + RUBY + assert_equal 3, err.size + err = err.join + assert_match(/-:2: warning/, err) + assert_match(/-:3: warning/, err) + assert_match(/-:5: warning/, err) + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil + 10.times{foo{}} # warn once + RUBY + assert_equal 1, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + def foo = nil; b = nil + foo(&b) # no warning + 1.object_id{} # no warning because it is written in C + + class C + def initialize + end + end + C.new{} # no warning + + RUBY + assert_equal 0, err.size + end + + assert_in_out_err '-w', <<-'RUBY' do |_out, err, _status| + class C0 + def f1 = nil + def f2 = nil + def f3 = nil + def f4 = nil + def f5 = nil + def f6 = nil + end + + class C1 < C0 + def f1 = super # zsuper / use + def f2 = super() # super / use + def f3(&_) = super(&_) # super / use + def f4 = super(&nil) # super / unuse + def f5 = super(){} # super / unuse + def f6 = super{} # zsuper / unuse + end + + C1.new.f1{} # no warning + C1.new.f2{} # no warning + C1.new.f3{} # no warning + C1.new.f4{} # warning + C1.new.f5{} # warning + C1.new.f6{} # warning + RUBY + assert_equal 3, err.size, err.join("\n") + assert_match(/-:22: warning.+f4/, err.join) + assert_match(/-:23: warning.+f5/, err.join) + assert_match(/-:24: warning.+f6/, err.join) + end + end end diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index a7cfb064a87e10..1c97bd517e46cd 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -66,8 +66,11 @@ def test_id2ref_invalid_argument end def test_id2ref_invalid_symbol_id + # RB_STATIC_SYM_P checks for static symbols by checking that the bottom + # 8 bits of the object is equal to RUBY_SYMBOL_FLAG, so we need to make + # sure that the bottom 8 bits remain unchanged. msg = /is not symbol id value/ - assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]) } + assert_raise_with_message(RangeError, msg) { ObjectSpace._id2ref(:a.object_id + 256) } end def test_count_objects diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb index 70b6bde6ed611b..a7a0582dbb49c5 100644 --- a/test/ruby/test_optimization.rb +++ b/test/ruby/test_optimization.rb @@ -16,6 +16,18 @@ def #{method}(*args) end; end + def assert_performance_warning(klass, method) + assert_in_out_err([], "#{<<-"begin;"}\n#{<<~"end;"}", [], ["-:4: warning: Redefining '#{klass}##{method}' disables interpreter and JIT optimizations"]) + begin; + Warning[:performance] = true + class #{klass} + undef #{method} + def #{method} + end + end + end; + end + def disasm(name) RubyVM::InstructionSequence.of(method(name)).disasm end @@ -23,102 +35,122 @@ def disasm(name) def test_fixnum_plus assert_equal 21, 10 + 11 assert_redefine_method('Integer', '+', 'assert_equal 11, 10 + 11') + assert_performance_warning('Integer', '+') end def test_fixnum_minus assert_equal 5, 8 - 3 assert_redefine_method('Integer', '-', 'assert_equal 3, 8 - 3') + assert_performance_warning('Integer', '-') end def test_fixnum_mul assert_equal 15, 3 * 5 assert_redefine_method('Integer', '*', 'assert_equal 5, 3 * 5') + assert_performance_warning('Integer', '*') end def test_fixnum_div assert_equal 3, 15 / 5 assert_redefine_method('Integer', '/', 'assert_equal 5, 15 / 5') + assert_performance_warning('Integer', '/') end def test_fixnum_mod assert_equal 1, 8 % 7 assert_redefine_method('Integer', '%', 'assert_equal 7, 8 % 7') + assert_performance_warning('Integer', '%') end def test_fixnum_lt assert_equal true, 1 < 2 assert_redefine_method('Integer', '<', 'assert_equal 2, 1 < 2') + assert_performance_warning('Integer', '<') end def test_fixnum_le assert_equal true, 1 <= 2 assert_redefine_method('Integer', '<=', 'assert_equal 2, 1 <= 2') + assert_performance_warning('Integer', '<=') end def test_fixnum_gt assert_equal false, 1 > 2 assert_redefine_method('Integer', '>', 'assert_equal 2, 1 > 2') + assert_performance_warning('Integer', '>') end def test_fixnum_ge assert_equal false, 1 >= 2 assert_redefine_method('Integer', '>=', 'assert_equal 2, 1 >= 2') + assert_performance_warning('Integer', '>=') end def test_float_plus assert_equal 4.0, 2.0 + 2.0 assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + assert_performance_warning('Float', '+') end def test_float_minus assert_equal 4.0, 2.0 + 2.0 - assert_redefine_method('Float', '+', 'assert_equal 2.0, 2.0 + 2.0') + assert_redefine_method('Float', '-', 'assert_equal 2.0, 4.0 - 2.0') + assert_performance_warning('Float', '-') end def test_float_mul assert_equal 29.25, 4.5 * 6.5 assert_redefine_method('Float', '*', 'assert_equal 6.5, 4.5 * 6.5') + assert_performance_warning('Float', '*') end def test_float_div assert_in_delta 0.63063063063063063, 4.2 / 6.66 assert_redefine_method('Float', '/', 'assert_equal 6.66, 4.2 / 6.66', "[Bug #9238]") + assert_performance_warning('Float', '/') end def test_float_lt assert_equal true, 1.1 < 2.2 assert_redefine_method('Float', '<', 'assert_equal 2.2, 1.1 < 2.2') + assert_performance_warning('Float', '<') end def test_float_le assert_equal true, 1.1 <= 2.2 assert_redefine_method('Float', '<=', 'assert_equal 2.2, 1.1 <= 2.2') + assert_performance_warning('Float', '<=') end def test_float_gt assert_equal false, 1.1 > 2.2 assert_redefine_method('Float', '>', 'assert_equal 2.2, 1.1 > 2.2') + assert_performance_warning('Float', '>') end def test_float_ge assert_equal false, 1.1 >= 2.2 assert_redefine_method('Float', '>=', 'assert_equal 2.2, 1.1 >= 2.2') + assert_performance_warning('Float', '>=') end def test_string_length assert_equal 6, "string".length assert_redefine_method('String', 'length', 'assert_nil "string".length') + assert_performance_warning('String', 'length') end def test_string_size assert_equal 6, "string".size assert_redefine_method('String', 'size', 'assert_nil "string".size') + assert_performance_warning('String', 'size') end def test_string_empty? assert_equal true, "".empty? assert_equal false, "string".empty? assert_redefine_method('String', 'empty?', 'assert_nil "string".empty?') + assert_performance_warning('String', 'empty?') end def test_string_plus @@ -127,39 +159,50 @@ def test_string_plus assert_equal "x", "" + "x" assert_equal "ab", "a" + "b" assert_redefine_method('String', '+', 'assert_equal "b", "a" + "b"') + assert_performance_warning('String', '+') end def test_string_succ assert_equal 'b', 'a'.succ assert_equal 'B', 'A'.succ + assert_performance_warning('String', 'succ') end def test_string_format assert_equal '2', '%d' % 2 assert_redefine_method('String', '%', 'assert_equal 2, "%d" % 2') + assert_performance_warning('String', '%') end def test_string_freeze assert_equal "foo", "foo".freeze assert_equal "foo".freeze.object_id, "foo".freeze.object_id assert_redefine_method('String', 'freeze', 'assert_nil "foo".freeze') + assert_performance_warning('String', 'freeze') end def test_string_uminus assert_same "foo".freeze, -"foo" assert_redefine_method('String', '-@', 'assert_nil(-"foo")') + assert_performance_warning('String', '-@') end def test_array_min assert_equal 1, [1, 2, 4].min assert_redefine_method('Array', 'min', 'assert_nil([1, 2, 4].min)') assert_redefine_method('Array', 'min', 'assert_nil([1 + 0, 2, 4].min)') + assert_performance_warning('Array', 'min') end def test_array_max assert_equal 4, [1, 2, 4].max assert_redefine_method('Array', 'max', 'assert_nil([1, 2, 4].max)') assert_redefine_method('Array', 'max', 'assert_nil([1 + 0, 2, 4].max)') + assert_performance_warning('Array', 'max') + end + + def test_array_hash + assert_performance_warning('Array', 'hash') end def test_trace_optimized_methods @@ -235,6 +278,8 @@ def test_string_eq_neq assert_equal :b, (b #{m} "b").to_sym end end + + assert_performance_warning('String', '==') end def test_string_ltlt @@ -243,50 +288,59 @@ def test_string_ltlt assert_equal "x", "" << "x" assert_equal "ab", "a" << "b" assert_redefine_method('String', '<<', 'assert_equal "b", "a" << "b"') + assert_performance_warning('String', '<<') end def test_fixnum_and assert_equal 1, 1&3 assert_redefine_method('Integer', '&', 'assert_equal 3, 1&3') + assert_performance_warning('Integer', '&') end def test_fixnum_or assert_equal 3, 1|3 assert_redefine_method('Integer', '|', 'assert_equal 1, 3|1') + assert_performance_warning('Integer', '|') end def test_array_plus assert_equal [1,2], [1]+[2] assert_redefine_method('Array', '+', 'assert_equal [2], [1]+[2]') + assert_performance_warning('Array', '+') end def test_array_minus assert_equal [2], [1,2] - [1] assert_redefine_method('Array', '-', 'assert_equal [1], [1,2]-[1]') + assert_performance_warning('Array', '-') end def test_array_length assert_equal 0, [].length assert_equal 3, [1,2,3].length assert_redefine_method('Array', 'length', 'assert_nil([].length); assert_nil([1,2,3].length)') + assert_performance_warning('Array', 'length') end def test_array_empty? assert_equal true, [].empty? assert_equal false, [1,2,3].empty? assert_redefine_method('Array', 'empty?', 'assert_nil([].empty?); assert_nil([1,2,3].empty?)') + assert_performance_warning('Array', 'empty?') end def test_hash_length assert_equal 0, {}.length assert_equal 1, {1=>1}.length assert_redefine_method('Hash', 'length', 'assert_nil({}.length); assert_nil({1=>1}.length)') + assert_performance_warning('Hash', 'length') end def test_hash_empty? assert_equal true, {}.empty? assert_equal false, {1=>1}.empty? assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)') + assert_performance_warning('Hash', 'empty?') end def test_hash_aref_with @@ -297,6 +351,7 @@ def test_hash_aref_with h = { "foo" => 1 } assert_equal "foo", h["foo"] end; + assert_performance_warning('Hash', '[]') end def test_hash_aset_with @@ -308,6 +363,7 @@ def test_hash_aset_with assert_equal 1, h["foo"] = 1, "assignment always returns value set" assert_nil h["foo"] end; + assert_performance_warning('Hash', '[]=') end class MyObj @@ -577,7 +633,7 @@ def test_string_freeze_block begin; class String undef freeze - def freeze + def freeze(&) block_given? end end @@ -639,6 +695,7 @@ def test_eqq [ nil, true, false, 0.1, :sym, 'str', 0xffffffffffffffff ].each do |v| k = v.class.to_s assert_redefine_method(k, '===', "assert_equal(#{v.inspect} === 0, 0)") + assert_performance_warning(k, '===') end end diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index 2aa69dc6a49e47..84b3b205f03343 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -983,26 +983,38 @@ def test_comparison_when_recursive end def test_size - assert_equal 42, (1..42).size - assert_equal 41, (1...42).size - assert_equal 6, (1...6.3).size - assert_equal 5, (1.1...6).size - assert_equal 3, (1..3r).size - assert_equal 2, (1...3r).size - assert_equal 3, (1..3.1r).size - assert_equal 3, (1...3.1r).size - assert_equal 42, (1..42).each.size + Enumerator.product([:to_i, :to_f, :to_r].repeated_permutation(2), [1, 10], [5, 5.5], [true, false]) do |(m1, m2), beg, ende, exclude_end| + r = Range.new(beg.send(m1), ende.send(m2), exclude_end) + iterable = true + yielded = [] + begin + r.each { yielded << _1 } + rescue TypeError + iterable = false + end + + if iterable + assert_equal(yielded.size, r.size, "failed on #{r}") + assert_equal(yielded.size, r.each.size, "failed on #{r}") + else + assert_raise(TypeError, "failed on #{r}") { r.size } + assert_raise(TypeError, "failed on #{r}") { r.each.size } + end + end + assert_nil ("a"..."z").size - assert_nil ("a"...).size - assert_nil (..."z").size # [Bug #18983] - assert_nil (nil...nil).size # [Bug #18983] - - assert_equal Float::INFINITY, (1...).size - assert_equal Float::INFINITY, (1.0...).size - assert_equal Float::INFINITY, (...1).size - assert_equal Float::INFINITY, (...1.0).size - assert_nil ("a"...).size - assert_nil (..."z").size + + assert_equal Float::INFINITY, (1..).size + assert_raise(TypeError) { (1.0..).size } + assert_raise(TypeError) { (1r..).size } + assert_nil ("a"..).size + + assert_raise(TypeError) { (..1).size } + assert_raise(TypeError) { (..1.0).size } + assert_raise(TypeError) { (..1r).size } + assert_raise(TypeError) { (..'z').size } + + assert_raise(TypeError) { (nil...nil).size } end def test_bsearch_typechecks_return_values diff --git a/test/ruby/test_rubyoptions.rb b/test/ruby/test_rubyoptions.rb index 8396066dc1ba19..76be9152a7eee8 100644 --- a/test/ruby/test_rubyoptions.rb +++ b/test/ruby/test_rubyoptions.rb @@ -306,7 +306,7 @@ def test_parser_flag assert_in_out_err(%w(--parser=prism -e) + ["puts :hi"], "", %w(hi), warning) assert_in_out_err(%w(--parser=prism -W:no-experimental -e) + ["puts :hi"], "", %w(hi), []) - assert_in_out_err(%w(--parser=prism -W:no-experimental --dump=parsetree -e :hi), "", /"hi"/, []) + assert_in_out_err(%w(--parser=prism -W:no-experimental --dump=parsetree -e _=:hi), "", /"hi"/, []) assert_in_out_err(%w(--parser=parse.y -e) + ["puts :hi"], "", %w(hi), []) assert_norun_with_rflag('--parser=parse.y', '--version', "") @@ -1138,17 +1138,17 @@ def test_pflag_sub assert_in_out_err(['-p', '-e', 'sub(/t.*/){"TEST"}'], %[test], %w[TEST], [], bug7157) end - def assert_norun_with_rflag(*opt) + def assert_norun_with_rflag(*opt, test_stderr: []) bug10435 = "[ruby-dev:48712] [Bug #10435]: should not run with #{opt} option" stderr = [] Tempfile.create(%w"bug10435- .rb") do |script| dir, base = File.split(script.path) File.write(script, "abort ':run'\n") opts = ['-C', dir, '-r', "./#{base}", *opt] - _, e = assert_in_out_err([*opts, '-ep'], "", //) + _, e = assert_in_out_err([*opts, '-ep'], "", //, test_stderr) stderr.concat(e) if e stderr << "---" - _, e = assert_in_out_err([*opts, base], "", //) + _, e = assert_in_out_err([*opts, base], "", //, test_stderr) stderr.concat(e) if e end assert_not_include(stderr, ":run", bug10435) @@ -1171,6 +1171,15 @@ def test_dump_parsetree_with_rflag assert_norun_with_rflag('--dump=parse+error_tolerant') end + def test_dump_parsetree_error_tolerant + assert_in_out_err(['--dump=parse', '-e', 'begin'], + "", [], /unexpected end-of-input/, success: false) + assert_in_out_err(['--dump=parse', '--dump=+error_tolerant', '-e', 'begin'], + "", /^# @/, /unexpected end-of-input/, success: true) + assert_in_out_err(['--dump=+error_tolerant', '-e', 'begin p :run'], + "", [], /unexpected end-of-input/, success: false) + end + def test_dump_insns_with_rflag assert_norun_with_rflag('--dump=insns') end diff --git a/test/ruby/test_shapes.rb b/test/ruby/test_shapes.rb index bbea03344e926d..9b02504384491c 100644 --- a/test/ruby/test_shapes.rb +++ b/test/ruby/test_shapes.rb @@ -1006,7 +1006,7 @@ def test_freezing_and_cloning_object_with_ivars end def test_freezing_and_cloning_string - str = "str".freeze + str = ("str" + "str").freeze str2 = str.clone(freeze: true) assert_predicate(str2, :frozen?) assert_shape_equal(RubyVM::Shape.of(str), RubyVM::Shape.of(str2)) diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 9ff3791e7de986..13261eacdd1f79 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -3356,7 +3356,11 @@ def test_uplus_minus assert_same(str, +str) assert_not_same(str, -str) + require 'objspace' + str = "bar".freeze + assert_includes ObjectSpace.dump(str), '"fstring":true' + assert_predicate(str, :frozen?) assert_not_predicate(+str, :frozen?) assert_predicate(-str, :frozen?) @@ -3364,8 +3368,8 @@ def test_uplus_minus assert_not_same(str, +str) assert_same(str, -str) - bar = %w(b a r).join('') - assert_same(str, -bar, "uminus deduplicates [Feature #13077]") + bar = -%w(b a r).join('') + assert_same(str, bar, "uminus deduplicates [Feature #13077] str: #{ObjectSpace.dump(str)} bar: #{ObjectSpace.dump(bar)}") end def test_uminus_frozen diff --git a/test/ruby/test_syntax.rb b/test/ruby/test_syntax.rb index 7cc5e542a7b7bd..44162f06cbdfee 100644 --- a/test/ruby/test_syntax.rb +++ b/test/ruby/test_syntax.rb @@ -190,6 +190,7 @@ def test_argument_forwarding_with_anon_rest_kwrest_and_block assert_syntax_error("def f(...); g(0, *); end", /no anonymous rest parameter/) assert_syntax_error("def f(...); g(**); end", /no anonymous keyword rest parameter/) assert_syntax_error("def f(...); g(x: 1, **); end", /no anonymous keyword rest parameter/) + assert_syntax_error("def f(...); g(&); end", /no anonymous block parameter/) end def test_newline_in_block_parameters diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index bc76ee824e577f..34db31f61c81f4 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d933382388cc7a6fdfd54e222eca7994791ac4b9ce5c9e8df280c739d86bbe" +checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc5a7e3a875419baaa0d8cc606cdfb9361b444cb7e5abcf0de4693025887374" +checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 424c61e45226cc..00a48df5d57ddb 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.90" +rb-sys = "0.9.91" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 612196063f3ffa..f96b1442936e2f 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d933382388cc7a6fdfd54e222eca7994791ac4b9ce5c9e8df280c739d86bbe" +checksum = "eb81203e271055178603e243fee397f5f4aac125bcd20036279683fb1445a899" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.90" +version = "0.9.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc5a7e3a875419baaa0d8cc606cdfb9361b444cb7e5abcf0de4693025887374" +checksum = "9de9403a6aac834e7c9534575cb14188b6b5b99bafe475d18d838d44fbc27d31" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index e584a7f427e1b2..dca81463949243 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.90" +rb-sys = "0.9.91" diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index 4829d9620cd202..e95b0d2d7a0086 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -263,6 +263,29 @@ def test_netmask assert_equal(a.netmask, "255.255.255.0") end + def test_wildcard_mask + a = IPAddr.new("192.168.1.2/1") + assert_equal(a.wildcard_mask, "127.255.255.255") + + a = IPAddr.new("192.168.1.2/8") + assert_equal(a.wildcard_mask, "0.255.255.255") + + a = IPAddr.new("192.168.1.2/16") + assert_equal(a.wildcard_mask, "0.0.255.255") + + a = IPAddr.new("192.168.1.2/24") + assert_equal(a.wildcard_mask, "0.0.0.255") + + a = IPAddr.new("192.168.1.2/32") + assert_equal(a.wildcard_mask, "0.0.0.0") + + a = IPAddr.new("3ffe:505:2::/48") + assert_equal(a.wildcard_mask, "0000:0000:0000:ffff:ffff:ffff:ffff:ffff") + + a = IPAddr.new("3ffe:505:2::/128") + assert_equal(a.wildcard_mask, "0000:0000:0000:0000:0000:0000:0000:0000") + end + def test_zone_id a = IPAddr.new("192.168.1.2") assert_raise(IPAddr::InvalidAddressError) { a.zone_id = '%ab0' } diff --git a/tool/bundler/dev_gems.rb b/tool/bundler/dev_gems.rb index cb4b556d2fbc66..acf733557873f4 100644 --- a/tool/bundler/dev_gems.rb +++ b/tool/bundler/dev_gems.rb @@ -7,7 +7,7 @@ gem "rb_sys" gem "webrick", "~> 1.6" -gem "turbo_tests", "~> 2.1" +gem "turbo_tests", "= 2.1.0" gem "parallel_tests", "< 3.9.0" gem "parallel", "~> 1.19" gem "rspec-core", "~> 3.12" diff --git a/tool/m4/ruby_shared_gc.m4 b/tool/m4/ruby_shared_gc.m4 new file mode 100644 index 00000000000000..a27b9b8505ca77 --- /dev/null +++ b/tool/m4/ruby_shared_gc.m4 @@ -0,0 +1,19 @@ +dnl -*- Autoconf -*- +AC_DEFUN([RUBY_SHARED_GC],[ +AC_ARG_WITH(shared-gc, + AS_HELP_STRING([--with-shared-gc], + [Enable replacement of Ruby's GC from a shared library.]), + [with_shared_gc=$withval], [unset with_shared_gc] +) + +AC_SUBST([with_shared_gc]) +AC_MSG_CHECKING([if Ruby is build with shared GC support]) +AS_IF([test "$with_shared_gc" = "yes"], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([USE_SHARED_GC], [1]) +], [ + AC_MSG_RESULT([no]) + with_shared_gc="no" + AC_DEFINE([USE_SHARED_GC], [0]) +]) +])dnl diff --git a/tool/mk_builtin_loader.rb b/tool/mk_builtin_loader.rb index 07c829124948a1..4abd497f0eae19 100644 --- a/tool/mk_builtin_loader.rb +++ b/tool/mk_builtin_loader.rb @@ -6,7 +6,7 @@ SUBLIBS = {} REQUIRED = {} -BUILTIN_ATTRS = %w[leaf inline_block] +BUILTIN_ATTRS = %w[leaf inline_block use_block] def string_literal(lit, str = []) while lit diff --git a/tool/test/testunit/test_parallel.rb b/tool/test/testunit/test_parallel.rb index f79c3a1d806e59..6882fd6c5f8f82 100644 --- a/tool/test/testunit/test_parallel.rb +++ b/tool/test/testunit/test_parallel.rb @@ -119,7 +119,7 @@ def test_done result = Marshal.load($1.chomp.unpack1("m")) assert_equal(5, result[0]) - pend "TODO: result[1] returns 17. We should investigate it" do + pend "TODO: result[1] returns 17. We should investigate it" do # TODO: misusage of pend (pend doens't use given block) assert_equal(12, result[1]) end assert_kind_of(Array,result[2]) diff --git a/tool/test_for_warn_bundled_gems/test.sh b/tool/test_for_warn_bundled_gems/test.sh index ef5007f320ab77..2404571daf8880 100755 --- a/tool/test_for_warn_bundled_gems/test.sh +++ b/tool/test_for_warn_bundled_gems/test.sh @@ -24,6 +24,10 @@ echo "* Show warning when bundle exec with shebang's script" bundle exec ./test_warn_bundle_exec_shebang.rb echo +echo "* Show warning when bundle exec with -r option" +bundle exec ruby -rostruct -e '' +echo + echo "* Show warning with bootsnap" ruby test_warn_bootsnap.rb echo diff --git a/trace_point.rb b/trace_point.rb index e61b4b680207bd..b136e9b399dbe6 100644 --- a/trace_point.rb +++ b/trace_point.rb @@ -94,6 +94,7 @@ class TracePoint # Access from other threads is also forbidden. # def self.new(*events) + Primitive.attr! :use_block Primitive.tracepoint_new_s(events) end @@ -131,6 +132,7 @@ def self.stat # trace.enabled? #=> true # def self.trace(*events) + Primitive.attr! :use_block Primitive.tracepoint_trace_s(events) end @@ -196,6 +198,7 @@ def self.trace(*events) # out calls by itself from :line handler, otherwise it will call itself infinitely). # def self.allow_reentry + Primitive.attr! :use_block Primitive.tracepoint_allow_reentry end @@ -258,6 +261,7 @@ def self.allow_reentry # #=> RuntimeError: access from outside # def enable(target: nil, target_line: nil, target_thread: :default) + Primitive.attr! :use_block Primitive.tracepoint_enable_m(target, target_line, target_thread) end @@ -294,6 +298,7 @@ def enable(target: nil, target_line: nil, target_thread: :default) # trace.disable { p tp.lineno } # #=> RuntimeError: access from outside def disable + Primitive.attr! :use_block Primitive.tracepoint_disable_m end diff --git a/universal_parser.c b/universal_parser.c index 1a9619bf31a1c9..23a86311a5e039 100644 --- a/universal_parser.c +++ b/universal_parser.c @@ -88,11 +88,6 @@ #define compile_callback p->config->compile_callback #define reg_named_capture_assign p->config->reg_named_capture_assign -#undef FIXNUM_P -#define FIXNUM_P p->config->fixnum_p -#undef SYMBOL_P -#define SYMBOL_P p->config->symbol_p - #define rb_attr_get p->config->attr_get #define rb_ary_new p->config->ary_new @@ -100,9 +95,6 @@ #undef rb_ary_new_from_args #define rb_ary_new_from_args p->config->ary_new_from_args #define rb_ary_unshift p->config->ary_unshift -#undef rb_ary_new2 -#define rb_ary_new2 p->config->ary_new2 -#define rb_ary_clear p->config->ary_clear #define rb_ary_modify p->config->ary_modify #undef RARRAY_LEN #define RARRAY_LEN p->config->array_len @@ -133,9 +125,6 @@ #define rb_str_cat_cstr p->config->str_cat_cstr #define rb_str_subseq p->config->str_subseq #define rb_str_new_frozen p->config->str_new_frozen -#define rb_str_buf_new p->config->str_buf_new -#undef rb_str_buf_cat -#define rb_str_buf_cat p->config->str_buf_cat #define rb_str_modify p->config->str_modify #define rb_str_set_len p->config->str_set_len #define rb_str_cat p->config->str_cat @@ -147,8 +136,6 @@ #define rb_str_to_interned_str p->config->str_to_interned_str #define is_ascii_string p->config->is_ascii_string #define rb_enc_str_new p->config->enc_str_new -#define rb_enc_str_buf_cat p->config->enc_str_buf_cat -#define rb_str_buf_append p->config->str_buf_append #define rb_str_vcatf p->config->str_vcatf #undef StringValueCStr #define StringValueCStr(v) p->config->string_value_cstr(&(v)) @@ -162,8 +149,6 @@ #define rb_filesystem_str_new_cstr p->config->filesystem_str_new_cstr #define rb_obj_as_string p->config->obj_as_string -#undef NUM2INT -#define NUM2INT p->config->num2int #undef INT2NUM #define INT2NUM p->config->int2num @@ -245,8 +230,6 @@ #undef RBOOL #define RBOOL p->config->rbool -#undef UNDEF_P -#define UNDEF_P p->config->undef_p #undef RTEST #define RTEST p->config->rtest #undef NIL_P @@ -257,8 +240,6 @@ #define Qtrue p->config->qtrue #undef Qfalse #define Qfalse p->config->qfalse -#undef Qundef -#define Qundef p->config->qundef #define rb_eArgError p->config->eArgError() #undef rb_long2int #define rb_long2int p->config->long2int @@ -266,8 +247,5 @@ #define rb_enc_isascii p->config->enc_isascii #define rb_enc_mbc_to_codepoint p->config->enc_mbc_to_codepoint -#undef st_init_table_with_size -#define st_init_table_with_size rb_parser_st_init_table_with_size - #define rb_ast_new() \ rb_ast_new(p->config) diff --git a/vm.c b/vm.c index 7568d6930eafd0..329e47fe68de94 100644 --- a/vm.c +++ b/vm.c @@ -1483,7 +1483,7 @@ rb_binding_add_dynavars(VALUE bindval, rb_binding_t *bind, int dyncount, const I ast.root = RNODE(&tmp_node); ast.frozen_string_literal = -1; ast.coverage_enabled = -1; - ast.script_lines = INT2FIX(-1); + ast.script_lines = (rb_parser_ary_t *)INT2FIX(-1); if (base_iseq) { iseq = rb_iseq_new(&ast, ISEQ_BODY(base_iseq)->location.label, path, realpath, base_iseq, ISEQ_TYPE_EVAL); @@ -2136,6 +2136,12 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass) if (st_lookup(vm_opt_method_def_table, (st_data_t)me->def, &bop)) { int flag = vm_redefinition_check_flag(klass); if (flag != 0) { + rb_category_warn( + RB_WARN_CATEGORY_PERFORMANCE, + "Redefining '%s#%s' disables interpreter and JIT optimizations", + rb_class2name(me->owner), + rb_id2name(me->called_id) + ); rb_yjit_bop_redefined(flag, (enum ruby_basic_operators)bop); rb_rjit_bop_redefined(flag, (enum ruby_basic_operators)bop); ruby_vm_redefined_flag[bop] |= flag; diff --git a/vm_backtrace.c b/vm_backtrace.c index 6f4379ad23e85c..3fe816930da2de 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -201,8 +201,8 @@ location_lineno_m(VALUE self) VALUE rb_mod_name0(VALUE klass, bool *permanent); -static VALUE -gen_method_name(VALUE owner, VALUE name) +VALUE +rb_gen_method_name(VALUE owner, VALUE name) { bool permanent; if (RB_TYPE_P(owner, T_CLASS) || RB_TYPE_P(owner, T_MODULE)) { @@ -235,7 +235,7 @@ calculate_iseq_label(VALUE owner, const rb_iseq_t *iseq) case ISEQ_TYPE_MAIN: return ISEQ_BODY(iseq)->location.label; case ISEQ_TYPE_METHOD: - return gen_method_name(owner, ISEQ_BODY(iseq)->location.label); + return rb_gen_method_name(owner, ISEQ_BODY(iseq)->location.label); case ISEQ_TYPE_BLOCK: case ISEQ_TYPE_PLAIN: { int level = 0; @@ -269,7 +269,7 @@ static VALUE location_label(rb_backtrace_location_t *loc) { if (loc->cme && loc->cme->def->type == VM_METHOD_TYPE_CFUNC) { - return gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + return rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); } else { VALUE owner = Qnil; @@ -457,7 +457,7 @@ location_to_str(rb_backtrace_location_t *loc) file = GET_VM()->progname; lineno = 0; } - name = gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); + name = rb_gen_method_name(loc->cme->owner, rb_id2str(loc->cme->def->original_id)); } else { file = rb_iseq_path(loc->iseq); diff --git a/vm_core.h b/vm_core.h index 2ae4ecf235a936..949d912872a1dd 100644 --- a/vm_core.h +++ b/vm_core.h @@ -106,6 +106,14 @@ extern int ruby_assert_critical_section_entered; #include "ruby/thread_native.h" +#if USE_SHARED_GC +typedef struct gc_function_map { + void *(*init)(void); +} rb_gc_function_map_t; + +#define rb_gc_functions (GET_VM()->gc_functions_map) +#endif + /* * implementation selector of get_insn_info algorithm * 0: linear search @@ -418,6 +426,7 @@ struct rb_iseq_constant_body { unsigned int ruby2_keywords: 1; unsigned int anon_rest: 1; unsigned int anon_kwrest: 1; + unsigned int use_block: 1; } flags; unsigned int size; @@ -771,6 +780,10 @@ typedef struct rb_vm_struct { bool global_gc_underway; rb_nativethread_cond_t global_gc_finished; +#if USE_SHARED_GC + rb_gc_function_map_t *gc_functions_map; +#endif + rb_at_exit_list *at_exit; rb_gc_safe_lock_t fstring_table_lock; @@ -1227,7 +1240,8 @@ rb_iseq_t *rb_iseq_new_top (const rb_ast_body_t *ast, VALUE name, VALUE path rb_iseq_t *rb_iseq_new_main (const rb_ast_body_t *ast, VALUE path, VALUE realpath, const rb_iseq_t *parent, int opt); rb_iseq_t *rb_iseq_new_eval (const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth); rb_iseq_t *rb_iseq_new_with_opt(const rb_ast_body_t *ast, VALUE name, VALUE path, VALUE realpath, int first_lineno, const rb_iseq_t *parent, int isolated_depth, - enum rb_iseq_type, const rb_compile_option_t*); + enum rb_iseq_type, const rb_compile_option_t*, + VALUE script_lines); struct iseq_link_anchor; struct rb_iseq_new_with_callback_callback_func { diff --git a/vm_eval.c b/vm_eval.c index 1b8be07124037a..ab323182e0251c 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1810,7 +1810,7 @@ eval_make_iseq(VALUE src, VALUE fname, int line, } rb_parser_set_context(parser, parent, FALSE); - rb_parser_set_script_lines(parser, RBOOL(ruby_vm_keep_script_lines)); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); ast = rb_parser_compile_string_path(parser, fname, src, line); if (ast->body.root) { ast->body.coverage_enabled = coverage_enabled; diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 56ad643c2efb3a..04e7d3714ef288 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2975,6 +2975,57 @@ vm_call_single_noarg_leaf_builtin(rb_execution_context_t *ec, rb_control_frame_t return builtin_invoker0(ec, calling->recv, NULL, (rb_insn_func_t)bf->func_ptr); } +VALUE rb_gen_method_name(VALUE owner, VALUE name); // in vm_backtrace.c + +static void +warn_unused_block(const rb_callable_method_entry_t *cme, const rb_iseq_t *iseq, void *pc) +{ + static st_table *dup_check_table = NULL; + + st_data_t key = 0; + union { + VALUE v; + unsigned char b[SIZEOF_VALUE]; + } k1 = { + .v = (VALUE)pc, + }, k2 = { + .v = (VALUE)cme->def, + }; + + // make unique key from pc and me->def pointer + for (int i=0; idef); + fprintf(stderr, "key:%p\n", (void *)key); + } + + if (!dup_check_table) { + dup_check_table = st_init_numtable(); + } + + // duplication check + if (st_insert(dup_check_table, key, 1)) { + // already shown + } + else { + VALUE m_loc = rb_method_entry_location((const rb_method_entry_t *)cme); + VALUE name = rb_gen_method_name(cme->defined_class, ISEQ_BODY(iseq)->location.base_label); + + if (!NIL_P(m_loc)) { + rb_warning("the passed block for '%"PRIsVALUE"' defined at %"PRIsVALUE":%"PRIsVALUE" may be ignored", + name, RARRAY_AREF(m_loc, 0), RARRAY_AREF(m_loc, 1)); + } + else { + rb_warning("the block may be ignored because '%"PRIsVALUE"' does not use a block", name); + } + } +} + static inline int vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size) @@ -2983,6 +3034,12 @@ vm_callee_setup_arg(rb_execution_context_t *ec, struct rb_calling_info *calling, const struct rb_callcache *cc = calling->cc; bool cacheable_ci = vm_ci_markable(ci); + if (UNLIKELY(!ISEQ_BODY(iseq)->param.flags.use_block && + calling->block_handler != VM_BLOCK_HANDLER_NONE && + !(vm_ci_flag(calling->cd->ci) & VM_CALL_SUPER))) { + warn_unused_block(vm_cc_cme(cc), iseq, (void *)ec->cfp->pc); + } + if (LIKELY(!(vm_ci_flag(ci) & VM_CALL_KW_SPLAT))) { if (LIKELY(rb_simple_iseq_p(iseq))) { rb_control_frame_t *cfp = ec->cfp; diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 092df6326f2e05..a62ea45e7e10ff 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -431,12 +431,33 @@ impl Assembler } } }, - Insn::And { left, right, .. } | - Insn::Or { left, right, .. } | - Insn::Xor { left, right, .. } => { + Insn::And { left, right, out } | + Insn::Or { left, right, out } | + Insn::Xor { left, right, out } => { let (opnd0, opnd1) = split_boolean_operands(asm, *left, *right); *left = opnd0; *right = opnd1; + + // Since these instructions are lowered to an instruction that have 2 input + // registers and an output register, look to merge with an `Insn::Mov` that + // follows which puts the output in another register. For example: + // `Add a, b => out` followed by `Mov c, out` becomes `Add a, b => c`. + if let (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) = (left, right, iterator.peek()) { + if live_ranges[index] == index + 1 { + // Check after potentially lowering a stack operand to a register operand + let lowered_dest = if let Opnd::Stack { .. } = dest { + asm.lower_stack_opnd(dest) + } else { + *dest + }; + if out == src && matches!(lowered_dest, Opnd::Reg(_)) { + *out = lowered_dest; + iterator.map_insn_index(asm); + iterator.next_unmapped(); // Pop merged Insn::Mov + } + } + } + asm.push_insn(insn); }, Insn::CCall { opnds, fptr, .. } => { diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs index 19e604a2f8241e..d52ed265bd52e0 100644 --- a/yjit/src/backend/x86_64/mod.rs +++ b/yjit/src/backend/x86_64/mod.rs @@ -181,6 +181,23 @@ impl Assembler iterator.map_insn_index(&mut asm); iterator.next_unmapped(); // Pop merged Insn::Mov } + (Opnd::Reg(_), Opnd::Reg(_), Some(Insn::Mov { dest, src })) + if out == src && live_ranges[index] == index + 1 && { + // We want to do `dest == left`, but `left` has already gone + // through lower_stack_opnd() while `dest` has not. So we + // lower `dest` before comparing. + let lowered_dest = if let Opnd::Stack { .. } = dest { + asm.lower_stack_opnd(dest) + } else { + *dest + }; + lowered_dest == *left + } => { + *out = *dest; + asm.push_insn(insn); + iterator.map_insn_index(&mut asm); + iterator.next_unmapped(); // Pop merged Insn::Mov + } _ => { match (unmapped_opnds[0], unmapped_opnds[1]) { (Opnd::Mem(_), Opnd::Mem(_)) => { diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index be719023c01019..d60f4f0ec1029b 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -3827,7 +3827,7 @@ fn gen_opt_and( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { @@ -3867,7 +3867,7 @@ fn gen_opt_or( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { @@ -3909,7 +3909,7 @@ fn gen_opt_minus( // Push the output on the stack let dst = asm.stack_push(Type::Fixnum); - asm.store(dst, val); + asm.mov(dst, val); Some(KeepCompiling) } else { diff --git a/yjit/src/invariants.rs b/yjit/src/invariants.rs index c4facb31dd00d9..e4602934409dac 100644 --- a/yjit/src/invariants.rs +++ b/yjit/src/invariants.rs @@ -416,7 +416,7 @@ pub fn block_assumptions_free(blockref: BlockRef) { invariants.constant_state_blocks.shrink_to_fit(); } - // Remove tracking for blocks assumping no singleton class + // Remove tracking for blocks assuming no singleton class for (_, blocks) in invariants.no_singleton_classes.iter_mut() { blocks.remove(&blockref); }