diff --git a/.github/workflows/add-issue-header.yml b/.github/workflows/add-issue-header.yml index 4c25976b9c24f7..09da61a470ff95 100644 --- a/.github/workflows/add-issue-header.yml +++ b/.github/workflows/add-issue-header.yml @@ -22,7 +22,7 @@ jobs: issues: write timeout-minutes: 5 steps: - - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: # language=JavaScript script: | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9a956a6bf5303..0edf4602bfaf97 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: run: | apt update && apt install git -yq git config --global --add safe.directory "$GITHUB_WORKSPACE" - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 1 persist-credentials: false @@ -101,7 +101,7 @@ jobs: needs: build-context if: needs.build-context.outputs.run-tests == 'true' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -291,7 +291,7 @@ jobs: SSLLIB_DIR: ${{ github.workspace }}/multissl/${{ matrix.ssllib.name }}/${{ matrix.ssllib.version }} LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/${{ matrix.ssllib.name }}/${{ matrix.ssllib.version }}/lib steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Runner image version @@ -350,7 +350,7 @@ jobs: runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Build and test @@ -363,7 +363,7 @@ jobs: timeout-minutes: 60 runs-on: macos-14 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false @@ -401,7 +401,7 @@ jobs: OPENSSL_VER: 3.5.7 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Register gcc problem matcher @@ -510,7 +510,7 @@ jobs: PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Runner image version @@ -577,7 +577,7 @@ jobs: needs: build-context if: needs.build-context.outputs.run-ubuntu == 'true' steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Runner image version diff --git a/.github/workflows/jit.yml b/.github/workflows/jit.yml index 025ff7ecbeeaff..846c90d2525d25 100644 --- a/.github/workflows/jit.yml +++ b/.github/workflows/jit.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Install dependencies @@ -72,7 +72,7 @@ jobs: architecture: ARM64 runner: windows-11-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -106,7 +106,7 @@ jobs: - target: aarch64-apple-darwin/clang runner: macos-15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -149,7 +149,7 @@ jobs: - target: aarch64-unknown-linux-gnu/gcc runner: ubuntu-24.04-arm steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -191,7 +191,7 @@ jobs: use_clang: true run_tests: false steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e9a4eb2b0808cb..8a79ea20d5f50b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - - uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1.1.1 + - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4 diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index d748b6ff63e68a..3cdce4f5952e3d 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -66,7 +66,7 @@ jobs: "Tools/peg_generator", ] steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 diff --git a/.github/workflows/new-bugs-announce-notifier.yml b/.github/workflows/new-bugs-announce-notifier.yml index 1267361040c81b..9c19adb7ffda70 100644 --- a/.github/workflows/new-bugs-announce-notifier.yml +++ b/.github/workflows/new-bugs-announce-notifier.yml @@ -20,7 +20,7 @@ jobs: node-version: 20 - run: npm install mailgun.js form-data - name: Send notification - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: MAILGUN_API_KEY: ${{ secrets.MAILGUN_PYTHON_ORG_MAILGUN_KEY }} with: diff --git a/.github/workflows/reusable-check-c-api-docs.yml b/.github/workflows/reusable-check-c-api-docs.yml index 49e5ef7f768b79..db030c80008b1e 100644 --- a/.github/workflows/reusable-check-c-api-docs.yml +++ b/.github/workflows/reusable-check-c-api-docs.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/reusable-check-html-ids.yml b/.github/workflows/reusable-check-html-ids.yml index 4f827c55cacd06..41ba1288be1ecf 100644 --- a/.github/workflows/reusable-check-html-ids.yml +++ b/.github/workflows/reusable-check-html-ids.yml @@ -16,7 +16,7 @@ jobs: timeout-minutes: 30 steps: - name: 'Check out PR head' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/reusable-context.yml b/.github/workflows/reusable-context.yml index b8a9e2960eca59..6595aef0377e50 100644 --- a/.github/workflows/reusable-context.yml +++ b/.github/workflows/reusable-context.yml @@ -84,7 +84,7 @@ jobs: - run: >- echo '${{ github.event_name }}' - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false ref: >- diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 7b524569f85c9e..cac481d26a8d0c 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -27,7 +27,7 @@ jobs: refspec_pr: '+${{ github.event.pull_request.head.sha }}:remotes/origin/${{ github.event.pull_request.head.ref }}' steps: - name: 'Check out latest PR branch commit' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false ref: >- @@ -98,7 +98,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 @@ -124,7 +124,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: 'Set up Python' diff --git a/.github/workflows/reusable-emscripten.yml b/.github/workflows/reusable-emscripten.yml index 38e6dcceb8f47c..46664522aa6d79 100644 --- a/.github/workflows/reusable-emscripten.yml +++ b/.github/workflows/reusable-emscripten.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 40 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: "Read Emscripten config" diff --git a/.github/workflows/reusable-macos.yml b/.github/workflows/reusable-macos.yml index 65a7f857fc4c77..5393e9e34040b9 100644 --- a/.github/workflows/reusable-macos.yml +++ b/.github/workflows/reusable-macos.yml @@ -31,7 +31,7 @@ jobs: PYTHONSTRICTEXTENSIONBUILD: 1 TERM: linux steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Runner image version diff --git a/.github/workflows/reusable-san.yml b/.github/workflows/reusable-san.yml index ef36447964cf41..4c76952386095e 100644 --- a/.github/workflows/reusable-san.yml +++ b/.github/workflows/reusable-san.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 60 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Runner image version diff --git a/.github/workflows/reusable-ubuntu.yml b/.github/workflows/reusable-ubuntu.yml index f4321cefa1b598..b8f264ff13b2ce 100644 --- a/.github/workflows/reusable-ubuntu.yml +++ b/.github/workflows/reusable-ubuntu.yml @@ -39,7 +39,7 @@ jobs: PYTHONSTRICTEXTENSIONBUILD: 1 TERM: linux steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Register gcc problem matcher diff --git a/.github/workflows/reusable-wasi.yml b/.github/workflows/reusable-wasi.yml index 4b4888c3844409..9675c39b86a24c 100644 --- a/.github/workflows/reusable-wasi.yml +++ b/.github/workflows/reusable-wasi.yml @@ -18,7 +18,7 @@ jobs: WASMTIME_VERSION: 38.0.3 CROSS_BUILD_WASI: cross-build/wasm32-wasip1 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # No problem resolver registered as one doesn't currently exist for Clang. diff --git a/.github/workflows/reusable-windows-msi.yml b/.github/workflows/reusable-windows-msi.yml index d07b4f7f29e487..d57046ae5ca8f0 100644 --- a/.github/workflows/reusable-windows-msi.yml +++ b/.github/workflows/reusable-windows-msi.yml @@ -23,7 +23,7 @@ jobs: ARCH: ${{ inputs.arch }} IncludeFreethreaded: true steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Build CPython installer diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index dbb192fb8819a4..707b8d7efb5e0d 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -31,7 +31,7 @@ jobs: env: ARCH: ${{ inputs.arch }} steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Register MSVC problem matcher diff --git a/.github/workflows/tail-call.yml b/.github/workflows/tail-call.yml index 656a14906b3cb7..f5b174d87d8448 100644 --- a/.github/workflows/tail-call.yml +++ b/.github/workflows/tail-call.yml @@ -36,7 +36,7 @@ jobs: - target: aarch64-apple-darwin/clang runner: macos-15 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 @@ -75,7 +75,7 @@ jobs: runner: ubuntu-24.04-arm configure_flags: --with-pydebug steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/verify-ensurepip-wheels.yml b/.github/workflows/verify-ensurepip-wheels.yml index cb40f6abc0b3b7..7def8c9e78abb8 100644 --- a/.github/workflows/verify-ensurepip-wheels.yml +++ b/.github/workflows/verify-ensurepip-wheels.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/verify-expat.yml b/.github/workflows/verify-expat.yml index 472a11db2da5fb..bae93a4d9ac0a2 100644 --- a/.github/workflows/verify-expat.yml +++ b/.github/workflows/verify-expat.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Download and verify bundled libexpat files diff --git a/Doc/c-api/bytes.rst b/Doc/c-api/bytes.rst index fa77d3d38ff89f..ff68ecafcda4d0 100644 --- a/Doc/c-api/bytes.rst +++ b/Doc/c-api/bytes.rst @@ -184,10 +184,11 @@ called with a non-bytes parameter. .. c:function:: void PyBytes_Concat(PyObject **bytes, PyObject *newpart) Create a new bytes object in *\*bytes* containing the contents of *newpart* - appended to *bytes*; the caller will own the new reference. The reference to - the old value of *bytes* will be stolen. If the new object cannot be - created, the old reference to *bytes* will still be discarded and the value - of *\*bytes* will be set to ``NULL``; the appropriate exception will be set. + appended to *bytes*; the caller will own the new reference. + The reference to the old value of *bytes* will be ":term:`stolen `". + If the new object cannot be created, the old reference to *bytes* will still + be "stolen", the value of *\*bytes* will be set to ``NULL``, and + the appropriate exception will be set. .. note:: If *newpart* implements the buffer protocol, then the buffer diff --git a/Doc/c-api/dict.rst b/Doc/c-api/dict.rst index 556113a97bf772..87d09ad2412e40 100644 --- a/Doc/c-api/dict.rst +++ b/Doc/c-api/dict.rst @@ -107,8 +107,8 @@ Dictionary objects Insert *val* into the dictionary *p* with a key of *key*. *key* must be :term:`hashable`; if it isn't, :exc:`TypeError` will be raised. Return - ``0`` on success or ``-1`` on failure. This function *does not* steal a - reference to *val*. + ``0`` on success or ``-1`` on failure. + This function *does not* ":term:`steal`" a reference to *val*. .. note:: diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 82f594e11300a7..78c95791ab3104 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -503,7 +503,8 @@ Querying the error indicator .. warning:: - This call steals a reference to *exc*, which must be a valid exception. + This call ":term:`steals `" a reference to *exc*, + which must be a valid exception. .. versionadded:: 3.12 @@ -641,7 +642,8 @@ Querying the error indicator Set the exception info, as known from ``sys.exc_info()``. This refers to an exception that was *already caught*, not to an exception that was - freshly raised. This function steals the references of the arguments. + freshly raised. This function ":term:`steals `" the references + of the arguments. To clear the exception state, pass ``NULL`` for all three arguments. This function is kept for backwards compatibility. Prefer using :c:func:`PyErr_SetHandledException`. @@ -658,8 +660,8 @@ Querying the error indicator .. versionchanged:: 3.11 The ``type`` and ``traceback`` arguments are no longer used and can be NULL. The interpreter now derives them from the exception - instance (the ``value`` argument). The function still steals - references of all three arguments. + instance (the ``value`` argument). The function still + ":term:`steals `" references of all three arguments. Signal Handling @@ -869,7 +871,7 @@ Exception Objects Set the context associated with the exception to *ctx*. Use ``NULL`` to clear it. There is no type check to make sure that *ctx* is an exception instance. - This steals a reference to *ctx*. + This ":term:`steals `" a reference to *ctx*. .. c:function:: PyObject* PyException_GetCause(PyObject *ex) @@ -884,7 +886,8 @@ Exception Objects Set the cause associated with the exception to *cause*. Use ``NULL`` to clear it. There is no type check to make sure that *cause* is either an exception - instance or ``None``. This steals a reference to *cause*. + instance or ``None``. + This ":term:`steals `" a reference to *cause*. The :attr:`~BaseException.__suppress_context__` attribute is implicitly set to ``True`` by this function. diff --git a/Doc/c-api/gen.rst b/Doc/c-api/gen.rst index 7713ba2ee4f804..88c3cc31186826 100644 --- a/Doc/c-api/gen.rst +++ b/Doc/c-api/gen.rst @@ -35,8 +35,8 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`. .. c:function:: PyObject* PyGen_New(PyFrameObject *frame) Create and return a new generator object based on the *frame* object. - A reference to *frame* is stolen by this function. The argument must not be - ``NULL``. + A reference to *frame* is ":term:`stolen `" by this function (even + on error). The argument must not be ``NULL``. .. deprecated-removed:: 3.16 3.18 @@ -48,8 +48,8 @@ than explicitly calling :c:func:`PyGen_New` or :c:func:`PyGen_NewWithQualName`. Create and return a new generator object based on the *frame* object, with ``__name__`` and ``__qualname__`` set to *name* and *qualname*. - A reference to *frame* is stolen by this function. The *frame* argument - must not be ``NULL``. + A reference to *frame* is ":term:`stolen `" by this function (even + on error). The *frame* argument must not be ``NULL``. .. deprecated-removed:: 3.16 3.18 @@ -80,8 +80,9 @@ Asynchronous Generator Objects .. c:function:: PyObject *PyAsyncGen_New(PyFrameObject *frame, PyObject *name, PyObject *qualname) Create a new asynchronous generator wrapping *frame*, with ``__name__`` and - ``__qualname__`` set to *name* and *qualname*. *frame* is stolen by this - function and must not be ``NULL``. + ``__qualname__`` set to *name* and *qualname*. + *frame* is ":term:`stolen `" by this function (even on error) and + must not be ``NULL``. On success, this function returns a :term:`strong reference` to the new asynchronous generator. On failure, this function returns ``NULL`` diff --git a/Doc/c-api/intro.rst b/Doc/c-api/intro.rst index 500f2818e2e40a..4c0c9af45e8360 100644 --- a/Doc/c-api/intro.rst +++ b/Doc/c-api/intro.rst @@ -737,9 +737,12 @@ the caller is said to *borrow* the reference. Nothing needs to be done for a Conversely, when a calling function passes in a reference to an object, there are two possibilities: the function *steals* a reference to the object, or it -does not. *Stealing a reference* means that when you pass a reference to a -function, that function assumes that it now owns that reference, and you are not -responsible for it any longer. +does not. + +*Stealing a reference* means that when you pass a reference to a +function, that function assumes that it now owns that reference. +Since the new owner can use :c:func:`!Py_DECREF` at its discretion, +you (the caller) must not use that reference after the call. .. index:: single: PyList_SetItem (C function) diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst index 8f560699d355e4..21769c4c5c869c 100644 --- a/Doc/c-api/list.rst +++ b/Doc/c-api/list.rst @@ -102,8 +102,10 @@ List Objects .. note:: - This function "steals" a reference to *item* and discards a reference to - an item already in the list at the affected position. + This function ":term:`steals `" a reference to *item*, + even on error. + On success, it discards a reference to an item already in the list + at the affected position (unless it was ``NULL``). .. c:function:: void PyList_SET_ITEM(PyObject *list, Py_ssize_t i, PyObject *o) @@ -117,7 +119,7 @@ List Objects .. note:: - This macro "steals" a reference to *item*, and, unlike + This macro ":term:`steals `" a reference to *item*, and, unlike :c:func:`PyList_SetItem`, does *not* discard a reference to any item that is being replaced; any reference in *list* at position *i* will be leaked. diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index 9f68abba66bc5d..31df4451f15e66 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -488,7 +488,7 @@ defining the module state. .. versionadded:: 3.15 - Use :c:member:`PyModuleDef.m_size` instead to support previous versions. + Use :c:member:`PyModuleDef.m_traverse` instead to support previous versions. .. c:macro:: Py_mod_state_clear @@ -974,8 +974,8 @@ or code that creates modules dynamically. .. c:function:: int PyModule_Add(PyObject *module, const char *name, PyObject *value) - Similar to :c:func:`PyModule_AddObjectRef`, but "steals" a reference - to *value*. + Similar to :c:func:`PyModule_AddObjectRef`, but ":term:`steals `" + a reference to *value* (even on error). It can be called with a result of function that returns a new reference without bothering to check its result or even saving it to a variable. @@ -990,8 +990,8 @@ or code that creates modules dynamically. .. c:function:: int PyModule_AddObject(PyObject *module, const char *name, PyObject *value) - Similar to :c:func:`PyModule_AddObjectRef`, but steals a reference to - *value* on success (if it returns ``0``). + Similar to :c:func:`PyModule_AddObjectRef`, but :term:`steals ` + a reference to *value* on success (if it returns ``0``). The new :c:func:`PyModule_Add` or :c:func:`PyModule_AddObjectRef` functions are recommended, since it is diff --git a/Doc/c-api/sequence.rst b/Doc/c-api/sequence.rst index 6bae8f25ad75d1..90490cf6749b59 100644 --- a/Doc/c-api/sequence.rst +++ b/Doc/c-api/sequence.rst @@ -67,7 +67,7 @@ Sequence Protocol Assign object *v* to the *i*\ th element of *o*. Raise an exception and return ``-1`` on failure; return ``0`` on success. This is the equivalent of the Python statement ``o[i] = v``. This function *does - not* steal a reference to *v*. + not* ":term:`steal`" a reference to *v*. If *v* is ``NULL``, the element is deleted, but this feature is deprecated in favour of using :c:func:`PySequence_DelItem`. diff --git a/Doc/c-api/threads.rst b/Doc/c-api/threads.rst index 67a9ac54909727..f48af654e7422b 100644 --- a/Doc/c-api/threads.rst +++ b/Doc/c-api/threads.rst @@ -924,7 +924,7 @@ pointer and a void pointer argument. To prevent naive misuse, you must write your own C extension to call this. This function must be called with an :term:`attached thread state`. - This function does not steal any references to *exc*. + This function does not :term:`steal` any references to *exc*. This function does not necessarily interrupt system calls such as :py:func:`~time.sleep`. diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index ba4c6b93de4c11..e8be4762dc33a1 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -104,8 +104,9 @@ Tuple Objects .. note:: - This function "steals" a reference to *o* and discards a reference to - an item already in the tuple at the affected position. + This function ":term:`steals `" a reference to *o* and discards + a reference to an item already in the tuple at the affected position + (unless it was NULL). .. c:function:: void PyTuple_SET_ITEM(PyObject *p, Py_ssize_t pos, PyObject *o) @@ -118,7 +119,7 @@ Tuple Objects .. note:: - This function "steals" a reference to *o*, and, unlike + This function ":term:`steals `" a reference to *o*, and, unlike :c:func:`PyTuple_SetItem`, does *not* discard a reference to any item that is being replaced; any reference in the tuple at position *pos* will be leaked. @@ -263,7 +264,7 @@ type. .. note:: - This function "steals" a reference to *o*. + This function ":term:`steals `" a reference to *o*. .. c:function:: void PyStructSequence_SET_ITEM(PyObject *p, Py_ssize_t *pos, PyObject *o) diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index 634dcbce7a5791..9bf801ad608c77 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -676,7 +676,8 @@ APIs: Append the string *right* to the end of *p_left*. *p_left* must point to a :term:`strong reference` to a Unicode object; - :c:func:`!PyUnicode_Append` releases ("steals") this reference. + :c:func:`!PyUnicode_Append` releases (":term:`steals `") + this reference. On error, set *\*p_left* to ``NULL`` and set an exception. diff --git a/Doc/glossary.rst b/Doc/glossary.rst index b25532d2d63412..bb00a4f02f0efd 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -1515,6 +1515,14 @@ Glossary stdlib An abbreviation of :term:`standard library`. + steal + In Python's C API, "*stealing*" an argument means that ownership of the + argument is transferred to the called function. + The caller must not use that reference after the call. + Generally, functions that "steal" an argument do so even if they fail. + + See :ref:`api-refcountdetails` for a full explanation. + strong reference In Python's C API, a strong reference is a reference to an object which is owned by the code holding the reference. The strong diff --git a/Lib/idlelib/grep.py b/Lib/idlelib/grep.py index 42048ff2395fe1..5d604faec23f32 100644 --- a/Lib/idlelib/grep.py +++ b/Lib/idlelib/grep.py @@ -40,6 +40,20 @@ def grep(text, io=None, flist=None): dialog.open(text, searchphrase, io) +def default_glob(path): + """Return the initial "In files:" pattern for a file path (gh-80504). + + Always include a full directory so that grep output shows which + directory was searched. + """ + dir, base = os.path.split(path) + dir = os.path.abspath(dir) # An empty dir becomes the current directory. + head, tail = os.path.splitext(base) + if not tail: + tail = ".py" + return os.path.join(dir, "*" + tail) + + def walk_error(msg): "Handle os.walk error." print(msg) @@ -103,11 +117,7 @@ def open(self, text, searchphrase, io=None): path = io.filename or "" else: path = "" - dir, base = os.path.split(path) - head, tail = os.path.splitext(base) - if not tail: - tail = ".py" - self.globvar.set(os.path.join(dir, "*" + tail)) + self.globvar.set(default_glob(path)) def create_entries(self): "Create base entry widgets and add widget for search path." diff --git a/Lib/idlelib/idle_test/test_grep.py b/Lib/idlelib/idle_test/test_grep.py index d67dba76911fcf..94f217effb656e 100644 --- a/Lib/idlelib/idle_test/test_grep.py +++ b/Lib/idlelib/idle_test/test_grep.py @@ -37,6 +37,25 @@ def close(self): # gui method _grep = Dummy_grep() +class DefaultGlobTest(unittest.TestCase): + + def test_no_path(self): + # gh-80504: an unsaved editor or the Shell has no path, so the + # pattern uses the current directory, not just '*.py'. + self.assertEqual(grep.default_glob(''), + os.path.join(os.getcwd(), '*.py')) + + def test_full_path(self): + path = os.path.join(os.path.abspath(os.sep), 'ab', 'foo.py') + self.assertEqual(grep.default_glob(path), + os.path.join(os.path.dirname(path), '*.py')) + + def test_other_extension(self): + path = os.path.join(os.path.abspath(os.sep), 'ab', 'foo.txt') + self.assertEqual(grep.default_glob(path), + os.path.join(os.path.dirname(path), '*.txt')) + + class FindfilesTest(unittest.TestCase): @classmethod diff --git a/Lib/idlelib/idle_test/test_multicall.py b/Lib/idlelib/idle_test/test_multicall.py index 7d73761cfdfee8..fcce2030579f4b 100644 --- a/Lib/idlelib/idle_test/test_multicall.py +++ b/Lib/idlelib/idle_test/test_multicall.py @@ -43,6 +43,22 @@ def test_yview(self): mctext = self.mc(self.root) self.assertIs(mctext.yview.__func__, Text.yview) + def test_event_delete_unbound_sequence(self): + # gh-89360: deleting a sequence that was not added to a virtual + # event is ignored instead of raising ValueError. + mctext = self.mc(self.root) + mctext.event_add('<>', '') + info = mctext.event_info('<>') + self.assertEqual(len(info), 1) + + # A different sequence, never added: a no-op, not an error. + mctext.event_delete('<>', '') + self.assertEqual(mctext.event_info('<>'), info) + + # The added sequence can still be deleted normally. + mctext.event_delete('<>', '') + self.assertNotIn(info[0], mctext.event_info('<>')) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/Lib/idlelib/idle_test/test_pyshell.py b/Lib/idlelib/idle_test/test_pyshell.py index 51f7691eefe9d5..dec81bdbbccd67 100644 --- a/Lib/idlelib/idle_test/test_pyshell.py +++ b/Lib/idlelib/idle_test/test_pyshell.py @@ -2,6 +2,7 @@ # Plus coverage of test_warning. Was 20% with test_openshell. from idlelib import pyshell +import os import unittest from test.support import requires from tkinter import Tk @@ -28,6 +29,14 @@ def test_restart_line_narrow(self): self.assertEqual(pyshell.restart_line(width, ''), expect) self.assertEqual(pyshell.restart_line(taglen+2, ''), expect+' =') + def test_fix_user_path(self): + # gh-134300: the idlelib directory is removed, other entries kept. + eq = self.assertEqual + idlelib_dir = os.path.dirname(os.path.abspath(pyshell.__file__)) + eq(pyshell.fix_user_path(['', '/a', idlelib_dir, '/b']), ['', '/a', '/b']) + eq(pyshell.fix_user_path(['/a', '/b']), ['/a', '/b']) + eq(pyshell.fix_user_path([idlelib_dir]), []) + class PyShellFileListTest(unittest.TestCase): diff --git a/Lib/idlelib/idle_test/test_replace.py b/Lib/idlelib/idle_test/test_replace.py index 6c07389b29ad45..5342a3856ef65d 100644 --- a/Lib/idlelib/idle_test/test_replace.py +++ b/Lib/idlelib/idle_test/test_replace.py @@ -246,32 +246,108 @@ def test_replace_backwards(self): equal(text.get('1.2', '1.5'), 'was') def test_replace_all(self): + # The default mode, forward with wrap around, replaces every + # match, both below and above the current position. + equal = self.assertEqual text = self.text pv = self.engine.patvar rv = self.dialog.replvar replace_all = self.dialog.replace_all - text.insert('insert', '\n') - text.insert('insert', text.get('1.0', 'end')*100) - pv.set('is') - rv.set('was') + text.delete('1.0', 'end') + text.insert('1.0', 'a\na\na\n') + text.mark_set('insert', '2.1') + pv.set('a') + rv.set('b') replace_all() - self.assertNotIn('is', text.get('1.0', 'end')) + equal(text.get('1.0', '3.end'), 'b\nb\nb') # Wrapped around. + # An empty regular expression is reported as an error. self.engine.revar.set(True) pv.set('') replace_all() self.assertIn('error', showerror.title) self.assertIn('Empty', showerror.message) + # An invalid replacement expression is reported as an error, + # and nothing is replaced. + text.delete('1.0', 'end') + text.insert('1.0', 'asT') pv.set('[s][T]') rv.set('\\') replace_all() + self.assertIn('error', showerror.title) + self.assertIn('Invalid Replace Expression', showerror.message) + equal(text.get('1.0', '1.end'), 'asT') + # A pattern that is not present replaces nothing. self.engine.revar.set(False) + text.delete('1.0', 'end') + text.insert('1.0', 'unchanged') pv.set('text which is not present') rv.set('foobar') replace_all() + equal(text.get('1.0', '1.end'), 'unchanged') + + def test_replace_all_backwards_no_wrap(self): + # gh-71956: 'up' without wrap replaces all matches from the start + # of the text down to the current position, not just one up. + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_all = self.dialog.replace_all + self.engine.backvar.set(True) + self.engine.wrapvar.set(False) + + text.delete('1.0', 'end') + text.insert('1.0', 'a\na\na\n') + text.mark_set('insert', '2.1') + pv.set('a') + rv.set('b') + replace_all() + equal(text.get('1.0', '1.end'), 'b') # Above the cursor. + equal(text.get('2.0', '2.end'), 'b') # At the cursor. + equal(text.get('3.0', '3.end'), 'a') # Below the cursor, untouched. + + def test_replace_all_forwards_no_wrap(self): + # 'down' without wrap replaces all matches from the current + # position to the end of the text, and none before it. + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_all = self.dialog.replace_all + self.engine.backvar.set(False) + self.engine.wrapvar.set(False) + + text.delete('1.0', 'end') + text.insert('1.0', 'a\na\na\n') + text.mark_set('insert', '2.1') + pv.set('a') + rv.set('b') + replace_all() + equal(text.get('1.0', '1.end'), 'a') # Before the cursor, untouched. + equal(text.get('2.0', '2.end'), 'a') # Before the cursor, untouched. + equal(text.get('3.0', '3.end'), 'b') # After the cursor. + + def test_replace_all_backwards_wrap(self): + # With wrap around, an 'up' search also replaces every match. + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_all = self.dialog.replace_all + self.engine.backvar.set(True) + self.engine.wrapvar.set(True) + + text.delete('1.0', 'end') + text.insert('1.0', 'a\na\na\n') + text.mark_set('insert', '2.1') + pv.set('a') + rv.set('b') + replace_all() + equal(text.get('1.0', '3.end'), 'b\nb\nb') def test_default_command(self): text = self.text diff --git a/Lib/idlelib/idle_test/test_stackviewer.py b/Lib/idlelib/idle_test/test_stackviewer.py index 2434d38e4ffe83..341f158031d5f4 100644 --- a/Lib/idlelib/idle_test/test_stackviewer.py +++ b/Lib/idlelib/idle_test/test_stackviewer.py @@ -35,6 +35,8 @@ def test_init(self): isi(stackviewer.sc, ScrolledCanvas) isi(stackviewer.item, stackviewer.StackTreeItem) isi(stackviewer.node, TreeNode) + top = stackviewer.sc.frame.winfo_toplevel() + self.assertEqual(top.winfo_class(), 'Idle') if __name__ == '__main__': diff --git a/Lib/idlelib/idle_test/test_window.py b/Lib/idlelib/idle_test/test_window.py index 9b56d321a407d6..d8a77817205dc3 100644 --- a/Lib/idlelib/idle_test/test_window.py +++ b/Lib/idlelib/idle_test/test_window.py @@ -39,6 +39,11 @@ def test_init(self): win = window.ListedToplevel(self.root) self.assertIn(win, window.registry) self.assertEqual(win.focused_widget, win) + self.assertEqual(win.winfo_class(), 'Idle') + + def test_init_class_override(self): + win = window.ListedToplevel(self.root, class_='Other') + self.assertEqual(win.winfo_class(), 'Other') if __name__ == '__main__': diff --git a/Lib/idlelib/multicall.py b/Lib/idlelib/multicall.py index 41f81813113062..95f4fee7fbe740 100644 --- a/Lib/idlelib/multicall.py +++ b/Lib/idlelib/multicall.py @@ -386,10 +386,11 @@ def event_delete(self, virtual, *sequences): if triplet is None: #print("Tkinter event_delete: %s" % seq, file=sys.__stderr__) widget.event_delete(self, virtual, seq) - else: + elif triplet in triplets: if func is not None: self.__binders[triplet[1]].unbind(triplet, func) triplets.remove(triplet) + # Else the sequence is not bound; ignore it (gh-89360). def event_info(self, virtual=None): if virtual is None or virtual not in self.__eventinfo: diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py index 439f98170292b0..ef3d014d936ce8 100755 --- a/Lib/idlelib/pyshell.py +++ b/Lib/idlelib/pyshell.py @@ -408,6 +408,17 @@ def restart_line(width, filename): # See bpo-38141. return tag[:-2] # Remove ' ='. +def fix_user_path(path): + """Return path without the idlelib directory (gh-134300). + + That directory is on sys.path when idle.py is run as a script. + Otherwise user code could import idlelib submodules as top-level + modules, such as "import help". + """ + idlelib_dir = os.path.dirname(os.path.abspath(__file__)) + return [p for p in path if p != idlelib_dir] + + class ModifiedInterpreter(InteractiveInterpreter): def __init__(self, tkconsole): @@ -568,6 +579,7 @@ def transfer_path(self, with_cwd=False): path.extend(sys.path) else: path = sys.path + path = fix_user_path(path) # gh-134300 self.runcommand("""if 1: import sys as _sys @@ -644,7 +656,7 @@ def remote_stack_viewer(self): return item = debugobj_r.StubObjectTreeItem(self.rpcclt, oid) from idlelib.tree import ScrolledCanvas, TreeNode - top = Toplevel(self.tkconsole.root) + top = Toplevel(self.tkconsole.root, class_='Idle') theme = idleConf.CurrentTheme() background = idleConf.GetHighlight(theme, 'normal')['background'] sc = ScrolledCanvas(top, bg=background, highlightthickness=0) diff --git a/Lib/idlelib/replace.py b/Lib/idlelib/replace.py index 3716d841568d30..93d4f1c0c6c573 100644 --- a/Lib/idlelib/replace.py +++ b/Lib/idlelib/replace.py @@ -122,12 +122,13 @@ def _replace_expand(self, m, repl): def replace_all(self, event=None): """Handle the Replace All button. - Search text for occurrences of the Find value and replace - each of them. The 'wrap around' value controls the start - point for searching. If wrap isn't set, then the searching - starts at the first occurrence after the current selection; - if wrap is set, the replacement starts at the first line. - The replacement is always done top-to-bottom in the text. + Search text for occurrences of the Find value and replace each + of them. The 'wrap around' and direction values control which + occurrences are replaced. With wrap around, every occurrence is + replaced. Without it, a forward search replaces occurrences from + the current position to the end of the text, and a backward search + replaces occurrences from the beginning of the text to the current + position. The replacement is always done top-to-bottom. """ prog = self.engine.getprog() if not prog: @@ -142,9 +143,18 @@ def replace_all(self, event=None): text.tag_remove("hit", "1.0", "end") line = res[0] col = res[1].start() + # For a backward search without wrap, replace top-to-bottom from + # the start of the text down to the first match at or above the + # current position (gh-71956). A mark tracks that stop point. + stop = None if self.engine.iswrap(): line = 1 col = 0 + elif self.engine.isback(): + stop = "replace_all_stop" + text.mark_set(stop, "%d.%d" % (line, res[1].end())) + line = 1 + col = 0 ok = True first = last = None # XXX ought to replace circular instead of top-to-bottom when wrapping @@ -152,12 +162,13 @@ def replace_all(self, event=None): while res := self.engine.search_forward( text, prog, line, col, wrap=False, ok=ok): line, m = res - chars = text.get("%d.0" % line, "%d.0" % (line+1)) + i, j = m.span() + if stop is not None and text.compare("%d.%d" % (line, i), ">=", stop): + break orig = m.group() new = self._replace_expand(m, repl) if new is None: break - i, j = m.span() first = "%d.%d" % (line, i) last = "%d.%d" % (line, j) if new == orig: @@ -170,6 +181,8 @@ def replace_all(self, event=None): text.insert(first, new, self.insert_tags) col = i + len(new) ok = False + if stop is not None: + text.mark_unset(stop) text.undo_block_stop() if first and last: self.show_hit(first, last) diff --git a/Lib/idlelib/stackviewer.py b/Lib/idlelib/stackviewer.py index 95042d4debdc03..268e2f1daeb98c 100644 --- a/Lib/idlelib/stackviewer.py +++ b/Lib/idlelib/stackviewer.py @@ -11,7 +11,7 @@ def StackBrowser(root, exc, flist=None, top=None): global sc, item, node # For testing. if top is None: - top = tk.Toplevel(root) + top = tk.Toplevel(root, class_='Idle') sc = ScrolledCanvas(top, bg="white", highlightthickness=0) sc.frame.pack(expand=1, fill="both") item = StackTreeItem(exc, flist) diff --git a/Lib/idlelib/util.py b/Lib/idlelib/util.py index bf88c905e1d177..bdf02892de68ea 100644 --- a/Lib/idlelib/util.py +++ b/Lib/idlelib/util.py @@ -24,7 +24,7 @@ def fix_scaling(root): # Called in filelist _test, pyshell, and run. """Scale fonts on HiDPI displays, once per process.""" import tkinter.font - scaling = float(root.tk.call('tk', 'scaling')) + scaling = root.tk_scaling() if scaling > 1.4: for name in tkinter.font.names(root): font = tkinter.font.Font(root=root, name=name, exists=True) diff --git a/Lib/idlelib/window.py b/Lib/idlelib/window.py index 460d5b67948dde..a41d69927dcdd5 100644 --- a/Lib/idlelib/window.py +++ b/Lib/idlelib/window.py @@ -61,6 +61,10 @@ def call_callbacks(self): class ListedToplevel(Toplevel): def __init__(self, master, **kw): + # Set the WM_CLASS property so that X11 window managers group and + # label IDLE's windows under 'Idle' instead of the default + # 'Toplevel' (gh-66331). It matches the class name passed to Tk(). + kw.setdefault('class_', 'Idle') Toplevel.__init__(self, master, kw) registry.add(self) self.focused_widget = self diff --git a/Lib/test/test_tkinter/widget_tests.py b/Lib/test/test_tkinter/widget_tests.py index 014906cba2902c..05e60f7ee118e3 100644 --- a/Lib/test/test_tkinter/widget_tests.py +++ b/Lib/test/test_tkinter/widget_tests.py @@ -34,7 +34,7 @@ def scaling(self): try: return self._scaling except AttributeError: - self._scaling = float(self.root.call('tk', 'scaling')) + self._scaling = self.root.tk_scaling() return self._scaling def _str(self, value): diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index f16611b29a2658..60aaa5cd548cbf 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -10,6 +10,7 @@ import unittest from collections import deque from contextlib import _GeneratorContextManager, contextmanager, nullcontext +from _testinternalcapi import SelfInterruptingContextManager def do_with(obj): @@ -850,5 +851,21 @@ def exit_raises(): expected) +class InterruptDuringEnter(unittest.TestCase): + + def test_exit_called_after_interrupt(self): + cm = SelfInterruptingContextManager() + self.assertFalse(cm.within()) + try: + with cm: + self.assertTrue(cm.within()) + except KeyboardInterrupt: + self.assertFalse(cm.within()) + return + except: + self.fail("Wrong exception raised") + self.fail("No exception raised") + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst new file mode 100644 index 00000000000000..95f93338beb912 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-04-12-53-10.gh-issue-148874.r121cG.rst @@ -0,0 +1,3 @@ +Ignore interrupts immediately after calling the ``__enter__`` method of a +context menager in a ``with`` statement. This ensures that the ``__exit__`` +method is always called in a ``with`` statement. diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-00-00-00.gh-issue-66331.wMcLaS.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-00-00-00.gh-issue-66331.wMcLaS.rst new file mode 100644 index 00000000000000..e00e7c7d8fdc65 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-00-00-00.gh-issue-66331.wMcLaS.rst @@ -0,0 +1,3 @@ +Set the ``WM_CLASS`` window property of IDLE's windows to ``Idle`` on X11, +so that window managers group and label them correctly instead of using the +default ``Toplevel``. diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-12-00-00.gh-issue-71956.rPlAlL.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-12-00-00.gh-issue-71956.rPlAlL.rst new file mode 100644 index 00000000000000..1a92ed4cd389cf --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-12-00-00.gh-issue-71956.rPlAlL.rst @@ -0,0 +1,3 @@ +Fix Replace All in the IDLE editor's Replace dialog when the search +direction is "Up" and "Wrap around" is off: it now replaces all matches +above the current position instead of only the first one. diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-14-00-00.gh-issue-89360.mUlTiC.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-14-00-00.gh-issue-89360.mUlTiC.rst new file mode 100644 index 00000000000000..9e3023f8228233 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-14-00-00.gh-issue-89360.mUlTiC.rst @@ -0,0 +1,3 @@ +Fix a rare crash in the IDLE editor when the completion window is closed: +deleting a key binding for a sequence that is not bound to the virtual +event is now ignored instead of raising a ``ValueError``. diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-15-00-00.gh-issue-134300.iDlPaT.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-15-00-00.gh-issue-134300.iDlPaT.rst new file mode 100644 index 00000000000000..791acbbed62aaf --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-15-00-00.gh-issue-134300.iDlPaT.rst @@ -0,0 +1,3 @@ +Do not add the ``idlelib`` directory to the path of the IDLE user process. +User code run in IDLE can no longer import ``idlelib`` submodules as +top-level modules, such as ``import help``. diff --git a/Misc/NEWS.d/next/IDLE/2026-07-01-16-00-00.gh-issue-80504.gReP.rst b/Misc/NEWS.d/next/IDLE/2026-07-01-16-00-00.gh-issue-80504.gReP.rst new file mode 100644 index 00000000000000..93adb33202a41c --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-07-01-16-00-00.gh-issue-80504.gReP.rst @@ -0,0 +1,3 @@ +The "In files:" field of IDLE's Find in Files dialog now always contains a +full directory path, even for an unsaved editor or the Shell. This shows +in the grep output which directory was searched. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index f6ff7820821ce1..ea3ad2b81c2866 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -3207,6 +3207,66 @@ test_thread_state_ensure_from_view_interp_switch(PyObject *self, PyObject *unuse Py_RETURN_NONE; } +/* Self interrupting context manager */ + +typedef struct { + PyObject_HEAD + int within; +} SelfInterruptingContextManagerObject; + +static PyObject * +new_self_interrupting(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + SelfInterruptingContextManagerObject *self = + (SelfInterruptingContextManagerObject *)type->tp_alloc(type, 0); + if (self != NULL) { + self->within = 0; + } + return (PyObject *)self; +} + +static PyObject * +self_interrupting_enter(PyObject *op, PyObject *Py_UNUSED(dummy)) +{ + ((SelfInterruptingContextManagerObject *)op)->within = 1; + PyThreadState *tstate = PyThreadState_Get(); + PyObject *ki = Py_NewRef(PyExc_KeyboardInterrupt); + PyObject *old_exc = _Py_atomic_exchange_ptr(&tstate->async_exc, ki); + _Py_set_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT); + Py_XDECREF(old_exc); + + return Py_NewRef(op); +} + +static PyObject * +self_interrupting_within(PyObject *op, PyObject *Py_UNUSED(dummy)) +{ + return PyBool_FromLong(((SelfInterruptingContextManagerObject *)op)->within); +} + +static PyObject * +self_interrupting_exit(PyObject *op, PyObject *Py_UNUSED(args)) { + ((SelfInterruptingContextManagerObject *)op)->within = 0; + Py_RETURN_NONE; +} + +static PyMethodDef self_interrupting_methods[] = { + {"__enter__", self_interrupting_enter, METH_NOARGS, NULL}, + {"within", self_interrupting_within, METH_NOARGS, NULL}, + {"__exit__", self_interrupting_exit, METH_VARARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + +static PyTypeObject SelfInterruptingContextManager_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "_testcapi.SelfInterruptingContextManager", + sizeof(SelfInterruptingContextManagerObject), + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE, + .tp_new = new_self_interrupting, + .tp_methods = self_interrupting_methods, +}; + + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_eval_frame_stats", get_eval_frame_stats, METH_NOARGS, NULL}, @@ -3429,6 +3489,11 @@ module_exec(PyObject *module) } #endif + if (PyType_Ready(&SelfInterruptingContextManager_Type) < 0) { + return 1; + } + PyModule_AddObject(module, "SelfInterruptingContextManager", (PyObject *)&SelfInterruptingContextManager_Type); + return 0; } diff --git a/Modules/_testinternalcapi/test_cases.c.h b/Modules/_testinternalcapi/test_cases.c.h index f36c8192ff2662..5f2b1ae5d978aa 100644 --- a/Modules/_testinternalcapi/test_cases.c.h +++ b/Modules/_testinternalcapi/test_cases.c.h @@ -1927,7 +1927,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2453,7 +2453,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2546,7 +2546,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2635,7 +2635,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2746,7 +2746,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2860,7 +2860,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -3191,7 +3191,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -3703,7 +3703,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4122,7 +4122,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4242,7 +4242,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4364,7 +4364,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4499,7 +4499,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4576,7 +4576,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4880,7 +4880,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4958,7 +4958,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -7221,7 +7221,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -7403,7 +7403,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 753530b0dabff3..8ac397081e2738 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -161,7 +161,7 @@ dummy_func( } replaced op(_CHECK_PERIODIC_AT_END, (--)) { - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); ERROR_IF(err != 0); } diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index b13884bf8214d4..f19adfa0cfcfc1 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -528,6 +528,22 @@ check_periodics(PyThreadState *tstate) { return 0; } +static inline int +check_periodics_at_end(PyThreadState *tstate, _PyInterpreterFrame *frame) { + _Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY(); + QSBR_QUIESCENT_STATE(tstate); + if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) { + // Do not handle pending interrupts if the previous instruction was LOAD_SPECIAL + // This may also not handle interrupts if a cache looks like LOAD_SPECIAL, + // but this is benign as we won't skip periodic checks indefinitely. + if (frame->instr_ptr[-1].op.code == LOAD_SPECIAL) { + return 0; + } + return _Py_HandlePending(tstate); + } + return 0; +} + // Mark the generator as executing. Returns true if the state was changed, // false if it was already executing or finished. static inline bool diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 88678f14a99585..cc95179ccaab17 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -1927,7 +1927,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2453,7 +2453,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2546,7 +2546,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2635,7 +2635,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2746,7 +2746,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -2860,7 +2860,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -3191,7 +3191,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -3703,7 +3703,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4122,7 +4122,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4242,7 +4242,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4364,7 +4364,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4499,7 +4499,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4576,7 +4576,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4880,7 +4880,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -4958,7 +4958,7 @@ { assert(stack_pointer == _PyFrame_GetStackPointer(frame)); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -7221,7 +7221,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); @@ -7403,7 +7403,7 @@ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__); _PyFrame_SetStackPointer(frame, stack_pointer); _PyFrame_StackPointerValidate(frame); - int err = check_periodics(tstate); + int err = check_periodics_at_end(tstate, frame); _PyFrame_StackPointerInvalidate(frame); if (err != 0) { JUMP_TO_LABEL(error); diff --git a/Python/optimizer.c b/Python/optimizer.c index e95e4b5e24b2c5..c9f6ebdb62f07b 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -956,10 +956,15 @@ _PyJit_translate_single_bytecode_to_trace( case OPARG_REPLACED: uop = _PyUOp_Replacements[uop]; assert(uop != 0); - uint32_t next_inst = target + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; if (uop == _TIER2_RESUME_CHECK) { - target = next_inst; + if (this_instr[-1].op.code == LOAD_SPECIAL) { + // Don't check eval breaker immediately after LOAD_SPECIAL + uop = _NOP; + } + else { + target = next_inst; + } } else { int extended_arg = orig_oparg > 255; diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index bf08e5568205e7..6e18593ad69857 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -577,6 +577,7 @@ Modules/_testimportmultiple.c - _testimportmultiple - Modules/_testinternalcapi.c - pending_identify_result - Modules/_testinternalcapi.c - Test_EvalFrame_Resumes - Modules/_testinternalcapi.c - Test_EvalFrame_Loads - +Modules/_testinternalcapi.c - SelfInterruptingContextManager_Type - Modules/_testinternalcapi/interpreter.c - Test_EvalFrame_Resumes - Modules/_testinternalcapi/interpreter.c - Test_EvalFrame_Loads - Modules/_testlimitedcapi/slots.c - TestMethods -