From 0832828953e81797487a223f9c433201b0ce2213 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:31:37 +0200 Subject: [PATCH 01/18] Update `test_unpack.py` from 3.14.3 --- Lib/test/test_unpack.py | 66 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_unpack.py b/Lib/test/test_unpack.py index 515ec128a08..db4faec2e3c 100644 --- a/Lib/test/test_unpack.py +++ b/Lib/test/test_unpack.py @@ -18,6 +18,13 @@ >>> a == 4 and b == 5 and c == 6 True +Unpack dict + + >>> d = {4: 'four', 5: 'five', 6: 'six'} + >>> a, b, c = d + >>> a == 4 and b == 5 and c == 6 + True + Unpack implied tuple >>> a, b, c = 7, 8, 9 @@ -63,17 +70,17 @@ Unpacking tuple of wrong size - >>> a, b = t + >>> a, b = t # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... - ValueError: too many values to unpack (expected 2) + ValueError: too many values to unpack (expected 2, got 3) Unpacking tuple of wrong size - >>> a, b = l + >>> a, b = l # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... - ValueError: too many values to unpack (expected 2) + ValueError: too many values to unpack (expected 2, got 3) Unpacking sequence too short @@ -137,17 +144,62 @@ Unpacking to an empty iterable should raise ValueError - >>> () = [42] + >>> () = [42] # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE Traceback (most recent call last): ... - ValueError: too many values to unpack (expected 0) + ValueError: too many values to unpack (expected 0, got 1) + +Unpacking a larger iterable should raise ValuleError, but it +should not entirely consume the iterable + >>> it = iter(range(100)) + >>> x, y, z = it + Traceback (most recent call last): + ... + ValueError: too many values to unpack (expected 3) + >>> next(it) # TODO: RUSTPYTHON; Raise `StopIteration` # doctest: +SKIP + 4 + +Unpacking unbalanced dict + + >>> d = {4: 'four', 5: 'five', 6: 'six', 7: 'seven'} + >>> a, b, c = d # TODO: RUSTPYTHON; # doctest: +EXPECTED_FAILURE + Traceback (most recent call last): + ... + ValueError: too many values to unpack (expected 3, got 4) + +Ensure that custom `__len__()` is NOT called when showing the error message + + >>> class LengthTooLong: + ... def __len__(self): + ... return 5 + ... def __getitem__(self, i): + ... return i*2 + ... + >>> x, y, z = LengthTooLong() # TODO: RUSTPYTHON; Hangs # doctest: +SKIP + Traceback (most recent call last): + ... + ValueError: too many values to unpack (expected 3) + +For evil cases like these as well, no actual count to be shown + + >>> class BadLength: + ... def __len__(self): + ... return 1 + ... def __getitem__(self, i): + ... return i*2 + ... + >>> x, y, z = BadLength() # TODO: RUSTPYTHON; Hangs # doctest: +SKIP + Traceback (most recent call last): + ... + ValueError: too many values to unpack (expected 3) """ __test__ = {'doctests' : doctests} def load_tests(loader, tests, pattern): - tests.addTest(doctest.DocTestSuite()) + from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON + tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # XXX: RUSTPYTHON return tests From d71ffa6c47206b980b27e39da99038c0acdb8d0d Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:41:19 +0200 Subject: [PATCH 02/18] Use shared doctest checker --- Lib/test/test_unpack_ex.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_unpack_ex.py b/Lib/test/test_unpack_ex.py index d84befd9c7b..1496e3be93f 100644 --- a/Lib/test/test_unpack_ex.py +++ b/Lib/test/test_unpack_ex.py @@ -402,15 +402,10 @@ __test__ = {'doctests' : doctests} -EXPECTED_FAILURE = doctest.register_optionflag('EXPECTED_FAILURE') # TODO: RUSTPYTHON -class CustomOutputChecker(doctest.OutputChecker): # TODO: RUSTPYTHON - def check_output(self, want, got, optionflags): # TODO: RUSTPYTHON - if optionflags & EXPECTED_FAILURE: # TODO: RUSTPYTHON - return not super().check_output(want, got, optionflags) # TODO: RUSTPYTHON - return super().check_output(want, got, optionflags) # TODO: RUSTPYTHON def load_tests(loader, tests, pattern): - tests.addTest(doctest.DocTestSuite(checker=CustomOutputChecker())) # TODO: RUSTPYTHON + from test.support.rustpython import DocTestChecker # TODO: RUSTPYTHON + tests.addTest(doctest.DocTestSuite(checker=DocTestChecker())) # XXX: RUSTPYTHON return tests From 1f338ce201e1f16aeef9370c86801ce561183548 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:26:10 +0000 Subject: [PATCH 03/18] Bump pyo3 from 0.28.0 to 0.28.1 in the pyo3 group Bumps the pyo3 group with 1 update: [pyo3](https://github.com/pyo3/pyo3). Updates `pyo3` from 0.28.0 to 0.28.1 - [Release notes](https://github.com/pyo3/pyo3/releases) - [Changelog](https://github.com/PyO3/pyo3/blob/main/CHANGELOG.md) - [Commits](https://github.com/pyo3/pyo3/compare/v0.28.0...v0.28.1) --- updated-dependencies: - dependency-name: pyo3 dependency-version: 0.28.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: pyo3 ... Signed-off-by: dependabot[bot] --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 906621e099c..9c6ebeb17a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2529,9 +2529,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf3ccafdf54c050be48a3a086d372f77ba6615f5057211607cd30e5ac5cec6d" +checksum = "14c738662e2181be11cb82487628404254902bb3225d8e9e99c31f3ef82a405c" dependencies = [ "libc", "once_cell", @@ -2543,18 +2543,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972720a441c91fd9c49f212a1d2d74c6e3803b231ebc8d66c51efbd7ccab11c8" +checksum = "f9ca0864a7dd3c133a7f3f020cbff2e12e88420da854c35540fd20ce2d60e435" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5994456d9dab8934d600d3867571b6410f24fbd6002570ad56356733eb54859b" +checksum = "9dfc1956b709823164763a34cc42bbfd26b8730afa77809a3df8b94a3ae3b059" dependencies = [ "libc", "pyo3-build-config", @@ -2562,9 +2562,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ce9cc8d81b3c4969748807604d92b4eef363c5bb82b1a1bdb34ec6f1093a18" +checksum = "29dc660ad948bae134d579661d08033fbb1918f4529c3bbe3257a68f2009ddf2" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2574,9 +2574,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf4b60036a154d23282679b658e3cc7d88d3b8c9a40b43824785f232d2e1b98" +checksum = "e78cd6c6d718acfcedf26c3d21fe0f053624368b0d44298c55d7138fde9331f7" dependencies = [ "heck", "proc-macro2", From 530e4db6ecf701eafd685ae8dda0a92d547800f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:26:40 +0000 Subject: [PATCH 04/18] Bump bitflags from 2.10.0 to 2.11.0 Bumps [bitflags](https://github.com/bitflags/bitflags) from 2.10.0 to 2.11.0. - [Release notes](https://github.com/bitflags/bitflags/releases) - [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md) - [Commits](https://github.com/bitflags/bitflags/compare/2.10.0...2.11.0) --- updated-dependencies: - dependency-name: bitflags dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Cargo.lock | 40 ++++++++++++++++++++-------------------- Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c6ebeb17a5..5b13350cb56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -323,7 +323,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -345,9 +345,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "blake2" @@ -1761,7 +1761,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -2002,7 +2002,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -2015,7 +2015,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -2148,7 +2148,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -2732,7 +2732,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2842,7 +2842,7 @@ version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?rev=a2f11d239f91cf8daedb0764ec15fcfe29c5ae6d#a2f11d239f91cf8daedb0764ec15fcfe29c5ae6d" dependencies = [ "aho-corasick", - "bitflags 2.10.0", + "bitflags 2.11.0", "compact_str", "get-size2", "is-macro", @@ -2859,7 +2859,7 @@ name = "ruff_python_parser" version = "0.0.0" source = "git+https://github.com/astral-sh/ruff.git?rev=a2f11d239f91cf8daedb0764ec15fcfe29c5ae6d#a2f11d239f91cf8daedb0764ec15fcfe29c5ae6d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bstr", "compact_str", "get-size2", @@ -2923,7 +2923,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -3041,7 +3041,7 @@ name = "rustpython-codegen" version = "0.4.0" dependencies = [ "ahash", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap", "insta", "itertools 0.14.0", @@ -3065,7 +3065,7 @@ name = "rustpython-common" version = "0.4.0" dependencies = [ "ascii", - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "getrandom 0.3.4", "itertools 0.14.0", @@ -3104,7 +3104,7 @@ dependencies = [ name = "rustpython-compiler-core" version = "0.4.0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "itertools 0.14.0", "lz4_flex", "malachite-bigint", @@ -3194,7 +3194,7 @@ dependencies = [ name = "rustpython-sre_engine" version = "0.4.0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "criterion", "num_enum", "optional", @@ -3307,7 +3307,7 @@ version = "0.4.0" dependencies = [ "ahash", "ascii", - "bitflags 2.10.0", + "bitflags 2.11.0", "bstr", "caseless", "cfg-if", @@ -3416,7 +3416,7 @@ version = "17.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "clipboard-win", "fd-lock", @@ -3503,7 +3503,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3774,7 +3774,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] diff --git a/Cargo.toml b/Cargo.toml index 0ac56876a89..6356eef8c0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,7 +154,7 @@ ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", rev = "a2f11 phf = { version = "0.13.1", default-features = false, features = ["macros"]} ahash = "0.8.12" ascii = "1.1" -bitflags = "2.9.4" +bitflags = "2.11.0" bstr = "1" bytes = "1.11.1" cfg-if = "1.0" From 3d9480acf92227f1aaf1cb4a584a2480e72b7f3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:37:18 +0000 Subject: [PATCH 05/18] Bump actions/cache from 4.3.0 to 5.0.3 Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.3. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/0057852bfaa89a56745cba8c7296529d2fc39830...cdf6c1fa76f9f475f3d7449005a359c84ca0f306) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.3 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/upgrade-pylib.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upgrade-pylib.lock.yml b/.github/workflows/upgrade-pylib.lock.yml index 3ae6c3ddf79..2af9ce4991b 100644 --- a/.github/workflows/upgrade-pylib.lock.yml +++ b/.github/workflows/upgrade-pylib.lock.yml @@ -114,7 +114,7 @@ jobs: run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh # Cache configuration from frontmatter processed below - name: Cache (cpython-lib-${{ env.PYTHON_VERSION }}) - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3 with: key: cpython-lib-${{ env.PYTHON_VERSION }} path: cpython From 5ca3c2894b43f857297a17d466548e3816041fb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:37:40 +0000 Subject: [PATCH 06/18] Bump actions/download-artifact from 6.0.0 to 7.0.0 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/upgrade-pylib.lock.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/upgrade-pylib.lock.yml b/.github/workflows/upgrade-pylib.lock.yml index 2af9ce4991b..0527c4c2c4d 100644 --- a/.github/workflows/upgrade-pylib.lock.yml +++ b/.github/workflows/upgrade-pylib.lock.yml @@ -809,7 +809,7 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ @@ -930,13 +930,13 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent artifacts continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: agent-artifacts path: /tmp/gh-aw/threat-detection/ - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: agent-output path: /tmp/gh-aw/threat-detection/ @@ -1042,7 +1042,7 @@ jobs: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ @@ -1053,7 +1053,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: agent-artifacts path: /tmp/gh-aw/ From ee6ff22947b9a3cda180c6d6b24c32e9a18fddbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:37:53 +0000 Subject: [PATCH 07/18] Bump actions/setup-python from 5.6.0 to 6.2.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.6.0 to 6.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5.6.0...v6.2.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/upgrade-pylib.lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upgrade-pylib.lock.yml b/.github/workflows/upgrade-pylib.lock.yml index 0527c4c2c4d..8010f2ef869 100644 --- a/.github/workflows/upgrade-pylib.lock.yml +++ b/.github/workflows/upgrade-pylib.lock.yml @@ -107,7 +107,7 @@ jobs: with: persist-credentials: false - name: Setup Python - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.12' - name: Create gh-aw temp directory From e3c533a53c86e8b18b396dcb718cfaaa5a94a808 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:37:32 +0000 Subject: [PATCH 08/18] Bump github/gh-aw from 0.43.22 to 0.45.0 Bumps [github/gh-aw](https://github.com/github/gh-aw) from 0.43.22 to 0.45.0. - [Release notes](https://github.com/github/gh-aw/releases) - [Changelog](https://github.com/github/gh-aw/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/gh-aw/compare/fe858c3e14589bf396594a0b106e634d9065823e...58d1d157fbac0f1204798500faefc4f7461ebe28) --- updated-dependencies: - dependency-name: github/gh-aw dependency-version: 0.45.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/upgrade-pylib.lock.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/upgrade-pylib.lock.yml b/.github/workflows/upgrade-pylib.lock.yml index 8010f2ef869..4d8bd37a005 100644 --- a/.github/workflows/upgrade-pylib.lock.yml +++ b/.github/workflows/upgrade-pylib.lock.yml @@ -58,7 +58,7 @@ jobs: comment_repo: "" steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@fe858c3e14589bf396594a0b106e634d9065823e # v0.43.22 + uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 with: destination: /opt/gh-aw/actions - name: Check workflow file timestamps @@ -99,7 +99,7 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@fe858c3e14589bf396594a0b106e634d9065823e # v0.43.22 + uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 with: destination: /opt/gh-aw/actions - name: Checkout repository @@ -804,7 +804,7 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@fe858c3e14589bf396594a0b106e634d9065823e # v0.43.22 + uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 with: destination: /opt/gh-aw/actions - name: Download agent output artifact @@ -925,7 +925,7 @@ jobs: success: ${{ steps.parse_results.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@fe858c3e14589bf396594a0b106e634d9065823e # v0.43.22 + uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 with: destination: /opt/gh-aw/actions - name: Download agent artifacts @@ -1037,7 +1037,7 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@fe858c3e14589bf396594a0b106e634d9065823e # v0.43.22 + uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 with: destination: /opt/gh-aw/actions - name: Download agent output artifact From b87386f4fc2ab5fad5de0ebf3d5dabc67cec66a4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:49:59 +0900 Subject: [PATCH 09/18] Update test_fstring from v3.14.3 and impl more (#7164) * Update test_fstring from v3.14.3 * Fix 6 test_fstring expectedFailure tests - Add Unknown(char) variant to FormatType for proper error messages on unrecognized format codes (test_errors) - Strip comments from f-string debug text in compile.rs (test_debug_conversion) - Map ruff SyntaxError messages to match CPython in vm_new.rs: InvalidDeleteTarget, LineContinuationError, UnclosedStringError, OtherError(bytes mixing), OtherError(keyword identifier), FStringError(UnterminatedString/UnterminatedTripleQuotedString), and backtick-to-quote replacement for FStringError messages * Fix clippy::sliced_string_as_bytes warning --------- Co-authored-by: CPython Developers <> --- Lib/test/test_fstring.py | 83 +++++---- crates/codegen/src/compile.rs | 28 ++- crates/common/src/format.rs | 14 +- crates/vm/src/vm/compile.rs | 333 +++++++++++++++++++++++++++++++++- crates/vm/src/vm/vm_new.rs | 84 ++++++++- 5 files changed, 498 insertions(+), 44 deletions(-) diff --git a/Lib/test/test_fstring.py b/Lib/test/test_fstring.py index 930e409fb2e..f4fca1caec7 100644 --- a/Lib/test/test_fstring.py +++ b/Lib/test/test_fstring.py @@ -383,7 +383,7 @@ def test_ast_line_numbers_multiline_fstring(self): self.assertEqual(t.body[0].value.values[1].value.col_offset, 11) self.assertEqual(t.body[0].value.values[1].value.end_col_offset, 16) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 4 != 5 def test_ast_line_numbers_with_parentheses(self): expr = """ x = ( @@ -587,7 +587,6 @@ def test_ast_compile_time_concat(self): exec(c) self.assertEqual(x[0], 'foo3') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_time_concat_errors(self): self.assertAllRaise(SyntaxError, 'cannot mix bytes and nonbytes literals', @@ -600,7 +599,6 @@ def test_literal(self): self.assertEqual(f'a', 'a') self.assertEqual(f' ', ' ') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_unterminated_string(self): self.assertAllRaise(SyntaxError, 'unterminated string', [r"""f'{"x'""", @@ -609,7 +607,7 @@ def test_unterminated_string(self): r"""f'{("x}'""", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_mismatched_parens(self): self.assertAllRaise(SyntaxError, r"closing parenthesis '\}' " @@ -632,14 +630,24 @@ def test_mismatched_parens(self): r"does not match opening parenthesis '\('", ["f'{a(4}'", ]) - self.assertRaises(SyntaxError, eval, "f'{" + "("*500 + "}'") + self.assertRaises(SyntaxError, eval, "f'{" + "("*20 + "}'") - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: No exception raised @unittest.skipIf(support.is_wasi, "exhausts limited stack on WASI") def test_fstring_nested_too_deeply(self): - self.assertAllRaise(SyntaxError, - "f-string: expressions nested too deeply", - ['f"{1+2:{1+2:{1+1:{1}}}}"']) + def raises_syntax_or_memory_error(txt): + try: + eval(txt) + except SyntaxError: + pass + except MemoryError: + pass + except Exception as ex: + self.fail(f"Should raise SyntaxError or MemoryError, not {type(ex)}") + else: + self.fail("No exception raised") + + raises_syntax_or_memory_error('f"{1+2:{1+2:{1+1:{1}}}}"') def create_nested_fstring(n): if n == 0: @@ -647,9 +655,10 @@ def create_nested_fstring(n): prev = create_nested_fstring(n-1) return f'f"{{{prev}}}"' - self.assertAllRaise(SyntaxError, - "too many nested f-strings", - [create_nested_fstring(160)]) + raises_syntax_or_memory_error(create_nested_fstring(160)) + raises_syntax_or_memory_error("f'{" + "("*100 + "}'") + raises_syntax_or_memory_error("f'{" + "("*1000 + "}'") + raises_syntax_or_memory_error("f'{" + "("*10_000 + "}'") def test_syntax_error_in_nested_fstring(self): # See gh-104016 for more information on this crash @@ -692,7 +701,7 @@ def test_double_braces(self): ["f'{ {{}} }'", # dict in a set ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_compile_time_concat(self): x = 'def' self.assertEqual('abc' f'## {x}ghi', 'abc## defghi') @@ -730,7 +739,7 @@ def test_compile_time_concat(self): ['''f'{3' f"}"''', # can't concat to get a valid f-string ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_comments(self): # These aren't comments, since they're in strings. d = {'#': 'hash'} @@ -807,7 +816,7 @@ def build_fstr(n, extra=''): s = "f'{1}' 'x' 'y'" * 1024 self.assertEqual(eval(s), '1xy' * 1024) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_format_specifier_expressions(self): width = 10 precision = 4 @@ -841,7 +850,6 @@ def test_format_specifier_expressions(self): """f'{"s"!{"r"}}'""", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_custom_format_specifier(self): class CustomFormat: def __format__(self, format_spec): @@ -863,7 +871,7 @@ def __format__(self, spec): x = X() self.assertEqual(f'{x} {x}', '1 2') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_missing_expression(self): self.assertAllRaise(SyntaxError, "f-string: valid expression required before '}'", @@ -926,7 +934,7 @@ def test_missing_expression(self): "\xa0", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_parens_in_expressions(self): self.assertEqual(f'{3,}', '(3,)') @@ -939,13 +947,12 @@ def test_parens_in_expressions(self): ["f'{3)+(4}'", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_newlines_before_syntax_error(self): self.assertAllRaise(SyntaxError, "f-string: expecting a valid expression after '{'", ["f'{.}'", "\nf'{.}'", "\n\nf'{.}'"]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_backslashes_in_string_part(self): self.assertEqual(f'\t', '\t') self.assertEqual(r'\t', '\\t') @@ -1004,7 +1011,7 @@ def test_backslashes_in_string_part(self): self.assertEqual(fr'\N{AMPERSAND}', '\\Nspam') self.assertEqual(f'\\\N{AMPERSAND}', '\\&') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_misformed_unicode_character_name(self): # These test are needed because unicode names are parsed # differently inside f-strings. @@ -1024,7 +1031,7 @@ def test_misformed_unicode_character_name(self): r"'\N{GREEK CAPITAL LETTER DELTA'", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_backslashes_in_expression_part(self): self.assertEqual(f"{( 1 + @@ -1040,7 +1047,6 @@ def test_backslashes_in_expression_part(self): ["f'{\n}'", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_backslashes_inside_fstring_context(self): # All of these variations are invalid python syntax, # so they are also invalid in f-strings as well. @@ -1075,7 +1081,7 @@ def test_newlines_in_expressions(self): self.assertEqual(rf'''{3+ 4}''', '7') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: expecting a valid expression after '{'" does not match "invalid syntax (, line 1)" def test_lambda(self): x = 5 self.assertEqual(f'{(lambda y:x*y)("8")!r}', "'88888'") @@ -1118,7 +1124,6 @@ def test_roundtrip_raw_quotes(self): self.assertEqual(fr'\'\"\'', '\\\'\\"\\\'') self.assertEqual(fr'\"\'\"\'', '\\"\\\'\\"\\\'') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_fstring_backslash_before_double_bracket(self): deprecated_cases = [ (r"f'\{{\}}'", '\\{\\}'), @@ -1138,7 +1143,6 @@ def test_fstring_backslash_before_double_bracket(self): self.assertEqual(fr'\}}{1+1}', '\\}2') self.assertEqual(fr'{1+1}\}}', '2\\}') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_fstring_backslash_before_double_bracket_warns_once(self): with self.assertWarns(SyntaxWarning) as w: eval(r"f'\{{'") @@ -1288,6 +1292,7 @@ def test_nested_fstrings(self): self.assertEqual(f'{f"{0}"*3}', '000') self.assertEqual(f'{f"{y}"*3}', '555') + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_string_prefixes(self): single_quote_cases = ["fu''", "uf''", @@ -1312,7 +1317,7 @@ def test_invalid_string_prefixes(self): "Bf''", "BF''",] double_quote_cases = [case.replace("'", '"') for case in single_quote_cases] - self.assertAllRaise(SyntaxError, 'invalid syntax', + self.assertAllRaise(SyntaxError, 'prefixes are incompatible', single_quote_cases + double_quote_cases) def test_leading_trailing_spaces(self): @@ -1342,7 +1347,7 @@ def test_equal_equal(self): self.assertEqual(f'{0==1}', 'False') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_conversions(self): self.assertEqual(f'{3.14:10.10}', ' 3.14') self.assertEqual(f'{1.25!s:10.10}', '1.25 ') @@ -1367,7 +1372,6 @@ def test_conversions(self): self.assertAllRaise(SyntaxError, "f-string: expecting '}'", ["f'{3!'", "f'{3!s'", - "f'{3!g'", ]) self.assertAllRaise(SyntaxError, 'f-string: missing conversion character', @@ -1408,14 +1412,13 @@ def test_assignment(self): "f'{x}' = x", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_del(self): self.assertAllRaise(SyntaxError, 'invalid syntax', ["del f''", "del '' f''", ]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_mismatched_braces(self): self.assertAllRaise(SyntaxError, "f-string: single '}' is not allowed", ["f'{{}'", @@ -1514,7 +1517,6 @@ def test_str_format_differences(self): self.assertEqual('{d[a]}'.format(d=d), 'string') self.assertEqual('{d[0]}'.format(d=d), 'integer') - @unittest.expectedFailure # TODO: RUSTPYTHON def test_errors(self): # see issue 26287 self.assertAllRaise(TypeError, 'unsupported', @@ -1557,7 +1559,6 @@ def test_backslash_char(self): self.assertEqual(eval('f"\\\n"'), '') self.assertEqual(eval('f"\\\r"'), '') - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '1+2 = # my comment\n 3' != '1+2 = \n 3' def test_debug_conversion(self): x = 'A string' self.assertEqual(f'{x=}', 'x=' + repr(x)) @@ -1705,7 +1706,7 @@ def test_walrus(self): self.assertEqual(f'{(x:=10)}', '10') self.assertEqual(x, 10) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: expecting '=', or '!', or ':', or '}'" does not match "invalid syntax (?, line 1)" def test_invalid_syntax_error_message(self): with self.assertRaisesRegex(SyntaxError, "f-string: expecting '=', or '!', or ':', or '}'"): @@ -1731,7 +1732,7 @@ def test_with_an_underscore_and_a_comma_in_format_specifier(self): with self.assertRaisesRegex(ValueError, error_msg): f'{1:_,}' - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "f-string: expecting a valid expression after '{'" does not match "invalid syntax (?, line 1)" def test_syntax_error_for_starred_expressions(self): with self.assertRaisesRegex(SyntaxError, "can't use starred expression here"): compile("f'{*a}'", "?", "exec") @@ -1740,7 +1741,7 @@ def test_syntax_error_for_starred_expressions(self): "f-string: expecting a valid expression after '{'"): compile("f'{**a}'", "?", "exec") - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON; - def test_not_closing_quotes(self): self.assertAllRaise(SyntaxError, "unterminated f-string literal", ['f"', "f'"]) self.assertAllRaise(SyntaxError, "unterminated triple-quoted f-string literal", @@ -1760,7 +1761,7 @@ def test_not_closing_quotes(self): except SyntaxError as e: self.assertEqual(e.text, 'z = f"""') self.assertEqual(e.lineno, 3) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_syntax_error_after_debug(self): self.assertAllRaise(SyntaxError, "f-string: expecting a valid expression after '{'", [ @@ -1788,7 +1789,6 @@ def test_debug_in_file(self): self.assertEqual(stdout.decode('utf-8').strip().replace('\r\n', '\n').replace('\r', '\n'), "3\n=3") - @unittest.expectedFailure # TODO: RUSTPYTHON def test_syntax_warning_infinite_recursion_in_file(self): with temp_cwd(): script = 'script.py' @@ -1878,6 +1878,13 @@ def __format__(self, format): # Test multiple format specs in same raw f-string self.assertEqual(rf"{UnchangedFormat():\xFF} {UnchangedFormat():\n}", '\\xFF \\n') + def test_gh139516(self): + with temp_cwd(): + script = 'script.py' + with open(script, 'wb') as f: + f.write('''def f(a): pass\nf"{f(a=lambda: 'à'\n)}"'''.encode()) + assert_python_ok(script) + if __name__ == '__main__': unittest.main() diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index c132c0f4b09..a7672e9a472 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -8450,7 +8450,12 @@ impl Compiler { if let Some(ast::DebugText { leading, trailing }) = &fstring_expr.debug_text { let range = fstring_expr.expression.range(); let source = self.source_file.slice(range); - let text = [leading, source, trailing].concat(); + let text = [ + strip_fstring_debug_comments(leading).as_str(), + source, + strip_fstring_debug_comments(trailing).as_str(), + ] + .concat(); self.emit_load_const(ConstantData::Str { value: text.into() }); element_count += 1; @@ -8786,6 +8791,27 @@ impl ToU32 for usize { } } +/// Strip Python comments from f-string debug text (leading/trailing around `=`). +/// A comment starts with `#` and extends to the end of the line. +/// The newline character itself is preserved. +fn strip_fstring_debug_comments(text: &str) -> String { + let mut result = String::with_capacity(text.len()); + let mut in_comment = false; + for ch in text.chars() { + if in_comment { + if ch == '\n' { + in_comment = false; + result.push(ch); + } + } else if ch == '#' { + in_comment = true; + } else { + result.push(ch); + } + } + result +} + #[cfg(test)] mod ruff_tests { use super::*; diff --git a/crates/common/src/format.rs b/crates/common/src/format.rs index 2842bd0a3d4..40bc9e53046 100644 --- a/crates/common/src/format.rs +++ b/crates/common/src/format.rs @@ -149,6 +149,7 @@ pub enum FormatType { GeneralFormat(Case), FixedPoint(Case), Percentage, + Unknown(char), } impl From<&FormatType> for char { @@ -170,6 +171,7 @@ impl From<&FormatType> for char { FormatType::FixedPoint(Case::Lower) => 'f', FormatType::FixedPoint(Case::Upper) => 'F', FormatType::Percentage => '%', + FormatType::Unknown(c) => *c, } } } @@ -194,6 +196,7 @@ impl FormatParse for FormatType { Some('g') => (Some(Self::GeneralFormat(Case::Lower)), chars.as_wtf8()), Some('G') => (Some(Self::GeneralFormat(Case::Upper)), chars.as_wtf8()), Some('%') => (Some(Self::Percentage), chars.as_wtf8()), + Some(c) => (Some(Self::Unknown(c)), chars.as_wtf8()), _ => (None, text), } } @@ -429,7 +432,8 @@ impl FormatSpec { | FormatType::FixedPoint(_) | FormatType::GeneralFormat(_) | FormatType::Exponent(_) - | FormatType::Percentage, + | FormatType::Percentage + | FormatType::Number(_), ) => 3, None => 3, _ => panic!("Separators only valid for numbers!"), @@ -475,6 +479,7 @@ impl FormatSpec { let first_letter = (input.to_string().as_bytes()[0] as char).to_uppercase(); Ok(first_letter.collect::() + &input.to_string()[1..]) } + Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(*c, "int")), _ => Err(FormatSpecError::InvalidFormatSpecifier), } } @@ -496,7 +501,8 @@ impl FormatSpec { | Some(FormatType::Hex(_)) | Some(FormatType::String) | Some(FormatType::Character) - | Some(FormatType::Number(Case::Upper)) => { + | Some(FormatType::Number(Case::Upper)) + | Some(FormatType::Unknown(_)) => { let ch = char::from(self.format_type.as_ref().unwrap()); Err(FormatSpecError::UnknownFormatCode(ch, "float")) } @@ -609,6 +615,7 @@ impl FormatSpec { Some(float) => return self.format_float(float), _ => Err(FormatSpecError::UnableToConvert), }, + Some(FormatType::Unknown(c)) => Err(FormatSpecError::UnknownFormatCode(c, "int")), None => self.format_int_radix(magnitude, 10), }?; let format_sign = self.sign.unwrap_or(FormatSign::Minus); @@ -707,7 +714,8 @@ impl FormatSpec { | Some(FormatType::String) | Some(FormatType::Character) | Some(FormatType::Number(Case::Upper)) - | Some(FormatType::Percentage) => { + | Some(FormatType::Percentage) + | Some(FormatType::Unknown(_)) => { let ch = char::from(self.format_type.as_ref().unwrap()); Err(FormatSpecError::UnknownFormatCode(ch, "complex")) } diff --git a/crates/vm/src/vm/compile.rs b/crates/vm/src/vm/compile.rs index 97f0f9e97b8..7294dc8f897 100644 --- a/crates/vm/src/vm/compile.rs +++ b/crates/vm/src/vm/compile.rs @@ -25,6 +25,337 @@ impl VirtualMachine { source_path: String, opts: CompileOpts, ) -> Result, CompileError> { - compiler::compile(source, mode, &source_path, opts).map(|code| self.ctx.new_code(code)) + let code = + compiler::compile(source, mode, &source_path, opts).map(|code| self.ctx.new_code(code)); + #[cfg(feature = "parser")] + if code.is_ok() { + self.emit_string_escape_warnings(source, &source_path); + } + code + } +} + +/// Scan source for invalid escape sequences in all string literals and emit +/// SyntaxWarning. +/// +/// Corresponds to: +/// - `warn_invalid_escape_sequence()` in `Parser/string_parser.c` +/// - `_PyTokenizer_warn_invalid_escape_sequence()` in `Parser/tokenizer/helpers.c` +#[cfg(feature = "parser")] +mod escape_warnings { + use super::*; + use crate::warn; + use ruff_python_ast::{self as ast, visitor::Visitor}; + use ruff_text_size::TextRange; + + /// Calculate 1-indexed line number at byte offset in source. + fn line_number_at(source: &str, offset: usize) -> usize { + source[..offset.min(source.len())] + .bytes() + .filter(|&b| b == b'\n') + .count() + + 1 + } + + /// Get content bounds (start, end byte offsets) of a quoted string literal, + /// excluding prefix characters and quote delimiters. + fn content_bounds(source: &str, range: TextRange) -> Option<(usize, usize)> { + let s = range.start().to_usize(); + let e = range.end().to_usize(); + if s >= e || e > source.len() { + return None; + } + let bytes = &source.as_bytes()[s..e]; + // Skip prefix (u, b, r, etc.) to find the first quote character. + let qi = bytes.iter().position(|&c| c == b'\'' || c == b'"')?; + let qc = bytes[qi]; + let ql = if bytes.get(qi + 1) == Some(&qc) && bytes.get(qi + 2) == Some(&qc) { + 3 + } else { + 1 + }; + let cs = s + qi + ql; + let ce = e.checked_sub(ql)?; + if cs <= ce { Some((cs, ce)) } else { None } + } + + /// Scan `source[start..end]` for the first invalid escape sequence. + /// Returns `Some((invalid_char, byte_offset_in_source))` for the first + /// invalid escape found, or `None` if all escapes are valid. + /// + /// When `is_bytes` is true, `\u`, `\U`, and `\N` are treated as invalid + /// (bytes literals only support byte-oriented escapes). + /// + /// Only reports the **first** invalid escape per string literal, matching + /// `_PyUnicode_DecodeUnicodeEscapeInternal2` which stores only the first + /// `first_invalid_escape_char`. + fn first_invalid_escape( + source: &str, + start: usize, + end: usize, + is_bytes: bool, + ) -> Option<(char, usize)> { + let raw = &source[start..end]; + let mut chars = raw.char_indices().peekable(); + while let Some((i, ch)) = chars.next() { + if ch != '\\' { + continue; + } + let Some((_, next)) = chars.next() else { + break; + }; + let valid = match next { + '\\' | '\'' | '"' | 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' => true, + '\n' => true, + '\r' => { + if matches!(chars.peek(), Some(&(_, '\n'))) { + chars.next(); + } + true + } + '0'..='7' => { + for _ in 0..2 { + if matches!(chars.peek(), Some(&(_, '0'..='7'))) { + chars.next(); + } else { + break; + } + } + true + } + 'x' | 'u' | 'U' => { + // \u and \U are only valid in string literals, not bytes + if is_bytes && next != 'x' { + false + } else { + let count = match next { + 'x' => 2, + 'u' => 4, + 'U' => 8, + _ => unreachable!(), + }; + for _ in 0..count { + if chars.peek().is_some_and(|&(_, c)| c.is_ascii_hexdigit()) { + chars.next(); + } else { + break; + } + } + true + } + } + 'N' => { + // \N{name} is only valid in string literals, not bytes + if is_bytes { + false + } else { + if matches!(chars.peek(), Some(&(_, '{'))) { + chars.next(); + for (_, c) in chars.by_ref() { + if c == '}' { + break; + } + } + } + true + } + } + _ => false, + }; + if !valid { + return Some((next, start + i)); + } + } + None + } + + /// Emit `SyntaxWarning` for an invalid escape sequence. + /// + /// `warn_invalid_escape_sequence()` in `Parser/string_parser.c` + fn warn_invalid_escape_sequence( + source: &str, + ch: char, + offset: usize, + filename: &str, + vm: &VirtualMachine, + ) { + let lineno = line_number_at(source, offset); + let message = vm.ctx.new_str(format!( + "\"\\{ch}\" is an invalid escape sequence. \ + Such sequences will not work in the future. \ + Did you mean \"\\\\{ch}\"? A raw string is also an option." + )); + let fname = vm.ctx.new_str(filename); + let _ = warn::warn_explicit( + Some(vm.ctx.exceptions.syntax_warning.to_owned()), + message.into(), + fname, + lineno, + None, + vm.ctx.none(), + None, + None, + vm, + ); + } + + struct EscapeWarningVisitor<'a> { + source: &'a str, + filename: &'a str, + vm: &'a VirtualMachine, + } + + impl<'a> EscapeWarningVisitor<'a> { + /// Check a quoted string/bytes literal for invalid escapes. + /// The range must include the prefix and quote delimiters. + fn check_quoted_literal(&self, range: TextRange, is_bytes: bool) { + if let Some((start, end)) = content_bounds(self.source, range) + && let Some((ch, offset)) = first_invalid_escape(self.source, start, end, is_bytes) + { + warn_invalid_escape_sequence(self.source, ch, offset, self.filename, self.vm); + } + } + + /// Check an f-string literal element for invalid escapes. + /// The range covers content only (no prefix/quotes). + /// + /// Also handles `\{` / `\}` at the literal–interpolation boundary, + /// equivalent to `_PyTokenizer_warn_invalid_escape_sequence` handling + /// `FSTRING_MIDDLE` / `FSTRING_END` tokens. + fn check_fstring_literal(&self, range: TextRange) { + let start = range.start().to_usize(); + let end = range.end().to_usize(); + if start >= end || end > self.source.len() { + return; + } + if let Some((ch, offset)) = first_invalid_escape(self.source, start, end, false) { + warn_invalid_escape_sequence(self.source, ch, offset, self.filename, self.vm); + return; + } + // In CPython, _PyTokenizer_warn_invalid_escape_sequence handles + // `\{` and `\}` for FSTRING_MIDDLE/FSTRING_END tokens. Ruff + // splits the literal element before the interpolation delimiter, + // so the `\` sits at the end of the literal range and the `{`/`}` + // sits just after it. Only warn when the number of trailing + // backslashes is odd (an even count means they are all escaped). + let trailing_bs = self.source.as_bytes()[start..end] + .iter() + .rev() + .take_while(|&&b| b == b'\\') + .count(); + if trailing_bs % 2 == 1 + && let Some(&after) = self.source.as_bytes().get(end) + && (after == b'{' || after == b'}') + { + warn_invalid_escape_sequence( + self.source, + after as char, + end - 1, + self.filename, + self.vm, + ); + } + } + + /// Visit f-string elements, checking literals and recursing into + /// interpolation expressions and format specs. + fn visit_fstring_elements(&mut self, elements: &'a ast::InterpolatedStringElements) { + for element in elements { + match element { + ast::InterpolatedStringElement::Literal(lit) => { + self.check_fstring_literal(lit.range); + } + ast::InterpolatedStringElement::Interpolation(interp) => { + self.visit_expr(&interp.expression); + if let Some(spec) = &interp.format_spec { + self.visit_fstring_elements(&spec.elements); + } + } + } + } + } + } + + impl<'a> Visitor<'a> for EscapeWarningVisitor<'a> { + fn visit_expr(&mut self, expr: &'a ast::Expr) { + match expr { + // Regular string literals — decode_unicode_with_escapes path + ast::Expr::StringLiteral(string) => { + for part in string.value.as_slice() { + if !matches!( + part.flags.prefix(), + ast::str_prefix::StringLiteralPrefix::Raw { .. } + ) { + self.check_quoted_literal(part.range, false); + } + } + } + // Byte string literals — decode_bytes_with_escapes path + ast::Expr::BytesLiteral(bytes) => { + for part in bytes.value.as_slice() { + if !matches!( + part.flags.prefix(), + ast::str_prefix::ByteStringPrefix::Raw { .. } + ) { + self.check_quoted_literal(part.range, true); + } + } + } + // F-string literals — tokenizer + string_parser paths + ast::Expr::FString(fstring_expr) => { + for part in fstring_expr.value.as_slice() { + match part { + ast::FStringPart::Literal(string_lit) => { + // Plain string part in f-string concatenation + if !matches!( + string_lit.flags.prefix(), + ast::str_prefix::StringLiteralPrefix::Raw { .. } + ) { + self.check_quoted_literal(string_lit.range, false); + } + } + ast::FStringPart::FString(fstring) => { + if matches!( + fstring.flags.prefix(), + ast::str_prefix::FStringPrefix::Raw { .. } + ) { + continue; + } + self.visit_fstring_elements(&fstring.elements); + } + } + } + } + _ => ast::visitor::walk_expr(self, expr), + } + } + } + + impl VirtualMachine { + /// Walk all string literals in `source` and emit `SyntaxWarning` for + /// each that contains an invalid escape sequence. + pub(super) fn emit_string_escape_warnings(&self, source: &str, filename: &str) { + let Ok(parsed) = + ruff_python_parser::parse(source, ruff_python_parser::Mode::Module.into()) + else { + return; + }; + let ast = parsed.into_syntax(); + let mut visitor = EscapeWarningVisitor { + source, + filename, + vm: self, + }; + match ast { + ast::Mod::Module(module) => { + for stmt in &module.body { + visitor.visit_stmt(stmt); + } + } + ast::Mod::Expression(expr) => { + visitor.visit_expr(&expr.body); + } + } + } } } diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 7c6035b62d1..a67c0636614 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -503,11 +503,52 @@ impl VirtualMachine { } let mut narrow_caret = false; match error { + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: + ruff_python_parser::ParseErrorType::FStringError( + ruff_python_parser::InterpolatedStringErrorType::UnterminatedString, + ) + | ruff_python_parser::ParseErrorType::Lexical( + ruff_python_parser::LexicalErrorType::FStringError( + ruff_python_parser::InterpolatedStringErrorType::UnterminatedString, + ), + ), + .. + }) => { + msg = "unterminated f-string literal".to_owned(); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: + ruff_python_parser::ParseErrorType::FStringError( + ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString, + ) + | ruff_python_parser::ParseErrorType::Lexical( + ruff_python_parser::LexicalErrorType::FStringError( + ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString, + ), + ), + .. + }) => { + msg = "unterminated triple-quoted f-string literal".to_owned(); + } #[cfg(feature = "parser")] crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { error: ruff_python_parser::ParseErrorType::FStringError(_) - | ruff_python_parser::ParseErrorType::UnexpectedExpressionToken, + | ruff_python_parser::ParseErrorType::Lexical( + ruff_python_parser::LexicalErrorType::FStringError(_), + ), + .. + }) => { + // Replace backticks with single quotes to match CPython's error messages + msg = msg.replace('`', "'"); + msg.insert_str(0, "invalid syntax: "); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: ruff_python_parser::ParseErrorType::UnexpectedExpressionToken, .. }) => msg.insert_str(0, "invalid syntax: "), #[cfg(feature = "parser")] @@ -532,6 +573,47 @@ impl VirtualMachine { msg = "invalid syntax".to_owned(); narrow_caret = true; } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: ruff_python_parser::ParseErrorType::InvalidDeleteTarget, + .. + }) => { + msg = "invalid syntax".to_owned(); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: + ruff_python_parser::ParseErrorType::Lexical( + ruff_python_parser::LexicalErrorType::LineContinuationError, + ), + .. + }) => { + msg = "unexpected character after line continuation".to_owned(); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: + ruff_python_parser::ParseErrorType::Lexical( + ruff_python_parser::LexicalErrorType::UnclosedStringError, + ), + .. + }) => { + msg = "unterminated string".to_owned(); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: ruff_python_parser::ParseErrorType::OtherError(s), + .. + }) if s.eq_ignore_ascii_case("bytes literal cannot be mixed with non-bytes literals") => { + msg = "cannot mix bytes and nonbytes literals".to_owned(); + } + #[cfg(feature = "parser")] + crate::compiler::CompileError::Parse(rustpython_compiler::ParseError { + error: ruff_python_parser::ParseErrorType::OtherError(s), + .. + }) if s.starts_with("Expected an identifier, but found a keyword") => { + msg = "invalid syntax".to_owned(); + } _ => {} } if syntax_error_type.is(self.ctx.exceptions.tab_error) { From 6b4605280e2d3c49a022d958f80e66d3e3ffdd62 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:02:28 +0900 Subject: [PATCH 10/18] cap mt19937 version (#7182) --- crates/stdlib/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stdlib/Cargo.toml b/crates/stdlib/Cargo.toml index e1ac223fb1e..987bca8e88f 100644 --- a/crates/stdlib/Cargo.toml +++ b/crates/stdlib/Cargo.toml @@ -58,7 +58,7 @@ xml = "1.2" # random rand_core = { workspace = true } -mt19937 = "3.1" +mt19937 = "<=3.2" # upgrade it once rand is upgraded # Crypto: digest = "0.10.7" From b7643c08cf3d281203649753bf1199719d4ce60d Mon Sep 17 00:00:00 2001 From: CPython Developers <> Date: Mon, 16 Feb 2026 02:01:39 +0900 Subject: [PATCH 11/18] Update test_winsound from v3.14.3 --- Lib/test/audiodata/pluck-alaw.aifc | Bin 6910 -> 0 bytes Lib/test/audiodata/pluck-pcm16.aiff | Bin 13506 -> 0 bytes Lib/test/audiodata/pluck-pcm16.au | Bin 13252 -> 0 bytes Lib/test/audiodata/pluck-pcm24.aiff | Bin 20120 -> 0 bytes Lib/test/audiodata/pluck-pcm24.au | Bin 19866 -> 0 bytes Lib/test/audiodata/pluck-pcm32.aiff | Bin 26734 -> 0 bytes Lib/test/audiodata/pluck-pcm32.au | Bin 26480 -> 0 bytes Lib/test/audiodata/pluck-pcm8.aiff | Bin 6892 -> 0 bytes Lib/test/audiodata/pluck-pcm8.au | Bin 6638 -> 0 bytes Lib/test/audiodata/pluck-ulaw.aifc | Bin 6910 -> 0 bytes Lib/test/audiodata/pluck-ulaw.au | Bin 6638 -> 0 bytes Lib/test/test_winsound.py | 187 ++++++++++++++++++++++++++++ 12 files changed, 187 insertions(+) delete mode 100644 Lib/test/audiodata/pluck-alaw.aifc delete mode 100644 Lib/test/audiodata/pluck-pcm16.aiff delete mode 100644 Lib/test/audiodata/pluck-pcm16.au delete mode 100644 Lib/test/audiodata/pluck-pcm24.aiff delete mode 100644 Lib/test/audiodata/pluck-pcm24.au delete mode 100644 Lib/test/audiodata/pluck-pcm32.aiff delete mode 100644 Lib/test/audiodata/pluck-pcm32.au delete mode 100644 Lib/test/audiodata/pluck-pcm8.aiff delete mode 100644 Lib/test/audiodata/pluck-pcm8.au delete mode 100644 Lib/test/audiodata/pluck-ulaw.aifc delete mode 100644 Lib/test/audiodata/pluck-ulaw.au create mode 100644 Lib/test/test_winsound.py diff --git a/Lib/test/audiodata/pluck-alaw.aifc b/Lib/test/audiodata/pluck-alaw.aifc deleted file mode 100644 index 3b7fbd2af75a0a190b0a507ec43afbf8bd2b2267..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6910 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 B^uhoD diff --git a/Lib/test/audiodata/pluck-pcm16.aiff b/Lib/test/audiodata/pluck-pcm16.aiff deleted file mode 100644 index 6c8c40d14092893a4b4af626f6caf5c8c826017d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13506 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 Sc_W7FgxYhTfS>2TI`IF$J<(PG diff --git a/Lib/test/audiodata/pluck-pcm24.aiff b/Lib/test/audiodata/pluck-pcm24.aiff deleted file mode 100644 index 8eba145a44d37a837b00428c6e39c6d68e25ac4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20120 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 Q5k_cPsXyxS68(SuFDCQN5C8xG diff --git a/Lib/test/audiodata/pluck-pcm32.aiff b/Lib/test/audiodata/pluck-pcm32.aiff deleted file mode 100644 index 46ac0373f6abbc7454a53b536daa27589857604c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26734 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 diff --git a/Lib/test/audiodata/pluck-pcm32.au b/Lib/test/audiodata/pluck-pcm32.au deleted file mode 100644 index 92ee5965e40197bd2a086841f37f0d7ca681a03b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26480 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 z 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 z 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 m_6kLwwgKwD+q9R)gd8A{4wg&Mu=!@j^hN| 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 diff --git a/Lib/test/audiodata/pluck-ulaw.aifc b/Lib/test/audiodata/pluck-ulaw.aifc deleted file mode 100644 index 3085cf097fb18a839a3ae5b9a47bd75cdef5f8a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6910 zcmZ{pcU0R~w(q&Q$z<+~Q(TDwNi@^c=t!thg}SJsh+f<*b5(0sQ+E2X$1{bigu|0|H#N#ASbJzRpt@ZX=d!4iQIqS3b@3+ryo!?qt zBD0(x78da=pG+(yvWm;Y!opr1_@peakVy~a!`=ydB`j>$cG&OpcJYfsamdb{Xn8*@ zjFC?-4h653o;cUm8J1sFNeV@8S9F{{dhEmKinFIqw;gTiY{_RZn4x@l{<-#+wqs{M zjQ*eP(a`AXmZR^t91W|eV1%d#S3`UL$NU$+*t}v0Q|({)!t&OtwdoDw8(G(&oCJ9M zo|wsK1mv?AN+KT-iF=Kj%TUoE6an!v_06)sbcLHQDc+v=oR1g{8~cP8#{XjC^C{%` z0a5(SUu89<@_J@Z@=$CEWH(p^N(X%j=El`S3Fud-h^i$P ztOis58GF~s|5{C6{9ckI8sU965;pn?@81No)Bi3>oB>ZClzyrjQ1cXjo&AI8cjK3& z$r`W^Uiun}pVAZ87`+cd0h2&i!B^wquxZRImEcbGarDu6_NNVRpZs!*XOPRe6A6M( z$HK<`oqtHUZ}t;8Tnd>Dmt9hSJGZ9%t2kT~Ci+esREImrtk-jkp;zO6iGBwH1KYtI z$mRGXI2ZT3!+X{ww`kj-&i`%(pS+^1{#26X4ero6_xb-jFaAS`=Zu%?XUj8o8C)2OW zzgqfF?c0h-`K;zk?S$&Odn(%AMl74SfHug;zF7_L64(I^546+o{ zpL8u7{c)~zmuUaQs0?RFYP(E^=5zACVo=vibBp3$;VZ(c(^sT#X#dyp71bZ4DauRp z-DbWVIt@OwUM;`7-z)nDj7RlOlSp!nPTxAQL)-%hFd|0*bvd}R!;1?PgH zkulvM4(J_lZ1ngcA?zAKYe0+kkG;v;niZI%DK}8V>FBhta|O)f27>r|!CTx2;ippv z6&&4v&PB;~DXuMjsoPff&h`mj7et6J853Fib7NqUFraf2?92~0%F%gjPxF^YDX}t`AlE{N_-W-dY9e#vH6Jg!SRVf3=-V)_fnrL{W zaB$b?6~UM^#q!^dKHV;Tvj6j&-KH1vZebMn595QHzRqxLI2;md0rf^PAmQ;j(6OxE z`Y|b;n=tg&NTit8;?77<>Q7K7@({hG2{no@8IttB!4V3s%i*`bKK$+4UU&Epmw)@4 z<&JzWx~296Xc+7hrmKhG*EL(1AT82c&Y^6g)Ma8#5k1 z4)3G!rNq%Y{rsLi16cm|s^Z$7;;+l2&+P8#>H5MvA(`dUIB#;3q}Lx2F#Dn5(fG*T zND(+H0fH14^xAKX?+hbu92njwtIFpkW25B|VcZ0~zlJw?W{}*=>yGG;6JB3lEEvi8 zhJdZz-6`AtLfbbrH(DF210?A`tjao=v==v*CFIM^q; zM!t5e_X$6`5)T!|3F5jl`nAdYh%w3NSEE-)riUSd-9N(+L-Dd`5M&P|C8ihNSC*oU zkYg0xvhSzW6A0OR(Ke>2-XjhF0c3P%_dISCbs?<9sN z-AV0XjW5M(ASR>jnx(kWq)1oO3C5%@{8*1K=a%Sa&?2JPR zm?Tx^2+++L>P8Jf#2qqh7Z;tQwsu z(MDwLi;f4WV~28wBodCPPt`XyDp%^NiCIZWsnAu7K;OgZ>v=X5r$Z2gh1c}g_;%sJ zrUP(jqB?0VB^4TvM3t*mY9)0IL-jWB=f{j@P-t93j<2t+@g-r zg+{;-2xh)S*EiDBD;|dWl2K+t-~6>v!C2gd@DxZ~NumK(fPiP_RQuH?HM5J5cZ(sK z#X@w=a<>9fOKHf;~y_8HUo~!KU za5&iyA`?N;h{W1dT6PW!&)isYEir2GxK`@4iYpLLX5>dgA|jJwUBx59fq}8XDXvBw z^qpn?O2?ryGYd+*RzAP4Z)D`iGh`|bE0*^U3=Ce_iH!$?6BBdM1q5s^k?3_;jFl`L zjz(1~1pM&{a(*;8G9o4}uS~=n92gqmiNq?0x9UjQd0bvrW`5r7wQ=D<|IqN_FDL{W zW0H*a58Y7jz@lS7hfg-J#HYTL82odi7Ea%u4tGuF{mEZPHTe8WOEJ$fy_cNA1KF1ZVYe) zCq7QW=g=oqz5Kq(rg9J>Dme|Rhr1AIDClvbW^TdskjTtCv8)wHMLHS=k`NJ-lmy_p ziZQNq$iSKB%jVQ|=3Fuyfy}`NE#tg_f#J~(O)?{gDs=YD_KT}dM`eTH7`Pnngu~Jk zC`CqzMhz64MgQnn<|_qyRv{!MB0f2BDT`<4i{^$ML(0+V8KuXSPeLGIIhnS)iK!vZ z2Wkp*Fms)C-IhJv|~vI2o-Olac6MA@?fd7olb&3F)vi%a`EVj4k5 z#3AEzZc(|bS~J`Xw=C*7a{^6~KaK!FWF%`+6d5LlXMRyQIXXVP#H*QUTft+pk?ROK zO7x5?Srj40vnGYpeKTBXl{ubxDDx+T7+wO4hbLsL7V@7O%`iPur(PIQiPS|v0y#E4 z9coO`AZ>YodtqjZJ2|p6RxkYXTroW}`yWVo#w3YnR7sIDnNy<48x#D=CRI{L0;UZq zgAt*Jph<}JTtSt^1J|c5s8wU~DS3$wT5~8T73NP_g1b@X+IjV?aC&TUw3YX8h8E1o zYtNLY&*J#U<;xkW4C%!5z_?KK+bqnSNU6+GAWEQ#$;r^?X`(#aF@!Z!t5NVIvtou4 zv6@IgAb)}`!vblhf(4&MJi}iYJHg!&RjSjQYVh(L8AeDGKR{X1=VxR?Q{$qW6X~j? zi=;eNCIz0Hl9JpGpUSb4Q(t787F9yAc)DJksY|MWquLQh*lmOvJzptXoSoG0KjPjI zw2ITsj0!nHi5oAOs>8labxXAz+2o{aB3GJhCe*0$%nT?Tn!1R(oNh9ygbQ&&g;mEz9S z_W0eYEP3Iwe0!-;+$cOkJ%a3fuK}{{Y5RQVUk`Qx9V-^;RCayF| ztx}a!QXqwEplf8h19ub!pysd(6zPs~X-&2>6PQKG@e2ya@&ox(!6Vj7=9;{Y06W#f91qQO5Ex+kA?9ADfI5k?UHkqA^ZoYHFleX$z7X+k$ zN^f358q@rS_fJ-27H42FIqO;4Y$`ez+ft;hl;2r2RBN&{uw`uUetR>+Ptuf_sfrr; zW4CQvXVv7-qn74PX1meJx7uwPo~?k$D+QDWy)NC9>b3rSt~Q69jYp#|WH02DqA{2{ z!ZJ;9V&2iLLu=v7xz1mow$!hc&eJVSWu5F-hi}tl(~)#2gWh6x+MEKb0myco^h~>@ zfWn~F=NMDns}El^=F)QVQJ5x_7FCDFVJh)JiK=dH<6SdWk1(KY$8WWC-D59Q0W5Ws z>?IIf2OLI*0c8fPCb!=sbS&7=j(WGm6=E7SdW<2}{_)06C$1b_oP(>(T|#%D@wrT# zr$Eh6zxUWfFo%wpy^7zt zL%}}8FA~*d{w~T!rav>lTx+?1>L3!VmF1*43n=zH+@M_`u+jAVVqabW zPs5RL$MB{CH3eYR97jLTd|2|R@#&G5z)QDJT!_bo4N59e+!dabq<^Qw_%EH*oV9p|D) z?D2c3{`Z3F6_sD*wm9%EoWEn8{R8nBwgyM zL`&D&yOxWN$Md(ee!w2ItQgCTg{Gio%WiZ@T!4%2Z4PLIs({*SbQQU={`!rkC*%`& zHjY_G@e(_Ws09@Tw+eMcJ$0*`#74E?I?9hUAKN?uoOeE7+|&jv&VXgbR2O1ySvIVI zL+UU%%iMK7ec+!=y_@332CCLO9#Kyb8gS)h)Bx#XF}<*~aHr5f)KS+;={4v^Yzwo! z>G0#@)^}Ym_4l}c0aUaxJ{eA-lOYB#T1 zf>xz%-qz|UciVk){yCr4)8xSgs5g&3DEqLe4OdNJY*MyJ6+}jHR}nzek=^v#3S2Fw zp3>ORbiU1f+;z_MckL~s-{V-bJU5X{bs^?WKy96~p0}5|0-pKM*QE7a@DTi@wZq$0 z9}y4ZYfG3v(r%M$N*Ki*#ZIE0Y^S%B5vnnDqnZRP*IMt@z9bzZu#3yj(AO#TBnGjG=p!zZ0a{mS zVHKv9$gXWT-n7#0I$`@*w{2K;de&^47KWK=zHQkAbbuOIu~yjs;nI2LJqw|eMf4S| zHg48Gr*`Ev6jgRGZc^LG%#wQIYKehlq5jG&sl-+n)mF2QG_JS0jypck?-*{{eXEY! zK&6>(-nML7j3K5AVA;By2KRzT>vni3zQUEdJ54{(PZcy3vg(-|v`$JXskY>9iHT&U zK4ehKajb%x%DRsFttR&o*J;C#%eQTTRo4bkXQ5es3Ne8Y(*m$(;t0Zl<>| z$hy_?h<;}rwSrtt+9|Pwn70|sGJI8jby;m2d#BOc?m20CvAktnS#fXJ zS^%2mbcpE$G=LFkwl>;ruEh}32Flml9+v-!c({OFUU8rCg36+>NWYM*WHWV@ zUR9b`na5(*G_!wd@U?n7%^w@Kfwh2l!_f)QEk{D{(+4a7%fJaM+wOJhLR6!>#Zw$$ z-fX{L^(*OkVFRP;3G*?nmQqE2kK`nqDFHgWw4j2(qSrLmU2F(6dyfGh7sOn6N9<>eKUi-2g6r;|?WI5+a38oCs&Xkb*4efk2G^2%+5ON% z_ET4zH`zbZ-YssS)|@K8&uFGqQQFBtvW4oRcQHxjc~z8ZcI`>_dXvAydCK(Mvgut} z_dIe`S*w8^;I`FdT>{puY}+%3$)yW1Z+d7x%4*}C#wU!k#5Qu>;fj0ABXkzEiLy=s zs80GvOj>z<6}h^$wv&Cg(ckXwGQTw6_pIIYZ98jimDXq0yH<;J$?CH<+RiyFE`5mU z_b`3r7544sN2MQ-4wLJfs_vI|F{)_|)O!>g)lPrHWR@3Hl~h;P9%FAe2HLzQEFYO4 zxNolc@3|W66}CTFw`>mElFeyrwRbx0u4VVK$K|OCF>7zNKCJkNe2mmk$9h5Dy zr#_}SXmS*U^KAMfO%51D4v52*$buIOOZd_{%9Cx0#{M~VPW#z8#oU6*w;&|v-br>Bc z$8%@1Yr|vq8hjSt`+nL==|n;CWVF4_v+&IpzsuB22GYZ>gO zhWDE`+JeX3=Yb#Xo59sP{$JfS&PL}W=epDEG&=9Qn%&!8i_hpc`yU4wE6k0S?Z%&& z?@`(r?31iVoBx`W{_J6TDuA=c6B8yZeG-EChv?mZ7Yvu_2~?)aa1>RfE+ zQ|Fz~jbwDKyIVcaLO&G7fH`m{Sh~Wv)p)=8dHES?2fg8F_2Y`p5c4p7i{WJW%U}NX z8@VVWIwbFW6&ha+%L@zpm*0n?UH=mO+G{vMd`o3MtsoTd33=&y=RUuC$-GB#O?(?n{z2pNa}Gp&fJ zr9-G<(nZ>f)qm@Wv0YHTJoT9fIlgD&W8ogr=To1}pe7GXQ|A7z)F`jWKbw9|^1{>? z%D833tvKrQd3~89NtKX&U@a&I^aWUu+zcaPo}(e_SJ?1IY|W?Ky=VSAnz?*Mo*^9* zemk~j{A1yNi05bjQ=UEto;{-YL_4Gts{cO!7wNxEUQlEjz(GXypV1;{Uvg{0K?oI0 z23-bUPC>wDvCq|kPw7r#j;C-xX?eNli+e(|N+p;|6Mr(XXW~Casgi^9AFB{@$b5|Q zg6yIzmVcN^HuSotT*fa5!_|oK`LZFcRqw(Vixl0ba-wvPWO#O1bHH}I z^{+TE{Oc48Bp#9k)`90CU!!(}kK+FtzSQkgLR2Ed=SvaYMfr8<9x-G3Th)F`S;s#7KKR$kQxNd(F@RSg7g9M$ zb485%`32vS+!7bQc4p)_xZ`iz2uq(jW%f(S0nuLR$FqM@eYx^qOE0VA)boZfmZr4V zSYY7Kxr}dR~>Q5iEgSlAdpykJNq2 zQ-@{@1X=%NuIM@G$8-BsG~>s{Sj~QwT6b}ARLhrNkp72oujC7Px-s?yV%HQWT?VIu zV!>-*U&58N>p2Atxw?O!1Izf@bZbS=#hQ59myF&Xj@6}>&Pv34q<^2QQ5`aVY&xmk ztK89CTo}|!cFhCAKTE!nub2+-3UDvN@kw8U3qgm$HIS9W!HlZ~nDa%7z0yNd<4U|a zqw^vKR>Cj-l1W>)>X&8xlIJ9sXD=yUSo%M!m$ZLTKs6T@dUZPGg~@c`bK)U+pLrh_ zhCK{RO#B9<104eAK<1ML&@0%g0A}VT(MytP`GOTvdx>DjY@;s}Te=>vyd}FT{&x|3 z>a+P5b)OqQ(Y~NKtp3NsHw(Y2znRgA{#9Hl|I!lE2rdG{;}Uy8e9$Z4q=d;-3H&N? z$&8U6ns`yTH7~X$P_Ls$b1>Oo6^U6V%|zK1@k@eO$tN>M)O_QA>Eo4q)mK-(FmCJm z=Lf{ki(_RMtm*7SMTxtn0MrM9Kw^{R=^|7gdB%Vj$Bw-q$ekYxWe}BFeFzNVYTk8f zz?3)JCxDE-DEwTCSC*OoX6(`aMK8C1>YQAeRa}w0D0*S`>lHn3KMsTdC#iQ$BqS#J z`^<^#euh{>5<>VdP9)EdKEhx~@ZQXN=n$&EQnQjR9UUth-aCFtJfVQv|HnOG+-u4V zeRiYQ`kSg(5-<46q7z zrs>j!$bRya4lR<8$Om8GOT^byh+AKEf3tqT7xUxAKmK9Atr`_)kN;KNrn&llGVZ|c z-Ot7M$1g*Uq-G$**#4c-Iqo=YX#dz@g^E6d$Vw3;PNqyE2IwLMY5ew}sBixePIN_E z(bQM*RZYSh`;PYYd~TbP&kN}M7X=xLYmbQ7L$H_xLR^2G6da!hLCH${UDqdfMv>PK zj~-Okmk2X)2`Y#rc?vPuB%FS8n9?uojU7yuTw7f(9V`5rh-=z+O1b^{(!h*YyRWFuZFFZ2y6 zuZrY+)Ns7upd%e6O<0V-6+fRMD;QoF9o6;XuF9|F55=ixN(C@+`jspR`-mx7eQEL^ z6BENt14l*=Yc&Ku6p?rkbRLT1!I3vo^%L`##S`QC;~?pww-R6quq-YLQm3m* zM(0Pa&tD^4HTN>cK)xF}f;4f;HPq-W$b7u?y71yy|FG+N+{6KZo;#e%g2aFh#WTUz zGR5T}OT0=qe|f53Oc8_T57vS*O<-v}0A7IUIReS>fb=Tm>WTjEMG18Tm?T-8+?zYN zlqrgxkdJ>ketB$m6e8aD3miF;qD%lm_CuhF{fL1o=u)f-tL{}^nbl1}6bINxpggb; zq=6Wbwk?ThxLR z`!6GtAyUv-;#BV1YH&i-Khith&k`^e;RVnXYw{FyxV~2)>rWg^5s6gaE??H@6fw8< z=jB7kGv(04%)^;T6rVA=idjr_Vxz;RUWH5s_P&gYg#^z zk-6z9ywq7Ff2Vg;)C(OXlzCjQb`+K@#vvW)YEBT zKqZ(*SP$kLg5-fjNkfGqwP)1YzgymjW^V1>g515~HXFA_wc~S@OR@O}6H-9Bq>-W#xtwnu&<@Ou zt2D+2Qhr887A%StoBH?zeLs&R8<9jw*;Uh3kxO!fcNhUn*JbFTS+Eoox<;qfX=v+M zTA)RwpO^`2AVr50z#wL_0C#;(IHDd{9MTB&%SL7o1_?(ZStWAgz*t|uY!nvEMB9i1 z3s=X*6Upz#Kq1MM>1KE-5|LNf5Ykz7tR7r2km_X_gjW*c;&YPb2t!l+Vd-_v z=(5yqchNVAg;`nIWtGO&(TTnR;aJfezTZou*NXMUzvMk_2#EY+`b8wNyAf zG%_ZX%Czo4{jut|@x}RhCB?VbCnZCJBcsdD&`1o{Djy#lxvtxRCnSPWp{Po9HKwQ% z>pZ(;f80_^ET;qNX_0jDMrkG(6bng3y|XA79~l<$<>E!Xl^d$`VaVAzINYlb=cOZq z!+hDVWpE7U=-kx6*x=lISa5oL8XWdE>Pc=PF+0H2S?u>%?FycU$j0Xv5HTxE{PRVBOMD#i%rbP00;v0gg`N3<}ZkpdL74BL_r`?g@lNG zQaCg;I)2oU$tt8tJbm+nvijHK3qS}gLWS@k;5lj3a*N!c14`e-{ODd4X~ZUW83Y=e zl9|4eFLa5d`ce0Yc6@eD6Yv(3kw|!9o|7{*Gr}L^ADhVxpqs^8eYznH<9s|8lubg- zq1KQYsAK~5uGFNm;-cAW;Z=cFxYEdgAmh?tX*!gsX-aMr1o?{5NwHeDU00cdf}xQ% z!py?>#5n(%2&%z&M8SUJ$TICD2nWe+%$~`550#2aDq^>a0F^D@hxGYPV_Lxis~wUX zmw`-I!=)vPm9WG;Hp`bxXy-ZtRBRri2yV-hofS?^j0c3U8MIck(!V%rq<4d{2|1@x zGg*zOQDuPQ3=EbT3^|+4tAOWA8{R46q$v`HfQVMU<1koiM!iKOf8~J*%p*)mK zgdlS>4N!HimFZttmQ0UNj;;tB=Q`I2*aFlhQiYcOERZitk(2q;lG%Ydfui1)LQ2hh zij*NL;VFo;+-RBTvBd^Ap^Uo4F|AZr?nt8~<>bIDPy@FZOXX`VLYXd1Q?rG%4Usjv*>W|6qw?njui7j@bR)r^X4 zgf*rXX2CD0PHHL?6&s4Suq6hJ$;SON4s_m6lS*zq2?HKBm{@%EK zUcaiURl}AGBL)t)41q>uk&$|oB_D^ykRm*VL!tG^OBDzMY@NbzvxeRqSY1 z)|ESR;W@O5u&8#gK2SXtKVm;ay>9$@A}Cv3(!HyL8}kB-$J=R0JSx95+mvm}Ct@(f zM@;ntrOBoyD^WT)P~H_FcM)7TJ;6v;cBtLqh3`~8Wsw@Ups`v5x`-%rHj3J|Z3`m` zn|?X8h%o1bR(iTwS;g5HLY^ykxu615M0ky?>rlE@7nquC9o#~E5Up(^`AYPqW{Qfd zx)C(*=v^ATu4qYTwRx6(BG0BjI~v#!hZTUM2FuF}<~7u;`)c?ak{|3k*9; z!%_a>4sKbUMzRrYHrZ_+r&sJS0|oB0{#l;_P@9)bg%*@A`tY~bB6?v78p}g3p*a{l zwvGT)YMb<%ui9`Xq#5lzd9(fWUG8E%z}E4UzXOpCz-?ig(Kf(g^#ubG_o5TyZuZH& zyQalp!kV*O|Gc?#3SWb%D8$zltzeE~2t_Qszf{N6z4pjYwC%2!t7Wa@=buh2^Bg?G z3DpnwwW!N$YqVnQ5w|6{8It<6ZoH?~r}iowYO8Lw#GD1RM(%$^`2G`a~~SdIC_@OsqR|CVOPM`Wh(-ndCZ}QYttdMztqnN z>H`Y5+NL#A&Dr+q;MT+PP8_SSf=saDwy_mhD()d+nWU=@^-wqSLV01EeinH_F=HQdHxrj1I3DUx*u+pz_Xsr#FTszf&?>6}ifsLk=mpBPG z@Dv;c|31M~rlWgWSicqC&fBEjX?u9`iS-%q)V!^ZuKImI#QM-mu+=y%{@=s$h&)si zqC|j*Dx~pf?PqNHK!yL|CjEUP7sDv72o;A3bUYbFy=5t}(gCaISx_C81v^9JNN7zR(fGB_I{?~Q9Qb9U>bH`%qADWg zc3Ggfnn)(_h(M`5dEx7W05nxNL>cVgXy;Aj|{;B!6jRF@?w>dP^58EzC0Xc3+arAM(?JuSIlg+K|=@Vk)QcKt+;j-n*RmM|x8L!|N;S~FHd(Zl-_7B|OFWg)T0j`LB%~EYCvxb4Y zE{j+01$>M^TX-p=4eJ6HZ@CW_YTo31M>$R4;#nMOfOM*yR$5znv&={`(eBoex#)KM z(VAmzC$>%lZ+o6BZ!Jaap0It*%GovV*>5@kx591qRQotVQ}};PlaK1dh3hwtKBAo^ zw%}{3X<_oY3PxE~*-n|6WTdWFF&Z(gxOP?-ulvzS$E%)aru&O)HeU#c+PW;|*7t4e zfX}INExVh%^#NN*7t#e+{Iz~un6+_yhxtYc4`0V%tWzFUvMN~R=gY0TW{6e8#&WT( z^tRTH-@DzXy&sq#F0ESw0cX_yhPA@lWsBM)4vllc+2OA7xr6$UKDgxP`SBszjpGlh z-!1RJH&B^d)Vt(b60`hFIY2T}e9Wdgd=s{r+S*Rvs>dxmXo}-F7IjQdE>S@>hZ5Tx9m(C z%XZ6t2QUIUAmXTX{pL0L7yOI6n?(wiM_ad=pU`@WTgvOYnKx*i6jo(3DOzbE+i8EW zD(i3!~^H?YrhJ zCaaoIU(!(B)WO|p4Rra>Sbtl+>sVX!Z93Zly8ZRtoF2fio3+i+>T-IQeak+puOUzt zuG#2(Q1bz)yOdK?d!P9$olRwv{~$XkHd>TXUq!4dX0saGxKCSx9f4D}56#=adL*#v zJ_RuB$9Byiun4RIryX2Zz+>E1ExvYtd6;#h>wf(o^_17h9*T_` zW^k)YYl&<|V=L#w=5Sl!1n{nT$G#B?ZF=5uFo9O!F0cly?Dq6Kz;Q-BChv;R?0eHs z3Nh9@?=}5G?I~}iH=M6}z-Xq`QBP6=yEy}lPF6)Nv7Xk@()4C?loveadc*Rg{Z=rt z;rqo^1#p1-z>VFME5LmR$9dOn-ZfW!5B-!7E!wuleM~=F(N1eTTXUb;Mz5!KQ6dyO z&CBRvk!y?VsSVtwGu(~V;8D+6>uxcDwT-|dcfErR>;ShMR>v~1?%+Cqc3Zv1UGs*Y z9;8NFZ?}HWe3R5g;dIyCXB}g(X*}u%6`*+-=UDWbl6p!*Q`2egoz_s7ugCVg?Y@8g zMsVBHR~j{TWI&F zPMVAH1B+EtT3^}F&~$>k-5Tx;oVK60J@DOF58d;%x@w&tIPW@L&K0M}+2K0nad}sL ztA4M)e%EZg+3~RU9OVSLh12k``fX+ty_xnM%}sYP?y+iX%j-!E^-bN}N3D_0;3?n( z+m2^*J-p@ba@V@fI`6ssE|U{*o^T!Y_aL?UkeZlWUdubY&CbY4-`l`XuB}M)cIcnJ22YFok!QnW z^H@Chy=}hjfIVml*+SoinQN@g_U+cEtXHXBOzs)>qne{E4)ZwU2E)w^Rh_A4G?F-s z=9ZS%d3Uk&tuPRFW|L!H+&ubpLahLmar{+J5n8G-sIhH fds6cT?I@$=c*A$Kr*_S5#$Bd|8LD~q(Let`eo3ow diff --git a/Lib/test/test_winsound.py b/Lib/test/test_winsound.py new file mode 100644 index 00000000000..9724d830ade --- /dev/null +++ b/Lib/test/test_winsound.py @@ -0,0 +1,187 @@ +# Ridiculously simple test of the winsound module for Windows. + +import functools +import os +import time +import unittest + +from test import support +from test.support import import_helper +from test.support import os_helper + + +support.requires('audio') +winsound = import_helper.import_module('winsound') + + +# Unless we actually have an ear in the room, we have no idea whether a sound +# actually plays, and it's incredibly flaky trying to figure out if a sound +# even *should* play. Instead of guessing, just call the function and assume +# it either passed or raised the RuntimeError we expect in case of failure. +def sound_func(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + ret = func(*args, **kwargs) + except RuntimeError as e: + if support.verbose: + print(func.__name__, 'failed:', e) + else: + if support.verbose: + print(func.__name__, 'returned') + return ret + return wrapper + + +safe_Beep = sound_func(winsound.Beep) +safe_MessageBeep = sound_func(winsound.MessageBeep) +safe_PlaySound = sound_func(winsound.PlaySound) + + +class BeepTest(unittest.TestCase): + + def test_errors(self): + self.assertRaises(TypeError, winsound.Beep) + self.assertRaises(ValueError, winsound.Beep, 36, 75) + self.assertRaises(ValueError, winsound.Beep, 32768, 75) + + def test_extremes(self): + safe_Beep(37, 75) + safe_Beep(32767, 75) + + def test_increasingfrequency(self): + for i in range(100, 2000, 100): + safe_Beep(i, 75) + + def test_keyword_args(self): + safe_Beep(duration=75, frequency=2000) + + +class MessageBeepTest(unittest.TestCase): + + def tearDown(self): + time.sleep(0.5) + + def test_default(self): + self.assertRaises(TypeError, winsound.MessageBeep, "bad") + self.assertRaises(TypeError, winsound.MessageBeep, 42, 42) + safe_MessageBeep() + + def test_ok(self): + safe_MessageBeep(winsound.MB_OK) + + def test_asterisk(self): + safe_MessageBeep(winsound.MB_ICONASTERISK) + + def test_exclamation(self): + safe_MessageBeep(winsound.MB_ICONEXCLAMATION) + + def test_hand(self): + safe_MessageBeep(winsound.MB_ICONHAND) + + def test_question(self): + safe_MessageBeep(winsound.MB_ICONQUESTION) + + def test_error(self): + safe_MessageBeep(winsound.MB_ICONERROR) + + def test_information(self): + safe_MessageBeep(winsound.MB_ICONINFORMATION) + + def test_stop(self): + safe_MessageBeep(winsound.MB_ICONSTOP) + + def test_warning(self): + safe_MessageBeep(winsound.MB_ICONWARNING) + + def test_keyword_args(self): + safe_MessageBeep(type=winsound.MB_OK) + + +class PlaySoundTest(unittest.TestCase): + + def test_errors(self): + self.assertRaises(TypeError, winsound.PlaySound) + self.assertRaises(TypeError, winsound.PlaySound, "bad", "bad") + self.assertRaises( + RuntimeError, + winsound.PlaySound, + "none", winsound.SND_ASYNC | winsound.SND_MEMORY + ) + self.assertRaises(TypeError, winsound.PlaySound, b"bad", 0) + self.assertRaises(TypeError, winsound.PlaySound, "bad", + winsound.SND_MEMORY) + self.assertRaises(TypeError, winsound.PlaySound, 1, 0) + # embedded null character + self.assertRaises(ValueError, winsound.PlaySound, 'bad\0', 0) + + def test_keyword_args(self): + safe_PlaySound(flags=winsound.SND_ALIAS, sound="SystemExit") + + def test_snd_memory(self): + with open(support.findfile('pluck-pcm8.wav', + subdir='audiodata'), 'rb') as f: + audio_data = f.read() + safe_PlaySound(audio_data, winsound.SND_MEMORY) + audio_data = bytearray(audio_data) + safe_PlaySound(audio_data, winsound.SND_MEMORY) + + def test_snd_filename(self): + fn = support.findfile('pluck-pcm8.wav', subdir='audiodata') + safe_PlaySound(fn, winsound.SND_FILENAME | winsound.SND_NODEFAULT) + + def test_snd_filepath(self): + fn = support.findfile('pluck-pcm8.wav', subdir='audiodata') + path = os_helper.FakePath(fn) + safe_PlaySound(path, winsound.SND_FILENAME | winsound.SND_NODEFAULT) + + def test_snd_filepath_as_bytes(self): + fn = support.findfile('pluck-pcm8.wav', subdir='audiodata') + self.assertRaises( + TypeError, + winsound.PlaySound, + os_helper.FakePath(os.fsencode(fn)), + winsound.SND_FILENAME | winsound.SND_NODEFAULT + ) + + def test_aliases(self): + aliases = [ + "SystemAsterisk", + "SystemExclamation", + "SystemExit", + "SystemHand", + "SystemQuestion", + ] + for alias in aliases: + with self.subTest(alias=alias): + safe_PlaySound(alias, winsound.SND_ALIAS) + + def test_alias_fallback(self): + safe_PlaySound('!"$%&/(#+*', winsound.SND_ALIAS) + + def test_alias_nofallback(self): + safe_PlaySound('!"$%&/(#+*', winsound.SND_ALIAS | winsound.SND_NODEFAULT) + + def test_stopasync(self): + safe_PlaySound( + 'SystemQuestion', + winsound.SND_ALIAS | winsound.SND_ASYNC | winsound.SND_LOOP + ) + time.sleep(0.5) + safe_PlaySound('SystemQuestion', winsound.SND_ALIAS | winsound.SND_NOSTOP) + # Issue 8367: PlaySound(None, winsound.SND_PURGE) + # does not raise on systems without a sound card. + winsound.PlaySound(None, winsound.SND_PURGE) + + def test_sound_sentry(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SENTRY) + + def test_sound_sync(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SYNC) + + def test_sound_system(self): + safe_PlaySound("SystemExit", winsound.SND_ALIAS | winsound.SND_SYSTEM) + + +if __name__ == "__main__": + unittest.main() From 705dd7ddddd42038b68ef3c4398e63550165edfa Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Tue, 17 Feb 2026 21:20:04 +0900 Subject: [PATCH 12/18] Implement winsound module Add PlaySound, Beep, MessageBeep functions and all SND_*/MB_* constants. Uses Windows APIs via winmm.dll, kernel32, user32. --- crates/vm/Cargo.toml | 1 + crates/vm/src/stdlib/mod.rs | 4 + crates/vm/src/stdlib/winsound.rs | 206 +++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 crates/vm/src/stdlib/winsound.rs diff --git a/crates/vm/Cargo.toml b/crates/vm/Cargo.toml index b89cda4fdb5..78b1608673e 100644 --- a/crates/vm/Cargo.toml +++ b/crates/vm/Cargo.toml @@ -119,6 +119,7 @@ workspace = true features = [ "Win32_Foundation", "Win32_Globalization", + "Win32_Media_Audio", "Win32_Networking_WinSock", "Win32_Security", "Win32_Security_Authorization", diff --git a/crates/vm/src/stdlib/mod.rs b/crates/vm/src/stdlib/mod.rs index 5bec43f7222..e3bf42a4f77 100644 --- a/crates/vm/src/stdlib/mod.rs +++ b/crates/vm/src/stdlib/mod.rs @@ -67,6 +67,8 @@ pub mod sys; mod winapi; #[cfg(all(feature = "host_env", windows))] mod winreg; +#[cfg(all(feature = "host_env", windows))] +mod winsound; use crate::{Context, builtins::PyModuleDef}; @@ -132,6 +134,8 @@ pub fn builtin_module_defs(ctx: &Context) -> Vec<&'static PyModuleDef> { #[cfg(all(feature = "host_env", windows))] winreg::module_def(ctx), #[cfg(all(feature = "host_env", windows))] + winsound::module_def(ctx), + #[cfg(all(feature = "host_env", windows))] _wmi::module_def(ctx), ] } diff --git a/crates/vm/src/stdlib/winsound.rs b/crates/vm/src/stdlib/winsound.rs new file mode 100644 index 00000000000..3f65abbb890 --- /dev/null +++ b/crates/vm/src/stdlib/winsound.rs @@ -0,0 +1,206 @@ +// spell-checker:ignore pszSound fdwSound +#![allow(non_snake_case)] + +pub(crate) use winsound::module_def; + +mod win32 { + #[link(name = "winmm")] + unsafe extern "system" { + pub fn PlaySoundW(pszSound: *const u16, hmod: isize, fdwSound: u32) -> i32; + } + + unsafe extern "system" { + pub fn Beep(dwFreq: u32, dwDuration: u32) -> i32; + pub fn MessageBeep(uType: u32) -> i32; + } +} + +#[pymodule] +mod winsound { + use crate::builtins::{PyBytes, PyStr}; + use crate::common::windows::ToWideString; + use crate::convert::{IntoPyException, TryFromBorrowedObject}; + use crate::protocol::PyBuffer; + use crate::{AsObject, PyObjectRef, PyResult, VirtualMachine}; + + // PlaySound flags + #[pyattr] + const SND_SYNC: u32 = 0x0000; + #[pyattr] + const SND_ASYNC: u32 = 0x0001; + #[pyattr] + const SND_NODEFAULT: u32 = 0x0002; + #[pyattr] + const SND_MEMORY: u32 = 0x0004; + #[pyattr] + const SND_LOOP: u32 = 0x0008; + #[pyattr] + const SND_NOSTOP: u32 = 0x0010; + #[pyattr] + const SND_PURGE: u32 = 0x0040; + #[pyattr] + const SND_APPLICATION: u32 = 0x0080; + #[pyattr] + const SND_NOWAIT: u32 = 0x00002000; + #[pyattr] + const SND_ALIAS: u32 = 0x00010000; + #[pyattr] + const SND_FILENAME: u32 = 0x00020000; + #[pyattr] + const SND_SENTRY: u32 = 0x00080000; + #[pyattr] + const SND_SYSTEM: u32 = 0x00200000; + + // MessageBeep types + #[pyattr] + const MB_OK: u32 = 0x00000000; + #[pyattr] + const MB_ICONHAND: u32 = 0x00000010; + #[pyattr] + const MB_ICONQUESTION: u32 = 0x00000020; + #[pyattr] + const MB_ICONEXCLAMATION: u32 = 0x00000030; + #[pyattr] + const MB_ICONASTERISK: u32 = 0x00000040; + #[pyattr] + const MB_ICONERROR: u32 = MB_ICONHAND; + #[pyattr] + const MB_ICONSTOP: u32 = MB_ICONHAND; + #[pyattr] + const MB_ICONINFORMATION: u32 = MB_ICONASTERISK; + #[pyattr] + const MB_ICONWARNING: u32 = MB_ICONEXCLAMATION; + + #[derive(FromArgs)] + struct PlaySoundArgs { + #[pyarg(any)] + sound: PyObjectRef, + #[pyarg(any)] + flags: i32, + } + + #[pyfunction] + fn PlaySound(args: PlaySoundArgs, vm: &VirtualMachine) -> PyResult<()> { + let sound = args.sound; + let flags = args.flags as u32; + + if vm.is_none(&sound) { + let ok = unsafe { super::win32::PlaySoundW(core::ptr::null(), 0, flags) }; + if ok == 0 { + return Err(vm.new_runtime_error("Failed to play sound".to_owned())); + } + return Ok(()); + } + + if flags & SND_MEMORY != 0 { + if flags & SND_ASYNC != 0 { + return Err( + vm.new_runtime_error("Cannot play asynchronously from memory".to_owned()) + ); + } + let buffer = PyBuffer::try_from_borrowed_object(vm, &sound)?; + let buf = buffer.as_contiguous().ok_or_else(|| { + vm.new_type_error("a bytes-like object is required, not 'str'".to_owned()) + })?; + let ok = unsafe { super::win32::PlaySoundW(buf.as_ptr() as *const u16, 0, flags) }; + if ok == 0 { + return Err(vm.new_runtime_error("Failed to play sound".to_owned())); + } + return Ok(()); + } + + if sound.downcastable::() { + let type_name = sound.class().name().to_string(); + return Err(vm.new_type_error(format!( + "'sound' must be str, os.PathLike, or None, not {type_name}" + ))); + } + + // os.fspath(sound) + let path = match sound.downcast_ref::() { + Some(s) => s.as_str().to_owned(), + None => { + let fspath = vm.get_method_or_type_error( + sound.clone(), + identifier!(vm, __fspath__), + || { + let type_name = sound.class().name().to_string(); + format!("'sound' must be str, os.PathLike, or None, not {type_name}") + }, + )?; + + if vm.is_none(&fspath) { + return Err(vm.new_type_error(format!( + "'sound' must be str, os.PathLike, or None, not {}", + sound.class().name() + ))); + } + let result = fspath.call((), vm)?; + + if result.downcastable::() { + return Err( + vm.new_type_error("'sound' must resolve to str, not bytes".to_owned()) + ); + } + + let s: &PyStr = result.downcast_ref().ok_or_else(|| { + vm.new_type_error(format!( + "expected {}.__fspath__() to return str or bytes, not {}", + sound.class().name(), + result.class().name() + )) + })?; + + s.as_str().to_owned() + } + }; + + // Check for embedded null characters + if path.contains('\0') { + return Err(vm.new_value_error("embedded null character".to_owned())); + } + + let wide = path.to_wide_with_nul(); + let ok = unsafe { super::win32::PlaySoundW(wide.as_ptr(), 0, flags) }; + if ok == 0 { + return Err(vm.new_runtime_error("Failed to play sound".to_owned())); + } + Ok(()) + } + + #[derive(FromArgs)] + struct BeepArgs { + #[pyarg(any)] + frequency: i32, + #[pyarg(any)] + duration: i32, + } + + #[pyfunction] + fn Beep(args: BeepArgs, vm: &VirtualMachine) -> PyResult<()> { + if !(37..=32767).contains(&args.frequency) { + return Err(vm.new_value_error("frequency must be in 37 thru 32767".to_owned())); + } + + let ok = unsafe { super::win32::Beep(args.frequency as u32, args.duration as u32) }; + if ok == 0 { + return Err(vm.new_runtime_error("Failed to beep".to_owned())); + } + Ok(()) + } + + #[derive(FromArgs)] + struct MessageBeepArgs { + #[pyarg(any, default = 0)] + r#type: u32, + } + + #[pyfunction] + fn MessageBeep(args: MessageBeepArgs, vm: &VirtualMachine) -> PyResult<()> { + let ok = unsafe { super::win32::MessageBeep(args.r#type) }; + if ok == 0 { + return Err(std::io::Error::last_os_error().into_pyexception(vm)); + } + Ok(()) + } +} From 8fa0c66bc478c2439f8341b484a14ee73e02e464 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:35:05 +0900 Subject: [PATCH 13/18] Optimize coroutine exception handling and fix gen_throw traceback (#7166) - Replace ExceptionStack linked list with Vec for O(1) push/pop - Introduce FramePtr (NonNull>) to avoid Arc clone in frame stack - Use FramePtr in ThreadSlot for lock-protected cross-thread frame access - Add resume_gen_frame for lightweight generator/coroutine resume - Replace Coro.exception PyMutex with PyAtomicRef for lock-free swap - Add with_frame_exc to support initial exception state for generators - Use scopeguard for panic-safe cleanup in with_frame_exc/resume_gen_frame - Add traceback entries in gen_throw delegate/close error paths - Treat EndAsyncFor and CleanupThrow as reraise in handle_exception - Update current_frame()/current_globals() to return owned values - Add ThreadSlot with atomic exception field for sys._current_exceptions() - Push/pop thread frames in resume_gen_frame for sys._current_frames() --- crates/stdlib/src/faulthandler.rs | 31 +++-- crates/vm/src/builtins/frame.rs | 45 ++++-- crates/vm/src/coroutine.rs | 41 ++---- crates/vm/src/frame.rs | 43 +++++- crates/vm/src/protocol/callable.rs | 9 +- crates/vm/src/stdlib/builtins.rs | 4 +- crates/vm/src/stdlib/sys.rs | 30 ++-- crates/vm/src/stdlib/thread.rs | 9 +- crates/vm/src/vm/mod.rs | 214 +++++++++++++++++++++-------- crates/vm/src/vm/thread.rs | 105 ++++++++++---- crates/vm/src/warn.rs | 2 +- 11 files changed, 368 insertions(+), 165 deletions(-) diff --git a/crates/stdlib/src/faulthandler.rs b/crates/stdlib/src/faulthandler.rs index 265bcf9ca6d..f618f8f6731 100644 --- a/crates/stdlib/src/faulthandler.rs +++ b/crates/stdlib/src/faulthandler.rs @@ -96,9 +96,8 @@ mod decl { all_threads: AtomicBool::new(true), }; - /// Arc>> - shared frame slot for a thread #[cfg(feature = "threading")] - type ThreadFrameSlot = Arc>>; + type ThreadFrameSlot = Arc; // Watchdog thread state for dump_traceback_later struct WatchdogState { @@ -326,7 +325,7 @@ mod decl { /// Write a frame's info to an fd using signal-safe I/O. #[cfg(any(unix, windows))] - fn dump_frame_from_ref(fd: i32, frame: &crate::vm::PyRef) { + fn dump_frame_from_ref(fd: i32, frame: &crate::vm::Py) { let funcname = frame.code.obj_name.as_str(); let filename = frame.code.source_path().as_str(); let lineno = if frame.lasti() == 0 { @@ -345,20 +344,23 @@ mod decl { } /// Dump traceback for a thread given its frame stack (for cross-thread dumping). + /// # Safety + /// Each `FramePtr` must point to a live frame (caller holds the Mutex). #[cfg(all(any(unix, windows), feature = "threading"))] fn dump_traceback_thread_frames( fd: i32, thread_id: u64, is_current: bool, - frames: &[crate::vm::frame::FrameRef], + frames: &[rustpython_vm::vm::FramePtr], ) { write_thread_id(fd, thread_id, is_current); if frames.is_empty() { puts(fd, " \n"); } else { - for frame in frames.iter().rev() { - dump_frame_from_ref(fd, frame); + for fp in frames.iter().rev() { + // SAFETY: caller holds the Mutex, so the owning thread can't pop. + dump_frame_from_ref(fd, unsafe { fp.as_ref() }); } } } @@ -382,8 +384,9 @@ mod decl { } else { puts(fd, "Stack (most recent call first):\n"); let frames = vm.frames.borrow(); - for frame in frames.iter().rev() { - dump_frame_from_ref(fd, frame); + for fp in frames.iter().rev() { + // SAFETY: the frame is alive while it's in the Vec + dump_frame_from_ref(fd, unsafe { fp.as_ref() }); } } } @@ -410,7 +413,7 @@ mod decl { if tid == current_tid { continue; } - let frames_guard = slot.lock(); + let frames_guard = slot.frames.lock(); dump_traceback_thread_frames(fd, tid, false, &frames_guard); puts(fd, "\n"); } @@ -421,8 +424,8 @@ mod decl { if frames.is_empty() { puts(fd, " \n"); } else { - for frame in frames.iter().rev() { - dump_frame_from_ref(fd, frame); + for fp in frames.iter().rev() { + dump_frame_from_ref(fd, unsafe { fp.as_ref() }); } } } @@ -431,8 +434,8 @@ mod decl { { write_thread_id(fd, current_thread_id(), true); let frames = vm.frames.borrow(); - for frame in frames.iter().rev() { - dump_frame_from_ref(fd, frame); + for fp in frames.iter().rev() { + dump_frame_from_ref(fd, unsafe { fp.as_ref() }); } } } @@ -870,7 +873,7 @@ mod decl { #[cfg(feature = "threading")] { for (tid, slot) in &thread_frame_slots { - let frames = slot.lock(); + let frames = slot.frames.lock(); dump_traceback_thread_frames(fd, *tid, false, &frames); } } diff --git a/crates/vm/src/builtins/frame.rs b/crates/vm/src/builtins/frame.rs index 5d7510d8ff8..ed2e1e672fd 100644 --- a/crates/vm/src/builtins/frame.rs +++ b/crates/vm/src/builtins/frame.rs @@ -4,7 +4,7 @@ use super::{PyCode, PyDictRef, PyIntRef, PyStrRef}; use crate::{ - AsObject, Context, Py, PyObjectRef, PyRef, PyResult, VirtualMachine, + Context, Py, PyObjectRef, PyRef, PyResult, VirtualMachine, class::PyClassImpl, frame::{Frame, FrameOwner, FrameRef}, function::PySetterValue, @@ -195,16 +195,43 @@ impl Py { #[pygetset] pub fn f_back(&self, vm: &VirtualMachine) -> Option> { - // TODO: actually store f_back inside Frame struct + let previous = self.previous_frame(); + if previous.is_null() { + return None; + } - // get the frame in the frame stack that appears before this one. - // won't work if this frame isn't in the frame stack, hence the todo above - vm.frames + if let Some(frame) = vm + .frames .borrow() .iter() - .rev() - .skip_while(|p| !p.is(self.as_object())) - .nth(1) - .cloned() + .find(|fp| { + // SAFETY: the caller keeps the FrameRef alive while it's in the Vec + let py: &crate::Py = unsafe { fp.as_ref() }; + let ptr: *const Frame = &**py; + core::ptr::eq(ptr, previous) + }) + .map(|fp| unsafe { fp.as_ref() }.to_owned()) + { + return Some(frame); + } + + #[cfg(feature = "threading")] + { + let registry = vm.state.thread_frames.lock(); + for slot in registry.values() { + let frames = slot.frames.lock(); + // SAFETY: the owning thread can't pop while we hold the Mutex, + // so FramePtr is valid for the duration of the lock. + if let Some(frame) = frames.iter().find_map(|fp| { + let f = unsafe { fp.as_ref() }; + let ptr: *const Frame = &**f; + core::ptr::eq(ptr, previous).then(|| f.to_owned()) + }) { + return Some(frame); + } + } + } + + None } } diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index a066c9944fe..ac44e33f799 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -1,11 +1,11 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyStrRef}, + builtins::PyStrRef, common::lock::PyMutex, exceptions::types::PyBaseException, - frame::{ExecutionResult, FrameOwner, FrameRef}, + frame::{ExecutionResult, Frame, FrameOwner, FrameRef}, function::OptionalArg, - object::{Traverse, TraverseFn}, + object::{PyAtomicRef, Traverse, TraverseFn}, protocol::PyIterReturn, }; use crossbeam_utils::atomic::AtomicCell; @@ -36,7 +36,7 @@ pub struct Coro { // _weakreflist name: PyMutex, qualname: PyMutex, - exception: PyMutex>, // exc_state + exception: PyAtomicRef>, // exc_state } unsafe impl Traverse for Coro { @@ -44,7 +44,9 @@ unsafe impl Traverse for Coro { self.frame.traverse(tracer_fn); self.name.traverse(tracer_fn); self.qualname.traverse(tracer_fn); - self.exception.traverse(tracer_fn); + if let Some(exc) = self.exception.deref() { + exc.traverse(tracer_fn); + } } } @@ -65,7 +67,7 @@ impl Coro { frame, closed: AtomicCell::new(false), running: AtomicCell::new(false), - exception: PyMutex::default(), + exception: PyAtomicRef::from(None), name: PyMutex::new(name), qualname: PyMutex::new(qualname), } @@ -92,33 +94,20 @@ impl Coro { func: F, ) -> PyResult where - F: FnOnce(FrameRef) -> PyResult, + F: FnOnce(&Py) -> PyResult, { if self.running.compare_exchange(false, true).is_err() { return Err(vm.new_value_error(format!("{} already executing", gen_name(jen, vm)))); } - // swap exception state - // Get generator's saved exception state from last yield - let gen_exc = self.exception.lock().take(); - - // Use a slot to capture generator's exception state before with_frame pops - let exception_slot = &self.exception; + // SAFETY: running.compare_exchange guarantees exclusive access + let gen_exc = unsafe { self.exception.swap(None) }; + let exception_ptr = &self.exception as *const PyAtomicRef>; - // Run the generator frame - // with_frame does push_exception(None) which creates a new exception context - // The caller's exception remains in the chain via prev, so topmost_exception() - // will find it if generator's exception is None - let result = vm.with_frame(self.frame.clone(), |f| { - // with_frame pushed None, creating: { exc: None, prev: caller's exc_info } - // Pop None and push generator's exception instead - // This maintains the chain: { exc: gen_exc, prev: caller's exc_info } - vm.pop_exception(); - vm.push_exception(gen_exc); + let result = vm.resume_gen_frame(&self.frame, gen_exc, |f| { let result = func(f); - // Save generator's exception state BEFORE with_frame pops - // This is the generator's current exception context - *exception_slot.lock() = vm.current_exception(); + // SAFETY: exclusive access guaranteed by running flag + let _old = unsafe { (*exception_ptr).swap(vm.current_exception()) }; result }); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index f24b25c610a..b498dc3e726 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -463,7 +463,7 @@ impl ExecutingFrame<'_> { // Execute until return or exception: let instructions = &self.code.instructions; let mut arg_state = bytecode::OpArgState::default(); - let mut prev_line: usize = 0; + let mut prev_line: u32 = 0; loop { let idx = self.lasti() as usize; // Fire 'line' trace event when line number changes. @@ -472,9 +472,9 @@ impl ExecutingFrame<'_> { if vm.use_tracing.get() && !vm.is_none(&self.object.trace.lock()) && let Some((loc, _)) = self.code.locations.get(idx) - && loc.line.get() != prev_line + && loc.line.get() as u32 != prev_line { - prev_line = loc.line.get(); + prev_line = loc.line.get() as u32; vm.trace_event(crate::protocol::TraceEvent::Line, None)?; } self.update_lasti(|i| *i += 1); @@ -543,13 +543,16 @@ impl ExecutingFrame<'_> { // Check if this is a RERAISE instruction // Both AnyInstruction::Raise { kind: Reraise/ReraiseFromStack } and // AnyInstruction::Reraise are reraise operations that should not add - // new traceback entries + // new traceback entries. + // EndAsyncFor and CleanupThrow also re-raise non-matching exceptions. let is_reraise = match op { Instruction::RaiseVarargs { kind } => matches!( kind.get(arg), bytecode::RaiseKind::BareRaise | bytecode::RaiseKind::ReraiseFromStack ), - Instruction::Reraise { .. } => true, + Instruction::Reraise { .. } + | Instruction::EndAsyncFor + | Instruction::CleanupThrow => true, _ => false, }; @@ -653,6 +656,19 @@ impl ExecutingFrame<'_> { Ok(()) }; if let Err(err) = close_result { + let idx = self.lasti().saturating_sub(1) as usize; + if idx < self.code.locations.len() { + let (loc, _end_loc) = self.code.locations[idx]; + let next = err.__traceback__(); + let new_traceback = PyTraceback::new( + next, + self.object.to_owned(), + idx as u32 * 2, + loc.line, + ); + err.set_traceback_typed(Some(new_traceback.into_ref(&vm.ctx))); + } + self.push_value(vm.ctx.none()); vm.contextualize_exception(&err); return match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { @@ -678,6 +694,23 @@ impl ExecutingFrame<'_> { Either::B(meth) => meth.call((exc_type, exc_val, exc_tb), vm), }; return ret.map(ExecutionResult::Yield).or_else(|err| { + // Add traceback entry for the yield-from/await point. + // gen_send_ex2 resumes the frame with a pending exception, + // which goes through error: → PyTraceBack_Here. We add the + // entry here before calling unwind_blocks. + let idx = self.lasti().saturating_sub(1) as usize; + if idx < self.code.locations.len() { + let (loc, _end_loc) = self.code.locations[idx]; + let next = err.__traceback__(); + let new_traceback = PyTraceback::new( + next, + self.object.to_owned(), + idx as u32 * 2, + loc.line, + ); + err.set_traceback_typed(Some(new_traceback.into_ref(&vm.ctx))); + } + self.push_value(vm.ctx.none()); vm.contextualize_exception(&err); match self.unwind_blocks(vm, UnwindReason::Raising { exception: err }) { diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 9308ec8ffe2..9a621dee4f8 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -2,7 +2,7 @@ use crate::{ builtins::{PyBoundMethod, PyFunction}, function::{FuncArgs, IntoFuncArgs}, types::GenericMethod, - {AsObject, PyObject, PyObjectRef, PyResult, VirtualMachine}, + {PyObject, PyObjectRef, PyResult, VirtualMachine}, }; impl PyObject { @@ -111,12 +111,11 @@ impl VirtualMachine { return Ok(()); } - let frame_ref = self.current_frame(); - if frame_ref.is_none() { + let Some(frame_ref) = self.current_frame() else { return Ok(()); - } + }; - let frame = frame_ref.unwrap().as_object().to_owned(); + let frame: PyObjectRef = frame_ref.into(); let event = self.ctx.new_str(event.to_string()).into(); let args = vec![frame, event, arg.unwrap_or_else(|| self.ctx.none())]; diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 7b24d72d9b5..1b54a26e732 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -384,7 +384,7 @@ mod builtins { ) } None => ( - vm.current_globals().clone(), + vm.current_globals(), if let Some(locals) = self.locals { locals } else { @@ -503,7 +503,7 @@ mod builtins { #[pyfunction] fn globals(vm: &VirtualMachine) -> PyDictRef { - vm.current_globals().clone() + vm.current_globals() } #[pyfunction] diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 4c672af110b..0a6f26d642c 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -41,7 +41,7 @@ mod sys { hash::{PyHash, PyUHash}, }, convert::ToPyObject, - frame::FrameRef, + frame::{Frame, FrameRef}, function::{FuncArgs, KwArgs, OptionalArg, PosArgs}, stdlib::{builtins, warnings::warn}, types::PyStructSequence, @@ -971,12 +971,14 @@ mod sys { #[pyfunction] fn _getframe(offset: OptionalArg, vm: &VirtualMachine) -> PyResult { let offset = offset.into_option().unwrap_or(0); - if offset > vm.frames.borrow().len() - 1 { + let frames = vm.frames.borrow(); + if offset >= frames.len() { return Err(vm.new_value_error("call stack is not deep enough")); } - let idx = vm.frames.borrow().len() - offset - 1; - let frame = &vm.frames.borrow()[idx]; - Ok(frame.clone()) + let idx = frames.len() - offset - 1; + // SAFETY: the FrameRef is alive on the call stack while it's in the Vec + let py: &crate::Py = unsafe { frames[idx].as_ref() }; + Ok(py.to_owned()) } #[pyfunction] @@ -984,15 +986,19 @@ mod sys { let depth = depth.into_option().unwrap_or(0); // Get the frame at the specified depth - if depth > vm.frames.borrow().len() - 1 { - return Ok(vm.ctx.none()); - } - - let idx = vm.frames.borrow().len() - depth - 1; - let frame = &vm.frames.borrow()[idx]; + let func_obj = { + let frames = vm.frames.borrow(); + if depth >= frames.len() { + return Ok(vm.ctx.none()); + } + let idx = frames.len() - depth - 1; + // SAFETY: the FrameRef is alive on the call stack while it's in the Vec + let frame: &crate::Py = unsafe { frames[idx].as_ref() }; + frame.func_obj.clone() + }; // If the frame has a function object, return its __module__ attribute - if let Some(func_obj) = &frame.func_obj { + if let Some(func_obj) = func_obj { match func_obj.get_attr(identifier!(vm, __module__), vm) { Ok(module) => Ok(module), Err(_) => { diff --git a/crates/vm/src/stdlib/thread.rs b/crates/vm/src/stdlib/thread.rs index 12a741f62f0..bf495ecc382 100644 --- a/crates/vm/src/stdlib/thread.rs +++ b/crates/vm/src/stdlib/thread.rs @@ -891,7 +891,14 @@ pub(crate) mod _thread { let registry = vm.state.thread_frames.lock(); registry .iter() - .filter_map(|(id, slot)| slot.lock().last().cloned().map(|f| (*id, f))) + .filter_map(|(id, slot)| { + let frames = slot.frames.lock(); + // SAFETY: the owning thread can't pop while we hold the Mutex, + // so the FramePtr is valid for the duration of the lock. + frames + .last() + .map(|fp| (*id, unsafe { fp.as_ref() }.to_owned())) + }) .collect() } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 10409d943b3..07395b80460 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -41,7 +41,8 @@ use crate::{ }; use alloc::{borrow::Cow, collections::BTreeMap}; use core::{ - cell::{Cell, OnceCell, Ref, RefCell}, + cell::{Cell, OnceCell, RefCell}, + ptr::NonNull, sync::atomic::{AtomicBool, Ordering}, }; use crossbeam_utils::atomic::AtomicCell; @@ -72,7 +73,7 @@ pub struct VirtualMachine { pub builtins: PyRef, pub sys_module: PyRef, pub ctx: PyRc, - pub frames: RefCell>, + pub frames: RefCell>, pub wasm_id: Option, exceptions: RefCell, pub import_func: PyObjectRef, @@ -99,10 +100,26 @@ pub struct VirtualMachine { pub asyncio_running_task: RefCell>, } +/// Non-owning frame pointer for the frames stack. +/// The pointed-to frame is kept alive by the caller of with_frame_exc/resume_gen_frame. +#[derive(Copy, Clone)] +pub struct FramePtr(NonNull>); + +impl FramePtr { + /// # Safety + /// The pointed-to frame must still be alive. + pub unsafe fn as_ref(&self) -> &Py { + unsafe { self.0.as_ref() } + } +} + +// SAFETY: FramePtr is only stored in the VM's frames Vec while the corresponding +// FrameRef is alive on the call stack. The Vec is always empty when the VM moves between threads. +unsafe impl Send for FramePtr {} + #[derive(Debug, Default)] struct ExceptionStack { - exc: Option, - prev: Option>, + stack: Vec>, } pub struct PyGlobalState { @@ -129,7 +146,7 @@ pub struct PyGlobalState { /// Main thread identifier (pthread_self on Unix) #[cfg(feature = "threading")] pub main_thread_ident: AtomicCell, - /// Registry of all threads' current frames for sys._current_frames() + /// Registry of all threads' slots for sys._current_frames() and sys._current_exceptions() #[cfg(feature = "threading")] pub thread_frames: parking_lot::Mutex>, /// Registry of all ThreadHandles for fork cleanup @@ -997,31 +1014,55 @@ impl VirtualMachine { &self, frame: FrameRef, f: F, + ) -> PyResult { + self.with_frame_exc(frame, None, f) + } + + /// Like `with_frame` but allows specifying the initial exception state. + pub fn with_frame_exc PyResult>( + &self, + frame: FrameRef, + exc: Option, + f: F, ) -> PyResult { self.with_recursion("", || { - self.frames.borrow_mut().push(frame.clone()); + // SAFETY: `frame` (FrameRef) stays alive for the entire closure scope, + // keeping the FramePtr valid. We pass a clone to `f` so that `f` + // consuming its FrameRef doesn't invalidate our pointer. + let fp = FramePtr(NonNull::from(&*frame)); + self.frames.borrow_mut().push(fp); // Update the shared frame stack for sys._current_frames() and faulthandler #[cfg(feature = "threading")] - crate::vm::thread::push_thread_frame(frame.clone()); + crate::vm::thread::push_thread_frame(fp); // Link frame into the signal-safe frame chain (previous pointer) - let frame_ptr: *const Frame = &**frame; - let old_frame = crate::vm::thread::set_current_frame(frame_ptr); + let old_frame = crate::vm::thread::set_current_frame((&**frame) as *const Frame); frame.previous.store( old_frame as *mut Frame, core::sync::atomic::Ordering::Relaxed, ); - // Push a new exception context for frame isolation - // Each frame starts with no active exception (None) - // This prevents exceptions from leaking between function calls - self.push_exception(None); + // Push exception context for frame isolation. + // For normal calls: None (clean slate). + // For generators: the saved exception from last yield. + self.push_exception(exc); let old_owner = frame.owner.swap( crate::frame::FrameOwner::Thread as i8, core::sync::atomic::Ordering::AcqRel, ); + + // Ensure cleanup on panic: restore owner, pop exception, frame chain, and frames Vec. + scopeguard::defer! { + frame.owner.store(old_owner, core::sync::atomic::Ordering::Release); + self.pop_exception(); + crate::vm::thread::set_current_frame(old_frame); + self.frames.borrow_mut().pop(); + #[cfg(feature = "threading")] + crate::vm::thread::pop_thread_frame(); + } + use crate::protocol::TraceEvent; // Fire 'call' trace event after pushing frame // (current_frame() now returns the callee's frame) - let result = match self.trace_event(TraceEvent::Call, None) { + match self.trace_event(TraceEvent::Call, None) { Ok(()) => { // Set per-frame trace function so line events fire for this frame. // Frames entered before sys.settrace() keep trace=None and skip line events. @@ -1031,7 +1072,7 @@ impl VirtualMachine { *frame.trace.lock() = trace_func; } } - let result = f(frame); + let result = f(frame.clone()); // Fire 'return' trace event on success if result.is_ok() { let _ = self.trace_event(TraceEvent::Return, None); @@ -1039,23 +1080,67 @@ impl VirtualMachine { result } Err(e) => Err(e), - }; - // SAFETY: frame_ptr is valid because self.frames holds a clone - // of the frame, keeping the underlying allocation alive. - unsafe { &*frame_ptr } - .owner - .store(old_owner, core::sync::atomic::Ordering::Release); - // Pop the exception context - restores caller's exception state - self.pop_exception(); - // Restore previous frame as current (unlink from chain) + } + }) + } + + /// Lightweight frame execution for generator/coroutine resume. + /// Pushes to the thread frame stack and fires trace/profile events, + /// but skips the thread exception update for performance. + pub fn resume_gen_frame) -> PyResult>( + &self, + frame: &FrameRef, + exc: Option, + f: F, + ) -> PyResult { + self.check_recursive_call("")?; + if self.check_c_stack_overflow() { + return Err(self.new_recursion_error(String::new())); + } + self.recursion_depth.update(|d| d + 1); + + // SAFETY: frame (&FrameRef) stays alive for the duration, so NonNull is valid until pop. + let fp = FramePtr(NonNull::from(&**frame)); + self.frames.borrow_mut().push(fp); + #[cfg(feature = "threading")] + crate::vm::thread::push_thread_frame(fp); + let old_frame = crate::vm::thread::set_current_frame((&***frame) as *const Frame); + frame.previous.store( + old_frame as *mut Frame, + core::sync::atomic::Ordering::Relaxed, + ); + // Inline exception push without thread exception update + self.exceptions.borrow_mut().stack.push(exc); + let old_owner = frame.owner.swap( + crate::frame::FrameOwner::Thread as i8, + core::sync::atomic::Ordering::AcqRel, + ); + + // Ensure cleanup on panic: restore owner, pop exception, frame chain, frames Vec, + // and recursion depth. + scopeguard::defer! { + frame.owner.store(old_owner, core::sync::atomic::Ordering::Release); + self.exceptions.borrow_mut().stack + .pop() + .expect("pop_exception() without nested exc stack"); crate::vm::thread::set_current_frame(old_frame); - // defer dec frame - let _popped = self.frames.borrow_mut().pop(); - // Pop from shared frame stack + self.frames.borrow_mut().pop(); #[cfg(feature = "threading")] crate::vm::thread::pop_thread_frame(); - result - }) + self.recursion_depth.update(|d| d - 1); + } + + use crate::protocol::TraceEvent; + match self.trace_event(TraceEvent::Call, None) { + Ok(()) => { + let result = f(frame); + if result.is_ok() { + let _ = self.trace_event(TraceEvent::Return, None); + } + result + } + Err(e) => Err(e), + } } /// Returns a basic CompileOpts instance with options accurate to the vm. Used @@ -1077,15 +1162,11 @@ impl VirtualMachine { } } - pub fn current_frame(&self) -> Option> { - let frames = self.frames.borrow(); - if frames.is_empty() { - None - } else { - Some(Ref::map(self.frames.borrow(), |frames| { - frames.last().unwrap() - })) - } + pub fn current_frame(&self) -> Option { + self.frames.borrow().last().map(|fp| { + // SAFETY: the caller keeps the FrameRef alive while it's in the Vec + unsafe { fp.as_ref() }.to_owned() + }) } pub fn current_locals(&self) -> PyResult { @@ -1094,11 +1175,11 @@ impl VirtualMachine { .locals(self) } - pub fn current_globals(&self) -> Ref<'_, PyDictRef> { - let frame = self - .current_frame() - .expect("called current_globals but no frames on the stack"); - Ref::map(frame, |f| &f.globals) + pub fn current_globals(&self) -> PyDictRef { + self.current_frame() + .expect("called current_globals but no frames on the stack") + .globals + .clone() } pub fn try_class(&self, module: &'static str, class: &'static str) -> PyResult { @@ -1351,27 +1432,44 @@ impl VirtualMachine { } pub(crate) fn push_exception(&self, exc: Option) { - let mut excs = self.exceptions.borrow_mut(); - let prev = core::mem::take(&mut *excs); - excs.prev = Some(Box::new(prev)); - excs.exc = exc + self.exceptions.borrow_mut().stack.push(exc); + #[cfg(feature = "threading")] + thread::update_thread_exception(self.topmost_exception()); } pub(crate) fn pop_exception(&self) -> Option { - let mut excs = self.exceptions.borrow_mut(); - let cur = core::mem::take(&mut *excs); - *excs = *cur.prev.expect("pop_exception() without nested exc stack"); - cur.exc + let exc = self + .exceptions + .borrow_mut() + .stack + .pop() + .expect("pop_exception() without nested exc stack"); + #[cfg(feature = "threading")] + thread::update_thread_exception(self.topmost_exception()); + exc } pub(crate) fn current_exception(&self) -> Option { - self.exceptions.borrow().exc.clone() + self.exceptions.borrow().stack.last().cloned().flatten() } pub(crate) fn set_exception(&self, exc: Option) { // don't be holding the RefCell guard while __del__ is called - let prev = core::mem::replace(&mut self.exceptions.borrow_mut().exc, exc); - drop(prev); + let mut excs = self.exceptions.borrow_mut(); + debug_assert!( + !excs.stack.is_empty(), + "set_exception called with empty exception stack" + ); + if let Some(top) = excs.stack.last_mut() { + let prev = core::mem::replace(top, exc); + drop(excs); + drop(prev); + } else { + excs.stack.push(exc); + drop(excs); + } + #[cfg(feature = "threading")] + thread::update_thread_exception(self.topmost_exception()); } pub(crate) fn contextualize_exception(&self, exception: &Py) { @@ -1404,13 +1502,7 @@ impl VirtualMachine { pub(crate) fn topmost_exception(&self) -> Option { let excs = self.exceptions.borrow(); - let mut cur = &*excs; - loop { - if let Some(exc) = &cur.exc { - return Some(exc.clone()); - } - cur = cur.prev.as_deref()?; - } + excs.stack.iter().rev().find_map(|e| e.clone()) } pub fn handle_exit_exception(&self, exc: PyBaseExceptionRef) -> u32 { diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index af69fa8d8e5..575910f7900 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -1,6 +1,8 @@ -use crate::frame::Frame; #[cfg(feature = "threading")] -use crate::frame::FrameRef; +use super::FramePtr; +#[cfg(feature = "threading")] +use crate::builtins::PyBaseExceptionRef; +use crate::frame::Frame; use crate::{AsObject, PyObject, VirtualMachine}; #[cfg(feature = "threading")] use alloc::sync::Arc; @@ -12,20 +14,27 @@ use core::{ use itertools::Itertools; use std::thread_local; -/// Type for current frame slot - shared between threads for sys._current_frames() -/// Stores the full frame stack so faulthandler can dump complete tracebacks -/// for all threads. +/// Per-thread shared state for sys._current_frames() and sys._current_exceptions(). +/// The exception field uses atomic operations for lock-free cross-thread reads. +#[cfg(feature = "threading")] +pub struct ThreadSlot { + /// Raw frame pointers, valid while the owning thread's call stack is active. + /// Readers must hold the Mutex and convert to FrameRef inside the lock. + pub frames: parking_lot::Mutex>, + pub exception: crate::PyAtomicRef>, +} + #[cfg(feature = "threading")] -pub type CurrentFrameSlot = Arc>>; +pub type CurrentFrameSlot = Arc; thread_local! { pub(super) static VM_STACK: RefCell>> = Vec::with_capacity(1).into(); pub(crate) static COROUTINE_ORIGIN_TRACKING_DEPTH: Cell = const { Cell::new(0) }; - /// Current thread's frame slot for sys._current_frames() + /// Current thread's slot for sys._current_frames() and sys._current_exceptions() #[cfg(feature = "threading")] - static CURRENT_FRAME_SLOT: RefCell> = const { RefCell::new(None) }; + static CURRENT_THREAD_SLOT: RefCell> = const { RefCell::new(None) }; /// Current top frame for signal-safe traceback walking. /// Mirrors `PyThreadState.current_frame`. Read by faulthandler's signal @@ -49,23 +58,26 @@ pub fn enter_vm(vm: &VirtualMachine, f: impl FnOnce() -> R) -> R { VM_STACK.with(|vms| { vms.borrow_mut().push(vm.into()); - // Initialize frame slot for this thread if not already done + // Initialize thread slot for this thread if not already done #[cfg(feature = "threading")] - init_frame_slot_if_needed(vm); + init_thread_slot_if_needed(vm); scopeguard::defer! { vms.borrow_mut().pop(); } VM_CURRENT.set(vm, f) }) } -/// Initialize frame slot for current thread if not already initialized. +/// Initialize thread slot for current thread if not already initialized. /// Called automatically by enter_vm(). #[cfg(feature = "threading")] -fn init_frame_slot_if_needed(vm: &VirtualMachine) { - CURRENT_FRAME_SLOT.with(|slot| { +fn init_thread_slot_if_needed(vm: &VirtualMachine) { + CURRENT_THREAD_SLOT.with(|slot| { if slot.borrow().is_none() { let thread_id = crate::stdlib::thread::get_ident(); - let new_slot = Arc::new(parking_lot::Mutex::new(Vec::new())); + let new_slot = Arc::new(ThreadSlot { + frames: parking_lot::Mutex::new(Vec::new()), + exception: crate::PyAtomicRef::from(None::), + }); vm.state .thread_frames .lock() @@ -75,13 +87,18 @@ fn init_frame_slot_if_needed(vm: &VirtualMachine) { }); } -/// Push a frame onto the current thread's shared frame stack. -/// Called when a new frame is entered. +/// Push a frame pointer onto the current thread's shared frame stack. +/// The pointed-to frame must remain alive until the matching pop. #[cfg(feature = "threading")] -pub fn push_thread_frame(frame: FrameRef) { - CURRENT_FRAME_SLOT.with(|slot| { +pub fn push_thread_frame(fp: FramePtr) { + CURRENT_THREAD_SLOT.with(|slot| { if let Some(s) = slot.borrow().as_ref() { - s.lock().push(frame); + s.frames.lock().push(fp); + } else { + debug_assert!( + false, + "push_thread_frame called without initialized thread slot" + ); } }); } @@ -90,9 +107,14 @@ pub fn push_thread_frame(frame: FrameRef) { /// Called when a frame is exited. #[cfg(feature = "threading")] pub fn pop_thread_frame() { - CURRENT_FRAME_SLOT.with(|slot| { + CURRENT_THREAD_SLOT.with(|slot| { if let Some(s) = slot.borrow().as_ref() { - s.lock().pop(); + s.frames.lock().pop(); + } else { + debug_assert!( + false, + "pop_thread_frame called without initialized thread slot" + ); } }); } @@ -109,25 +131,51 @@ pub fn get_current_frame() -> *const Frame { CURRENT_FRAME.with(|c| c.load(Ordering::Relaxed) as *const Frame) } -/// Cleanup frame tracking for the current thread. Called at thread exit. +/// Update the current thread's exception slot atomically (no locks). +/// Called from push_exception/pop_exception/set_exception. +#[cfg(feature = "threading")] +pub fn update_thread_exception(exc: Option) { + CURRENT_THREAD_SLOT.with(|slot| { + if let Some(s) = slot.borrow().as_ref() { + // SAFETY: Called only from the owning thread. The old ref is dropped + // here on the owning thread, which is safe. + let _old = unsafe { s.exception.swap(exc) }; + } + }); +} + +/// Collect all threads' current exceptions for sys._current_exceptions(). +/// Acquires the global registry lock briefly, then reads each slot's exception atomically. +#[cfg(feature = "threading")] +pub fn get_all_current_exceptions(vm: &VirtualMachine) -> Vec<(u64, Option)> { + let registry = vm.state.thread_frames.lock(); + registry + .iter() + .map(|(id, slot)| (*id, slot.exception.to_owned())) + .collect() +} + +/// Cleanup thread slot for the current thread. Called at thread exit. #[cfg(feature = "threading")] pub fn cleanup_current_thread_frames(vm: &VirtualMachine) { let thread_id = crate::stdlib::thread::get_ident(); vm.state.thread_frames.lock().remove(&thread_id); - CURRENT_FRAME_SLOT.with(|s| { + CURRENT_THREAD_SLOT.with(|s| { *s.borrow_mut() = None; }); } -/// Reinitialize frame slot after fork. Called in child process. +/// Reinitialize thread slot after fork. Called in child process. /// Creates a fresh slot and registers it for the current thread, /// preserving the current thread's frames from `vm.frames`. #[cfg(feature = "threading")] pub fn reinit_frame_slot_after_fork(vm: &VirtualMachine) { let current_ident = crate::stdlib::thread::get_ident(); - // Preserve the current thread's frames across fork - let current_frames: Vec = vm.frames.borrow().clone(); - let new_slot = Arc::new(parking_lot::Mutex::new(current_frames)); + let current_frames: Vec = vm.frames.borrow().clone(); + let new_slot = Arc::new(ThreadSlot { + frames: parking_lot::Mutex::new(current_frames), + exception: crate::PyAtomicRef::from(vm.topmost_exception()), + }); // After fork, only the current thread exists. If the lock was held by // another thread during fork, force unlock it. @@ -144,8 +192,7 @@ pub fn reinit_frame_slot_after_fork(vm: &VirtualMachine) { registry.insert(current_ident, new_slot.clone()); drop(registry); - // Update thread-local to point to the new slot - CURRENT_FRAME_SLOT.with(|s| { + CURRENT_THREAD_SLOT.with(|s| { *s.borrow_mut() = Some(new_slot); }); } diff --git a/crates/vm/src/warn.rs b/crates/vm/src/warn.rs index b4406ff5246..684630e6af0 100644 --- a/crates/vm/src/warn.rs +++ b/crates/vm/src/warn.rs @@ -551,7 +551,7 @@ fn setup_context( skip_file_prefixes: Option<&PyTupleRef>, vm: &VirtualMachine, ) -> PyResult<(PyStrRef, usize, Option, PyObjectRef)> { - let mut f = vm.current_frame().as_deref().cloned(); + let mut f = vm.current_frame(); // Stack level comparisons to Python code is off by one as there is no // warnings-related stack level to avoid. From d4d010ec21e8e92a9ca2559844f65241ad83670b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:41:53 +0900 Subject: [PATCH 14/18] Fix unpack_sequence to match CPython behavior (#7175) Add fast path for exact tuple/list (direct length check without iterator). General path now iterates only size+1 elements, fixing hang on infinite sequences like __getitem__ without raising IndexError. Error messages now include "got N" for tuple, list, and dict. Remove 7 expectedFailure/skip markers from test_unpack. --- Lib/test/test_unpack.py | 14 +++--- crates/vm/src/frame.rs | 95 ++++++++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_unpack.py b/Lib/test/test_unpack.py index db4faec2e3c..305da05b7ce 100644 --- a/Lib/test/test_unpack.py +++ b/Lib/test/test_unpack.py @@ -70,14 +70,14 @@ Unpacking tuple of wrong size - >>> a, b = t # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> a, b = t Traceback (most recent call last): ... ValueError: too many values to unpack (expected 2, got 3) Unpacking tuple of wrong size - >>> a, b = l # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> a, b = l Traceback (most recent call last): ... ValueError: too many values to unpack (expected 2, got 3) @@ -144,7 +144,7 @@ Unpacking to an empty iterable should raise ValueError - >>> () = [42] # TODO: RUSTPYTHON # doctest: +EXPECTED_FAILURE + >>> () = [42] Traceback (most recent call last): ... ValueError: too many values to unpack (expected 0, got 1) @@ -157,13 +157,13 @@ Traceback (most recent call last): ... ValueError: too many values to unpack (expected 3) - >>> next(it) # TODO: RUSTPYTHON; Raise `StopIteration` # doctest: +SKIP + >>> next(it) 4 Unpacking unbalanced dict >>> d = {4: 'four', 5: 'five', 6: 'six', 7: 'seven'} - >>> a, b, c = d # TODO: RUSTPYTHON; # doctest: +EXPECTED_FAILURE + >>> a, b, c = d Traceback (most recent call last): ... ValueError: too many values to unpack (expected 3, got 4) @@ -176,7 +176,7 @@ ... def __getitem__(self, i): ... return i*2 ... - >>> x, y, z = LengthTooLong() # TODO: RUSTPYTHON; Hangs # doctest: +SKIP + >>> x, y, z = LengthTooLong() Traceback (most recent call last): ... ValueError: too many values to unpack (expected 3) @@ -189,7 +189,7 @@ ... def __getitem__(self, i): ... return i*2 ... - >>> x, y, z = BadLength() # TODO: RUSTPYTHON; Hangs # doctest: +SKIP + >>> x, y, z = BadLength() Traceback (most recent call last): ... ValueError: too many values to unpack (expected 3) diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index b498dc3e726..62df1b298e6 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -3043,13 +3043,54 @@ impl ExecutingFrame<'_> { Ok(None) } + /// _PyEval_UnpackIterableStackRef fn unpack_sequence(&mut self, size: u32, vm: &VirtualMachine) -> FrameResult { let value = self.pop_value(); + let size = size as usize; + + // Fast path for exact tuple/list types (not subclasses) — check + // length directly without creating an iterator, matching + // UNPACK_SEQUENCE_TUPLE / UNPACK_SEQUENCE_LIST specializations. + let cls = value.class(); + let fast_elements: Option> = if cls.is(vm.ctx.types.tuple_type) { + Some(value.downcast_ref::().unwrap().as_slice().to_vec()) + } else if cls.is(vm.ctx.types.list_type) { + Some( + value + .downcast_ref::() + .unwrap() + .borrow_vec() + .to_vec(), + ) + } else { + None + }; + if let Some(elements) = fast_elements { + return match elements.len().cmp(&size) { + core::cmp::Ordering::Equal => { + self.state + .stack + .extend(elements.into_iter().rev().map(Some)); + Ok(None) + } + core::cmp::Ordering::Greater => Err(vm.new_value_error(format!( + "too many values to unpack (expected {size}, got {})", + elements.len() + ))), + core::cmp::Ordering::Less => Err(vm.new_value_error(format!( + "not enough values to unpack (expected {size}, got {})", + elements.len() + ))), + }; + } + + // General path — iterate up to `size + 1` elements to avoid + // consuming the entire iterator (fixes hang on infinite sequences). let not_iterable = value.class().slots.iter.load().is_none() && value .get_class_attr(vm.ctx.intern_str("__getitem__")) .is_none(); - let elements: Vec<_> = value.try_to_value(vm).map_err(|e| { + let iter = PyIter::try_from_object(vm, value.clone()).map_err(|e| { if not_iterable && e.class().is(vm.ctx.exceptions.type_error) { vm.new_type_error(format!( "cannot unpack non-iterable {} object", @@ -3059,24 +3100,48 @@ impl ExecutingFrame<'_> { e } })?; - let msg = match elements.len().cmp(&(size as usize)) { - core::cmp::Ordering::Equal => { - // Wrap each element in Some() for Option stack + + let mut elements = Vec::with_capacity(size); + for _ in 0..size { + match iter.next(vm)? { + PyIterReturn::Return(item) => elements.push(item), + PyIterReturn::StopIteration(_) => { + return Err(vm.new_value_error(format!( + "not enough values to unpack (expected {size}, got {})", + elements.len() + ))); + } + } + } + + // Check that the iterator is exhausted. + match iter.next(vm)? { + PyIterReturn::Return(_) => { + // For exact dict types, show "got N" using the container's + // size (PyDict_Size). Exact tuple/list are handled by the + // fast path above and never reach here. + let msg = if value.class().is(vm.ctx.types.dict_type) { + if let Ok(got) = value.length(vm) { + if got > size { + format!("too many values to unpack (expected {size}, got {got})") + } else { + format!("too many values to unpack (expected {size})") + } + } else { + format!("too many values to unpack (expected {size})") + } + } else { + format!("too many values to unpack (expected {size})") + }; + Err(vm.new_value_error(msg)) + } + PyIterReturn::StopIteration(_) => { self.state .stack .extend(elements.into_iter().rev().map(Some)); - return Ok(None); - } - core::cmp::Ordering::Greater => { - format!("too many values to unpack (expected {size})") + Ok(None) } - core::cmp::Ordering::Less => format!( - "not enough values to unpack (expected {}, got {})", - size, - elements.len() - ), - }; - Err(vm.new_value_error(msg)) + } } fn convert_value( From f0f3c9c8cf374a63b934e1295f519780a35c3981 Mon Sep 17 00:00:00 2001 From: Shahar Naveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:43:49 +0200 Subject: [PATCH 15/18] Update `csv.py` from 3.14.3 (#7181) * Update `csv.py` from 3.14.3 * Don't hardcode error message for `int` class --- Lib/csv.py | 2 +- Lib/test/test_csv.py | 142 +++++++++++++++++++++++++++------------ crates/stdlib/src/csv.rs | 37 ++++++---- 3 files changed, 125 insertions(+), 56 deletions(-) diff --git a/Lib/csv.py b/Lib/csv.py index cd202659873..0a627ba7a51 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -63,7 +63,6 @@ class excel: written as two quotes """ -import re import types from _csv import Error, writer, reader, register_dialect, \ unregister_dialect, get_dialect, list_dialects, \ @@ -281,6 +280,7 @@ def _guess_quote_and_delimiter(self, data, delimiters): If there is no quotechar the delimiter can't be determined this way. """ + import re matches = [] for restr in (r'(?P[^\w\n"\'])(?P ?)(?P["\']).*?(?P=quote)(?P=delim)', # ,".*?", diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index bf9b1875573..8af2f0b337c 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1,4 +1,4 @@ -# Copyright (C) 2001,2002 Python Software Foundation +# Copyright (C) 2001 Python Software Foundation # csv package unit tests import copy @@ -10,7 +10,8 @@ import gc import pickle from test import support -from test.support import import_helper, check_disallow_instantiation +from test.support import cpython_only, import_helper, check_disallow_instantiation +from test.support.import_helper import ensure_lazy_imports from itertools import permutations from textwrap import dedent from collections import OrderedDict @@ -86,12 +87,12 @@ def _test_arg_valid(self, ctor, arg): self.assertRaises(ValueError, ctor, arg, quotechar='\x85', lineterminator='\x85') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_reader_arg_valid(self): self._test_arg_valid(csv.reader, []) self.assertRaises(OSError, csv.reader, BadIterable()) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_writer_arg_valid(self): self._test_arg_valid(csv.writer, StringIO()) class BadWriter: @@ -212,7 +213,7 @@ def test_write_bigfield(self): self._write_test([bigstring,bigstring], '%s,%s' % \ (bigstring, bigstring)) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_quoting(self): self._write_test(['a',1,'p,q'], 'a,1,"p,q"') self._write_error_test(csv.Error, ['a',1,'p,q'], @@ -230,7 +231,7 @@ def test_write_quoting(self): self._write_test(['a','',None,1], '"a","",,"1"', quoting = csv.QUOTE_NOTNULL) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_escape(self): self._write_test(['a',1,'p,q'], 'a,1,"p,q"', escapechar='\\') @@ -262,7 +263,7 @@ def test_write_escape(self): self._write_test(['C\\', '6', '7', 'X"'], 'C\\\\,6,7,"X"""', escapechar='\\', quoting=csv.QUOTE_MINIMAL) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_lineterminator(self): for lineterminator in '\r\n', '\n', '\r', '!@#', '\0': with self.subTest(lineterminator=lineterminator): @@ -276,7 +277,7 @@ def test_write_lineterminator(self): f'1,2{lineterminator}' f'"\r","\n"{lineterminator}') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_iterable(self): self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"') self._write_test(iter(['a', 1, None]), 'a,1,') @@ -319,7 +320,7 @@ def test_writerows_with_none(self): self.assertEqual(fileobj.read(), 'a\r\n""\r\n') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_empty_fields(self): self._write_test((), '') self._write_test([''], '""') @@ -333,7 +334,7 @@ def test_write_empty_fields(self): self._write_test(['', ''], ',') self._write_test([None, None], ',') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_write_empty_fields_space_delimiter(self): self._write_test([''], '""', delimiter=' ', skipinitialspace=False) self._write_test([''], '""', delimiter=' ', skipinitialspace=True) @@ -374,7 +375,7 @@ def _read_test(self, input, expect, **kwargs): result = list(reader) self.assertEqual(result, expect) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_oddinputs(self): self._read_test([], []) self._read_test([''], [[]]) @@ -385,7 +386,7 @@ def test_read_oddinputs(self): self.assertRaises(csv.Error, self._read_test, [b'abc'], None) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_eol(self): self._read_test(['a,b', 'c,d'], [['a','b'], ['c','d']]) self._read_test(['a,b\n', 'c,d\n'], [['a','b'], ['c','d']]) @@ -400,7 +401,7 @@ def test_read_eol(self): with self.assertRaisesRegex(csv.Error, errmsg): next(csv.reader(['a,b\r\nc,d'])) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_eof(self): self._read_test(['a,"'], [['a', '']]) self._read_test(['"a'], [['a']]) @@ -410,7 +411,7 @@ def test_read_eof(self): self.assertRaises(csv.Error, self._read_test, ['^'], [], escapechar='^', strict=True) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_nul(self): self._read_test(['\0'], [['\0']]) self._read_test(['a,\0b,c'], [['a', '\0b', 'c']]) @@ -423,7 +424,7 @@ def test_read_delimiter(self): self._read_test(['a;b;c'], [['a', 'b', 'c']], delimiter=';') self._read_test(['a\0b\0c'], [['a', 'b', 'c']], delimiter='\0') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') @@ -436,7 +437,7 @@ def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', '\\b', 'c']], escapechar=None) self._read_test(['a,\\b,c'], [['a', '\\b', 'c']]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_quoting(self): self._read_test(['1,",3,",5'], [['1', ',3,', '5']]) self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], @@ -473,7 +474,7 @@ def test_read_quoting(self): self._read_test(['1\\.5,\\.5,"\\.5"'], [[1.5, 0.5, ".5"]], quoting=csv.QUOTE_STRINGS, escapechar='\\') - @unittest.skip('TODO: RUSTPYTHON; slice index starts at 1 but ends at 0') + @unittest.skip("TODO: RUSTPYTHON; slice index starts at 1 but ends at 0") def test_read_skipinitialspace(self): self._read_test(['no space, space, spaces,\ttab'], [['no space', 'space', 'spaces', '\ttab']], @@ -488,7 +489,7 @@ def test_read_skipinitialspace(self): [[None, None, None]], skipinitialspace=True, quoting=csv.QUOTE_STRINGS) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_space_delimiter(self): self._read_test(['a b', ' a ', ' ', ''], [['a', '', '', 'b'], ['', '', 'a', '', ''], ['', '', ''], []], @@ -528,7 +529,7 @@ def test_read_linenum(self): self.assertRaises(StopIteration, next, r) self.assertEqual(r.line_num, 3) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_roundtrip_quoteed_newlines(self): rows = [ ['\na', 'b\nc', 'd\n'], @@ -547,7 +548,7 @@ def test_roundtrip_quoteed_newlines(self): for i, row in enumerate(csv.reader(fileobj)): self.assertEqual(row, rows[i]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_roundtrip_escaped_unquoted_newlines(self): rows = [ ['\na', 'b\nc', 'd\n'], @@ -662,7 +663,7 @@ def compare_dialect_123(self, expected, *writeargs, **kwwriteargs): fileobj.seek(0) self.assertEqual(fileobj.read(), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_dialect_apply(self): class testA(csv.excel): delimiter = "\t" @@ -784,7 +785,7 @@ def test_quoted_quote(self): '"I see," said the blind man', 'as he picked up his hammer and saw']]) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_quoted_nl(self): input = '''\ 1,2,3,"""I see,"" @@ -825,18 +826,18 @@ class EscapedExcel(csv.excel): class TestEscapedExcel(TestCsvBase): dialect = EscapedExcel() - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_escape_fieldsep(self): self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape_fieldsep(self): self.readerAssertEqual('abc\\,def\r\n', [['abc,def']]) class TestDialectUnix(TestCsvBase): dialect = 'unix' - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_simple_writer(self): self.writerAssertEqual([[1, 'abc def', 'abc']], '"1","abc def","abc"\n') @@ -853,7 +854,7 @@ class TestQuotedEscapedExcel(TestCsvBase): def test_write_escape_fieldsep(self): self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_escape_fieldsep(self): self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']]) @@ -941,6 +942,14 @@ def test_dict_reader_fieldnames_accepts_list(self): reader = csv.DictReader(f, fieldnames) self.assertEqual(reader.fieldnames, fieldnames) + def test_dict_reader_set_fieldnames(self): + fieldnames = ["a", "b", "c"] + f = StringIO() + reader = csv.DictReader(f) + self.assertIsNone(reader.fieldnames) + reader.fieldnames = fieldnames + self.assertEqual(reader.fieldnames, fieldnames) + def test_dict_writer_fieldnames_rejects_iter(self): fieldnames = ["a", "b", "c"] f = StringIO() @@ -956,6 +965,7 @@ def test_dict_writer_fieldnames_accepts_list(self): def test_dict_reader_fieldnames_is_optional(self): f = StringIO() reader = csv.DictReader(f, fieldnames=None) + self.assertIsNone(reader.fieldnames) def test_read_dict_fields(self): with TemporaryFile("w+", encoding="utf-8") as fileobj: @@ -1050,7 +1060,7 @@ def test_read_multi(self): "s1": 'abc', "s2": 'def'}) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_read_with_blanks(self): reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n", "1,2,abc,4,5,6\r\n"], @@ -1102,7 +1112,7 @@ def test_float_write(self): fileobj.seek(0) self.assertEqual(fileobj.read(), expected) - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_char_write(self): import array, string a = array.array('w', string.ascii_letters) @@ -1147,19 +1157,22 @@ class mydialect(csv.Dialect): with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"quotechar" must be a 1-character string') + '"quotechar" must be a unicode character or None, ' + 'not a string of length 0') mydialect.quotechar = "''" with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"quotechar" must be a 1-character string') + '"quotechar" must be a unicode character or None, ' + 'not a string of length 2') mydialect.quotechar = 4 with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"quotechar" must be string or None, not int') + '"quotechar" must be a unicode character or None, ' + 'not int') def test_delimiter(self): class mydialect(csv.Dialect): @@ -1176,31 +1189,32 @@ class mydialect(csv.Dialect): with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"delimiter" must be a 1-character string') + '"delimiter" must be a unicode character, ' + 'not a string of length 3') mydialect.delimiter = "" with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"delimiter" must be a 1-character string') + '"delimiter" must be a unicode character, not a string of length 0') mydialect.delimiter = b"," with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"delimiter" must be string, not bytes') + '"delimiter" must be a unicode character, not bytes') mydialect.delimiter = 4 with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"delimiter" must be string, not int') + '"delimiter" must be a unicode character, not int') mydialect.delimiter = None with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"delimiter" must be string, not NoneType') + '"delimiter" must be a unicode character, not NoneType') def test_escapechar(self): class mydialect(csv.Dialect): @@ -1214,20 +1228,32 @@ class mydialect(csv.Dialect): self.assertEqual(d.escapechar, "\\") mydialect.escapechar = "" - with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'): + with self.assertRaises(csv.Error) as cm: mydialect() + self.assertEqual(str(cm.exception), + '"escapechar" must be a unicode character or None, ' + 'not a string of length 0') mydialect.escapechar = "**" - with self.assertRaisesRegex(csv.Error, '"escapechar" must be a 1-character string'): + with self.assertRaises(csv.Error) as cm: mydialect() + self.assertEqual(str(cm.exception), + '"escapechar" must be a unicode character or None, ' + 'not a string of length 2') mydialect.escapechar = b"*" - with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not bytes'): + with self.assertRaises(csv.Error) as cm: mydialect() + self.assertEqual(str(cm.exception), + '"escapechar" must be a unicode character or None, ' + 'not bytes') mydialect.escapechar = 4 - with self.assertRaisesRegex(csv.Error, '"escapechar" must be string or None, not int'): + with self.assertRaises(csv.Error) as cm: mydialect() + self.assertEqual(str(cm.exception), + '"escapechar" must be a unicode character or None, ' + 'not int') def test_lineterminator(self): class mydialect(csv.Dialect): @@ -1248,9 +1274,15 @@ class mydialect(csv.Dialect): with self.assertRaises(csv.Error) as cm: mydialect() self.assertEqual(str(cm.exception), - '"lineterminator" must be a string') + '"lineterminator" must be a string, not int') + + mydialect.lineterminator = None + with self.assertRaises(csv.Error) as cm: + mydialect() + self.assertEqual(str(cm.exception), + '"lineterminator" must be a string, not NoneType') - @unittest.expectedFailure # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_invalid_chars(self): def create_invalid(field_name, value, **kwargs): class mydialect(csv.Dialect): @@ -1357,6 +1389,19 @@ class TestSniffer(unittest.TestCase): ghi\0jkl """ + sample15 = "\n\n\n" + sample16 = "abc\ndef\nghi" + + sample17 = ["letter,offset"] + sample17.extend(f"{chr(ord('a') + i)},{i}" for i in range(20)) + sample17.append("v,twenty_one") # 'u' was skipped + sample17 = '\n'.join(sample17) + + sample18 = ["letter,offset"] + sample18.extend(f"{chr(ord('a') + i)},{i}" for i in range(21)) + sample18.append("v,twenty_one") # 'u' was not skipped + sample18 = '\n'.join(sample18) + def test_issue43625(self): sniffer = csv.Sniffer() self.assertTrue(sniffer.has_header(self.sample12)) @@ -1378,6 +1423,11 @@ def test_has_header_regex_special_delimiter(self): self.assertIs(sniffer.has_header(self.sample8), False) self.assertIs(sniffer.has_header(self.header2 + self.sample8), True) + def test_has_header_checks_20_rows(self): + sniffer = csv.Sniffer() + self.assertFalse(sniffer.has_header(self.sample17)) + self.assertTrue(sniffer.has_header(self.sample18)) + def test_guess_quote_and_delimiter(self): sniffer = csv.Sniffer() for header in (";'123;4';", "'123;4';", ";'123;4'", "'123;4'"): @@ -1427,6 +1477,10 @@ def test_delimiters(self): self.assertEqual(dialect.quotechar, "'") dialect = sniffer.sniff(self.sample14) self.assertEqual(dialect.delimiter, '\0') + self.assertRaisesRegex(csv.Error, "Could not determine delimiter", + sniffer.sniff, self.sample15) + self.assertRaisesRegex(csv.Error, "Could not determine delimiter", + sniffer.sniff, self.sample16) def test_doublequote(self): sniffer = csv.Sniffer() @@ -1592,6 +1646,10 @@ class MiscTestCase(unittest.TestCase): def test__all__(self): support.check__all__(self, csv, ('csv', '_csv')) + @cpython_only + def test_lazy_import(self): + ensure_lazy_imports("csv", {"re"}) + def test_subclassable(self): # issue 44089 class Foo(csv.Error): ... diff --git a/crates/stdlib/src/csv.rs b/crates/stdlib/src/csv.rs index 0eecd07c936..b898dc8c106 100644 --- a/crates/stdlib/src/csv.rs +++ b/crates/stdlib/src/csv.rs @@ -17,7 +17,7 @@ mod _csv { use itertools::{self, Itertools}; use parking_lot::Mutex; use rustpython_common::lock::LazyLock; - use rustpython_vm::match_class; + use rustpython_vm::{match_class, sliceable::SliceableSequenceOp}; use std::collections::HashMap; #[pyattr] @@ -139,34 +139,43 @@ mod _csv { match_class!(match obj.to_owned() { s @ PyStr => { Ok(s.as_str().bytes().exactly_one().map_err(|_| { - let msg = r#""delimiter" must be a 1-character string"#; - vm.new_type_error(msg.to_owned()) + vm.new_type_error(format!( + r#""delimiter" must be a unicode character, not a string of length {}"#, + s.len() + )) })?) } attr => { - let msg = format!("\"delimiter\" must be string, not {}", attr.class()); + let msg = format!( + r#""delimiter" must be a unicode character, not {}"#, + attr.class() + ); Err(vm.new_type_error(msg)) } }) } } + fn parse_quotechar_from_obj(vm: &VirtualMachine, obj: &PyObject) -> PyResult> { match_class!(match obj.get_attr("quotechar", vm)? { s @ PyStr => { Ok(Some(s.as_str().bytes().exactly_one().map_err(|_| { vm.new_exception_msg( super::_csv::error(vm), - r#""quotechar" must be a 1-character string"#.to_owned(), + format!(r#""quotechar" must be a unicode character or None, not a string of length {}"#, s.len()), ) })?)) } _n @ PyNone => { Ok(None) } - _ => { + attr => { Err(vm.new_exception_msg( super::_csv::error(vm), - r#""quotechar" must be string or None, not int"#.to_owned(), + format!( + r#""quotechar" must be a unicode character or None, not {}"#, + attr.class() + ), )) } }) @@ -177,7 +186,7 @@ mod _csv { Ok(Some(s.as_str().bytes().exactly_one().map_err(|_| { vm.new_exception_msg( super::_csv::error(vm), - r#""escapechar" must be a 1-character string"#.to_owned(), + format!(r#""escapechar" must be a unicode character or None, not a string of length {}"#, s.len()), ) })?)) } @@ -186,7 +195,7 @@ mod _csv { } attr => { let msg = format!( - "\"escapechar\" must be string or None, not {}", + r#""escapechar" must be a unicode character or None, not {}"#, attr.class() ); Err(vm.new_type_error(msg.to_owned())) @@ -210,9 +219,11 @@ mod _csv { )); }) } - _ => { - let msg = "\"lineterminator\" must be a string".to_string(); - Err(vm.new_type_error(msg.to_owned())) + attr => { + Err(vm.new_type_error(format!( + r#""lineterminator" must be a string, not {}"#, + attr.class() + ))) } }) } @@ -225,7 +236,7 @@ mod _csv { })?) } attr => { - let msg = format!("\"quoting\" must be string or None, not {}", attr.class()); + let msg = format!(r#""quoting" must be string or None, not {}"#, attr.class()); Err(vm.new_type_error(msg.to_owned())) } }) From d07d52224eadd1e3c4a20c31ee0cd11894fbc195 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Wed, 18 Feb 2026 00:45:17 +0900 Subject: [PATCH 16/18] Optimize redundant bool check (#7176) * Add compile_bool_op_inner and optimize nested opposite-operator BoolOps to avoid redundant __bool__ calls When a nested BoolOp has the opposite operator (e.g., `And` inside `Or`), the inner BoolOp's short-circuit exits are redirected to skip the outer BoolOp's redundant truth test. This avoids calling `__bool__()` twice on the same value (e.g., `Test() and False or False` previously called `Test().__bool__()` twice instead of once). Co-Authored-By: Claude Opus 4.6 * Add snapshot test for nested BoolOp bytecode Co-Authored-By: Claude Opus 4.6 * Add runtime test for redundant __bool__ check (issue #3567) Co-Authored-By: Claude Opus 4.6 * Apply clippy and rustfmt * Apply ruff format * Refactor compile_bool_op: extract emit_short_circuit_test and unify with compile_bool_op_inner Reduce code duplication by: - Extracting the repeated Copy + conditional jump pattern into emit_short_circuit_test - Merging compile_bool_op and compile_bool_op_inner into a single compile_bool_op_with_target with an optional short_circuit_target parameter - Keeping compile_bool_op as a thin wrapper for the public interface Co-Authored-By: Claude Opus 4.6 * Relocate redundant __bool__ check test snippet * Update extra_tests/snippets/syntax_short_circuit_bool.py * Fix assertion in syntax_short_circuit_bool --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Jeong, YunWon <69878+youknowone@users.noreply.github.com> --- crates/codegen/src/compile.rs | 77 ++++++++++++++----- ...degen__compile__tests__nested_bool_op.snap | 20 +++++ .../snippets/syntax_short_circuit_bool.py | 3 + 3 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index a7672e9a472..205facd65bd 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6611,33 +6611,45 @@ impl Compiler { /// Compile a boolean operation as an expression. /// This means, that the last value remains on the stack. fn compile_bool_op(&mut self, op: &ast::BoolOp, values: &[ast::Expr]) -> CompileResult<()> { - let after_block = self.new_block(); + self.compile_bool_op_with_target(op, values, None) + } + /// Compile a boolean operation as an expression, with an optional + /// short-circuit target override. When `short_circuit_target` is `Some`, + /// the short-circuit jumps go to that block instead of the default + /// `after_block`, enabling jump threading to avoid redundant `__bool__` calls. + fn compile_bool_op_with_target( + &mut self, + op: &ast::BoolOp, + values: &[ast::Expr], + short_circuit_target: Option, + ) -> CompileResult<()> { + let after_block = self.new_block(); let (last_value, values) = values.split_last().unwrap(); + let jump_target = short_circuit_target.unwrap_or(after_block); for value in values { - self.compile_expression(value)?; - - emit!(self, Instruction::Copy { index: 1_u32 }); - match op { - ast::BoolOp::And => { - emit!( - self, - Instruction::PopJumpIfFalse { - target: after_block, - } - ); - } - ast::BoolOp::Or => { - emit!( - self, - Instruction::PopJumpIfTrue { - target: after_block, - } - ); - } + // Optimization: when a non-last value is a BoolOp with the opposite + // operator, redirect its short-circuit exits to skip the outer's + // redundant __bool__ test (jump threading). + if short_circuit_target.is_none() + && let ast::Expr::BoolOp(ast::ExprBoolOp { + op: inner_op, + values: inner_values, + .. + }) = value + && inner_op != op + { + let pop_block = self.new_block(); + self.compile_bool_op_with_target(inner_op, inner_values, Some(pop_block))?; + self.emit_short_circuit_test(op, after_block); + self.switch_to_block(pop_block); + emit!(self, Instruction::PopTop); + continue; } + self.compile_expression(value)?; + self.emit_short_circuit_test(op, jump_target); emit!(self, Instruction::PopTop); } @@ -6647,6 +6659,20 @@ impl Compiler { Ok(()) } + /// Emit `Copy 1` + conditional jump for short-circuit evaluation. + /// For `And`, emits `PopJumpIfFalse`; for `Or`, emits `PopJumpIfTrue`. + fn emit_short_circuit_test(&mut self, op: &ast::BoolOp, target: BlockIdx) { + emit!(self, Instruction::Copy { index: 1_u32 }); + match op { + ast::BoolOp::And => { + emit!(self, Instruction::PopJumpIfFalse { target }); + } + ast::BoolOp::Or => { + emit!(self, Instruction::PopJumpIfTrue { target }); + } + } + } + fn compile_dict(&mut self, items: &[ast::DictItem]) -> CompileResult<()> { let has_unpacking = items.iter().any(|item| item.key.is_none()); @@ -9006,6 +9032,15 @@ if (True and False) or (False and True): )); } + #[test] + fn test_nested_bool_op() { + assert_dis_snapshot!(compile_exec( + "\ +x = Test() and False or False +" + )); + } + #[test] fn test_nested_double_async_with() { assert_dis_snapshot!(compile_exec( diff --git a/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap new file mode 100644 index 00000000000..5b9a2182bdf --- /dev/null +++ b/crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap @@ -0,0 +1,20 @@ +--- +source: crates/codegen/src/compile.rs +assertion_line: 9071 +expression: "compile_exec(\"\\\nx = Test() and False or False\n\")" +--- + 1 0 RESUME (0) + 1 LOAD_NAME (0, Test) + 2 PUSH_NULL + 3 CALL (0) + 4 COPY (1) + 5 POP_JUMP_IF_FALSE (10) + 6 POP_TOP + 7 LOAD_CONST (False) + 8 COPY (1) + 9 POP_JUMP_IF_TRUE (12) + >> 10 POP_TOP + 11 LOAD_CONST (False) + >> 12 STORE_NAME (1, x) + 13 LOAD_CONST (None) + 14 RETURN_VALUE diff --git a/extra_tests/snippets/syntax_short_circuit_bool.py b/extra_tests/snippets/syntax_short_circuit_bool.py index 76d89352cbb..6cbae190cae 100644 --- a/extra_tests/snippets/syntax_short_circuit_bool.py +++ b/extra_tests/snippets/syntax_short_circuit_bool.py @@ -31,3 +31,6 @@ def __bool__(self): # if ExplodingBool(False) and False and True and False: # pass + +# Issue #3567: nested BoolOps should not call __bool__ redundantly +assert (ExplodingBool(False) and False or False) == False From b5785e2777fd586074c94a4a3f6cb45f06a44bf4 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:50:27 +0900 Subject: [PATCH 17/18] Use _print_exception_bltin in excepthook, register source in linecache (#7177) - excepthook: call traceback._print_exception_bltin instead of traceback.print_exception to match PyErr_Display behavior - run_string: register compiled code in linecache._interactive_cache so traceback can display source lines and caret indicators - Remove test_sys_tracebacklimit expectedFailure --- .cspell.dict/cpython.txt | 1 + Lib/test/test_sys.py | 1 - Lib/test/test_warnings/__init__.py | 1 - crates/vm/src/stdlib/sys.rs | 10 ++++++---- crates/vm/src/vm/python_run.rs | 16 +++++++++++++++- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index c0d007a90c3..f428c42e5f6 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -11,6 +11,7 @@ badsyntax baseinfo basetype binop +bltin boolop BUFMAX BUILDSTDLIB diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index b72d09865d8..9ebd6dd9cc1 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1232,7 +1232,6 @@ def test_getandroidapilevel(self): self.assertIsInstance(level, int) self.assertGreater(level, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON @force_not_colorized @support.requires_subprocess() def test_sys_tracebacklimit(self): diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index d466128e8be..53ac0363a3c 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1478,7 +1478,6 @@ def test_envvar_and_command_line(self): self.assertEqual(stdout, b"['ignore::DeprecationWarning', 'ignore::UnicodeWarning']") - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b"['error::DeprecationWarning']" != b"['default::DeprecationWarning', 'error::DeprecationWarning']" @force_not_colorized def test_conflicting_envvar_and_command_line(self): rc, stdout, stderr = assert_python_failure("-Werror::DeprecationWarning", "-c", diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 0a6f26d642c..22b720a1cd2 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -825,11 +825,13 @@ mod sys { let stderr = super::get_stderr(vm)?; match vm.normalize_exception(exc_type.clone(), exc_val.clone(), exc_tb) { Ok(exc) => { - // Try Python traceback module first for richer output - // (enables features like keyword typo suggestions in SyntaxError) + // PyErr_Display: try traceback._print_exception_bltin first if let Ok(tb_mod) = vm.import("traceback", 0) - && let Ok(print_exc) = tb_mod.get_attr("print_exception", vm) - && print_exc.call((exc.as_object().to_owned(),), vm).is_ok() + && let Ok(print_exc_builtin) = + tb_mod.get_attr("_print_exception_bltin", vm) + && print_exc_builtin + .call((exc.as_object().to_owned(),), vm) + .is_ok() { return Ok(()); } diff --git a/crates/vm/src/vm/python_run.rs b/crates/vm/src/vm/python_run.rs index e651b34cc50..70d845b03f5 100644 --- a/crates/vm/src/vm/python_run.rs +++ b/crates/vm/src/vm/python_run.rs @@ -1,7 +1,8 @@ //! Python code execution functions. use crate::{ - PyResult, VirtualMachine, + AsObject, PyRef, PyResult, VirtualMachine, + builtins::PyCode, compiler::{self}, scope::Scope, }; @@ -22,9 +23,22 @@ impl VirtualMachine { let code_obj = self .compile(source, compiler::Mode::Exec, source_path) .map_err(|err| self.new_syntax_error(&err, Some(source)))?; + // linecache._register_code(code, source, filename) + let _ = self.register_code_in_linecache(&code_obj, source); self.run_code_obj(code_obj, scope) } + /// Register a code object's source in linecache._interactive_cache + /// so that traceback can display source lines and caret indicators. + fn register_code_in_linecache(&self, code: &PyRef, source: &str) -> PyResult<()> { + let linecache = self.import("linecache", 0)?; + let register = linecache.get_attr("_register_code", self)?; + let source_str = self.ctx.new_str(source); + let filename = self.ctx.new_str(code.source_path().as_str()); + register.call((code.as_object().to_owned(), source_str, filename), self)?; + Ok(()) + } + #[deprecated(note = "use run_string instead")] pub fn run_code_string(&self, scope: Scope, source: &str, source_path: String) -> PyResult { self.run_string(scope, source, source_path) From e81a0fc765314296f4026db793c36080041fd43d Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:24:06 +0900 Subject: [PATCH 18/18] Use try_lock in py_os_after_fork_child (#7178) after_forkers_child.lock() can deadlock in the forked child if another thread held the mutex at the time of fork. Use try_lock and skip at-fork callbacks when the lock is unavailable, matching the pattern used in after_fork_child for thread_handles. --- crates/vm/src/stdlib/nt.rs | 19 ++----------------- crates/vm/src/stdlib/os.rs | 30 ++++++++++++++++++++++++++++-- crates/vm/src/stdlib/posix.rs | 27 ++++++++++----------------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/crates/vm/src/stdlib/nt.rs b/crates/vm/src/stdlib/nt.rs index 0013aa0f970..5b2cf3b92f5 100644 --- a/crates/vm/src/stdlib/nt.rs +++ b/crates/vm/src/stdlib/nt.rs @@ -6,7 +6,7 @@ pub use module::raw_set_handle_inheritable; #[pymodule(name = "nt", with(super::os::_os))] pub(crate) mod module { use crate::{ - Py, PyObjectRef, PyResult, TryFromObject, VirtualMachine, + Py, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyDictRef, PyListRef, PyStrRef, PyTupleRef}, common::{crt_fd, suppress_iph, windows::ToWideString}, convert::ToPyException, @@ -1212,21 +1212,6 @@ pub(crate) mod module { } } - fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult { - let obj = env.obj(); - if let Some(dict) = obj.downcast_ref_if_exact::(vm) { - return Ok(dict.to_owned()); - } - let keys = vm.call_method(obj, "keys", ())?; - let dict = vm.ctx.new_dict(); - for key in keys.get_iter(vm)?.into_iter::(vm)? { - let key = key?; - let val = obj.get_item(&*key, vm)?; - dict.set_item(&*key, val, vm)?; - } - Ok(dict) - } - #[cfg(target_env = "msvc")] #[pyfunction] fn execve( @@ -1261,7 +1246,7 @@ pub(crate) mod module { .chain(once(core::ptr::null())) .collect(); - let env = envobj_to_dict(env, vm)?; + let env = crate::stdlib::os::envobj_to_dict(env, vm)?; // Build environment strings as "KEY=VALUE\0" wide strings let mut env_strings: Vec = Vec::new(); for (key, value) in env.into_iter() { diff --git a/crates/vm/src/stdlib/os.rs b/crates/vm/src/stdlib/os.rs index 8b7d3915278..2fb71d9ec01 100644 --- a/crates/vm/src/stdlib/os.rs +++ b/crates/vm/src/stdlib/os.rs @@ -2,10 +2,10 @@ use crate::{ AsObject, Py, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, - builtins::{PyModule, PySet}, + builtins::{PyDictRef, PyModule, PySet}, common::crt_fd, convert::{IntoPyException, ToPyException, ToPyObject}, - function::{ArgumentError, FromArgs, FuncArgs}, + function::{ArgMapping, ArgumentError, FromArgs, FuncArgs}, }; use std::{fs, io, path::Path}; @@ -2038,6 +2038,32 @@ pub fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { Ok(()) } +/// Convert a mapping (e.g. os._Environ) to a plain dict for use by execve/posix_spawn. +/// +/// For `os._Environ`, accesses the internal `_data` dict directly at the Rust level. +/// This avoids Python-level method calls that can deadlock after fork() when +/// parking_lot locks are held by threads that no longer exist. +pub(crate) fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult { + let obj = env.obj(); + if let Some(dict) = obj.downcast_ref_if_exact::(vm) { + return Ok(dict.to_owned()); + } + if let Some(inst_dict) = obj.dict() + && let Ok(Some(data)) = inst_dict.get_item_opt("_data", vm) + && let Some(dict) = data.downcast_ref_if_exact::(vm) + { + return Ok(dict.to_owned()); + } + let keys = vm.call_method(obj, "keys", ())?; + let dict = vm.ctx.new_dict(); + for key in keys.get_iter(vm)?.into_iter::(vm)? { + let key = key?; + let val = obj.get_item(&*key, vm)?; + dict.set_item(&*key, val, vm)?; + } + Ok(dict) +} + #[cfg(not(windows))] use super::posix as platform; diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 1c4b502f9e2..ce70412df76 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -716,7 +716,15 @@ pub mod module { vm.signal_handlers .get_or_init(crate::signal::new_signal_handlers); - let after_forkers_child: Vec = vm.state.after_forkers_child.lock().clone(); + let after_forkers_child = match vm.state.after_forkers_child.try_lock() { + Some(guard) => guard.clone(), + None => { + // SAFETY: After fork in child process, only the current thread + // exists. The lock holder no longer exists. + unsafe { vm.state.after_forkers_child.force_unlock() }; + vm.state.after_forkers_child.lock().clone() + } + }; run_at_forkers(after_forkers_child, false, vm); } @@ -1073,21 +1081,6 @@ pub mod module { .map_err(|err| err.into_pyexception(vm)) } - fn envobj_to_dict(env: ArgMapping, vm: &VirtualMachine) -> PyResult { - let obj = env.obj(); - if let Some(dict) = obj.downcast_ref_if_exact::(vm) { - return Ok(dict.to_owned()); - } - let keys = vm.call_method(obj, "keys", ())?; - let dict = vm.ctx.new_dict(); - for key in keys.get_iter(vm)?.into_iter::(vm)? { - let key = key?; - let val = obj.get_item(&*key, vm)?; - dict.set_item(&*key, val, vm)?; - } - Ok(dict) - } - #[pyfunction] fn execve( path: OsPath, @@ -1110,7 +1103,7 @@ pub mod module { return Err(vm.new_value_error("execve() arg 2 first element cannot be empty")); } - let env = envobj_to_dict(env, vm)?; + let env = crate::stdlib::os::envobj_to_dict(env, vm)?; let env = env .into_iter() .map(|(k, v)| -> PyResult<_> {