Compare commits

...

26 Commits

Author SHA1 Message Date
David Peter
e935bc5578 [ty] enum.Flag 2025-07-29 16:52:42 +02:00
Alex Waygood
81867ea7ce [ty] Discard Definitions when normalizing Signatures (#19615) 2025-07-29 14:37:47 +01:00
Brent Westbrook
a54061e757 [ty] Fix empty spans following a line terminator and unprintable character spans in diagnostics (#19535)
## Summary

This was previously the last commit in #19415, split out to make it
easier to review. This applies the fixes from c9b99e4, 5021f32, and
2922490cb8 to the new rendering code in `ruff_db`. I initially intended
only to fix the empty span after a line terminator (as you can see in
the branch name), but the two fixes were tied pretty closely together,
and my initial fix for the empty spans needed a big change after trying
to handle unprintable characters too. I can still split this up if it
would help with review. I would just start with the unprintable
characters first.

The implementation here is essentially copy-pasted from
`ruff_linter::message::text.rs`, with the `SourceCode` struct renamed to
`EscapedSourceCode` since there's already a `SourceCode` in scope in
`render.rs`. It's also updated slightly to account for the multiple
annotations for a single snippet. The original implementation used some
types from the `line_width` module from `ruff_linter`. I copied over
heavily stripped-down versions of these instead of trying to import
them. We could inline the remaining code entirely, if we want, but I
thought it was nice enough to keep.

I also moved over `ceil_char_boundary`, which is unchanged except to
make it a free function taking a `&str` instead of a `Locator` method.
All of this code could be deleted from `ruff_linter` if we also move
over the `grouped` output format, which will be the last user after
#19415.

## Test Plan

I added new tests in `ruff_linter` that call into the new rendering code
to snapshot the diagnostics for the affected cases. These are copies of
existing snapshots in Ruff, so it's helpful to compare them. These are a
bit noisy because of the other rendering differences in the header, but
all of the `^^^` indicators should be the same.

<details><summary>`empty_span_after_line_terminator` diff</summary>

```diff
diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__empty_span_after_line_terminator.snap
index 5ade4346e0..6df75c16f0 100644
--- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap
+++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__empty_span_after_line_terminator.snap
@@ -1,17 +1,20 @@
 ---
-source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
+source: crates/ruff_linter/src/message/text.rs
+expression: value.to_string()
 ---
-E11.py:9:1: E112 Expected an indented block
+error[no-indented-block]: Expected an indented block
+  --> E11.py:9:1
    |
  7 | #: E112
  8 | if False:
  9 | print()
-   | ^ E112
+   | ^
 10 | #: E113
 11 | print()
    |
 
-E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
+error[invalid-syntax]: SyntaxError: Expected an indented block after `if` statement
+  --> E11.py:9:1
    |
  7 | #: E112
  8 | if False:
@@ -21,7 +24,8 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
 11 | print()
    |
 
-E11.py:12:1: SyntaxError: Unexpected indentation
+error[invalid-syntax]: SyntaxError: Unexpected indentation
+  --> E11.py:12:1
    |
 10 | #: E113
 11 | print()
@@ -31,7 +35,8 @@ E11.py:12:1: SyntaxError: Unexpected indentation
 14 | mimetype = 'application/x-directory'
    |
 
-E11.py:14:1: SyntaxError: Expected a statement
+error[invalid-syntax]: SyntaxError: Expected a statement
+  --> E11.py:14:1
    |
 12 |     print()
 13 | #: E114 E116
@@ -41,17 +46,19 @@ E11.py:14:1: SyntaxError: Expected a statement
 16 | create_date = False
    |
 
-E11.py:45:1: E112 Expected an indented block
+error[no-indented-block]: Expected an indented block
+  --> E11.py:45:1
    |
 43 | #: E112
 44 | if False:  #
 45 | print()
-   | ^ E112
+   | ^
 46 | #:
 47 | if False:
    |
 
-E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
+error[invalid-syntax]: SyntaxError: Expected an indented block after `if` statement
+  --> E11.py:45:1
    |
 43 | #: E112
 44 | if False:  #
```

</details>

<details><summary>`unprintable_characters` diff</summary>

```diff
diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__unprintable_characters.snap
index 52cfdf9cce..fcfa1ac9f1 100644
--- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap
+++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__unprintable_characters.snap
@@ -1,161 +1,115 @@
 ---
-source: crates/ruff_linter/src/rules/pylint/mod.rs
+source: crates/ruff_linter/src/message/text.rs
+expression: value.to_string()
 ---
-invalid_characters.py:24:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:24:12
    |
 22 | cr_ok = f'\\r'
 23 |
 24 | sub = 'sub '
-   |            ^ PLE2512
+   |            ^
 25 | sub = f'sub '
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-ℹ Safe fix
-21 21 | cr_ok = '\\r'
-22 22 | cr_ok = f'\\r'
-23 23 | 
-24    |-sub = 'sub '
-   24 |+sub = 'sub \x1A'
-25 25 | sub = f'sub '
-26 26 | 
-27 27 | sub_ok = '\x1a'
-
-invalid_characters.py:25:13: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:25:13
    |
 24 | sub = 'sub '
 25 | sub = f'sub '
-   |             ^ PLE2512
+   |             ^
 26 |
 27 | sub_ok = '\x1a'
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-22 22 | cr_ok = f'\\r'
-23 23 | 
-24 24 | sub = 'sub '
-25    |-sub = f'sub '
-   25 |+sub = f'sub \x1A'
-26 26 | 
-27 27 | sub_ok = '\x1a'
-28 28 | sub_ok = f'\x1a'
+help: Replace with escape sequence
 
-invalid_characters.py:55:25: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:55:25
    |
 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
 54 |
 55 | nested_fstrings = f'␈{f'{f'␛'}'}'
-   |                         ^ PLE2512
+   |                         ^
 56 |
 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
-53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
-54 54 | 
-55    |-nested_fstrings = f'␈{f'{f'␛'}'}'
-   55 |+nested_fstrings = f'␈{f'\x1A{f'␛'}'}'
-56 56 | 
-57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
-58 58 | x = f"""}}ab"""
+help: Replace with escape sequence
 
-invalid_characters.py:58:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:58:12
    |
 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
 58 | x = f"""}}ab"""
-   |            ^ PLE2512
+   |            ^
 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256
 60 | x = f"""}}a␛b"""
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-ℹ Safe fix
-55 55 | nested_fstrings = f'␈{f'{f'␛'}'}'
-56 56 | 
-57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
-58    |-x = f"""}}ab"""
-   58 |+x = f"""}}a\x1Ab"""
-59 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256
-60 60 | x = f"""}}a␛b"""
-61 61 | 
-
-invalid_characters.py:64:12: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:64:12
    |
 63 | # https://github.com/astral-sh/ruff/issues/13294
 64 | print(r"""␈␛�​
-   |            ^ PLE2512
+   |            ^
 65 | """)
 66 | print(fr"""␈␛�​
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:66:13: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:66:13
    |
 64 | print(r"""␈␛�​
 65 | """)
 66 | print(fr"""␈␛�​
-   |             ^ PLE2512
+   |             ^
 67 | """)
 68 | print(Rf"""␈␛�​
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:68:13: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:68:13
    |
 66 | print(fr"""␈␛�​
 67 | """)
 68 | print(Rf"""␈␛�​
-   |             ^ PLE2512
+   |             ^
 69 | """)
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:73:9: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:73:9
    |
 71 | # https://github.com/astral-sh/ruff/issues/18815
 72 | b = "\␈"
 73 | sub = "\"
-   |         ^ PLE2512
+   |         ^
 74 | esc = "\␛"
 75 | zwsp = "\​"
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:80:25: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:80:25
    |
 78 | # tstrings
 79 | esc = t'esc esc ␛'
 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
-   |                         ^ PLE2512
+   |                         ^
 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-77 77 | 
-78 78 | # tstrings
-79 79 | esc = t'esc esc ␛'
-80    |-nested_tstrings = t'␈{t'{t'␛'}'}'
-   80 |+nested_tstrings = t'␈{t'\x1A{t'␛'}'}'
-81 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
-82 82 | 
+help: Replace with escape sequence
 
-invalid_characters.py:81:26: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:81:26
    |
 79 | esc = t'esc esc ␛'
 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
-   |                          ^ PLE2512
+   |                          ^
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-78 78 | # tstrings
-79 79 | esc = t'esc esc ␛'
-80 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
-81    |-nested_ftstrings = t'␈{f'{t'␛'}'}'
-   81 |+nested_ftstrings = t'␈{f'\x1A{t'␛'}'}'
-82 82 |
+help: Replace with escape sequence
```

</details>
2025-07-29 08:25:58 -04:00
Brent Westbrook
19569bf838 Add LinterContext::settings to avoid passing separate settings (#19608)
Summary
--

I noticed while reviewing #19390 that in `check_tokens` we were still
passing
around an extra `LinterSettings`, despite all of the same functions also
receiving a `LintContext` with its own settings.

This PR adds the `LintContext::settings` method and calls that instead
of using
the separate `LinterSettings`.

Test Plan
--

Existing tests
2025-07-29 08:13:22 -04:00
Charlie Marsh
e0f4f25d28 Support .pyi files in ruff analyze graph (#19611)
## Summary

We now return both the `.pyi` and `.py` files. Previously, we only
returned the `.pyi` file.
2025-07-28 22:00:27 -04:00
github-actions[bot]
c6a123290d [ty] Sync vendored typeshed stubs (#19607)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-28 22:06:33 +00:00
Alex Waygood
d4f64cd474 [ty] Bump docstring-adder pin (#19606) 2025-07-28 22:59:56 +01:00
Igor Drokin
e4f64480da [perflint] Ignore rule if target is global or nonlocal (PERF401) (#19539)
## Summary

Resolves #19531

I've implemented a check to determine whether the for_stmt target is
declared as global or nonlocal. I believe we should skip the rule in all
such cases, since variables declared this way are intended for use
outside the loop scope, making value changes expected behavior.

## Test Plan

Added two test cases for global and nonlocal variable to snapshot.
2025-07-28 17:03:22 -04:00
Micha Reiser
4016aff057 Add license classifier back to pyproject.toml (#19599)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-28 20:58:16 +01:00
UnboundVariable
24134837f3 [ty] Add stub mapping support to signature help (#19570)
This PR improves the "signature help" language server feature in two
ways:
1. It adds support for the recently-introduced "stub mapper" which maps
symbol declarations within stubs to their implementation counterparts.
This allows the signature help to display docstrings from the original
implementation.
2. It incorporates a more robust fix to a bug that was addressed in a
[previous PR](https://github.com/astral-sh/ruff/pull/19542). It also
adds more comprehensive tests to cover this case.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-28 10:57:18 -07:00
Douglas Creager
130d4e1135 [ty] Don't panic with argument that doesn't actually implement Iterable (#19602)
This eliminates the panic reported in
https://github.com/astral-sh/ty/issues/909, though it doesn't address
the underlying cause, which is that we aren't yet checking the types of
the fields of a protocol when checking whether a class implements the
protocol. And in particular, if a class explictly opts out of iteration
via

```py
class NotIterable:
    __iter__ = None
```

we currently treat that as "having an `__iter__`" member, and therefore
implementing `Iterable`.

Note that the assumption that was in the comment before is still
correct: call binding will have already checked that the argument
satisfies `Iterable`, and so it shouldn't be an error to iterate over
said argument. But arguably, the new logic in this PR is a better way to
discharge that assumption — instead of panicking if we happen to be
wrong, fall back on an unknown iteration result.
2025-07-28 12:09:54 -04:00
Dan Parizher
e63dfa3d18 [flake8-commas] Add support for trailing comma checks in type parameter lists (COM812,COM819) (#19390)
## Summary

Fixes #18844

I'm not too sure if the solution is as simple as the way I implemented
it, but I'm curious to see if we are covering all cases correctly here.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-28 10:53:04 -04:00
Junhson Jean-Baptiste
6d0f3ef3a5 [pylint] Implement auto-fix for missing-maxsplit-arg (PLC0207) (#19387)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary
As a follow-up to #18949 (suggested
[here](https://github.com/astral-sh/ruff/pull/18949#pullrequestreview-2998417889)),
this PR implements auto-fix logic for `PLC0207`.

## Test Plan

<!-- How was it tested? -->

Existing tests pass, with updates to the snapshot so that it expects the
new output that comes along with the auto-fix.
2025-07-28 10:45:26 -04:00
Dan Parizher
201b079084 [refurb] Mark int and bool cases for Decimal.from_float as safe fixes in FURB164 tests (#19468)
## Summary

Fixes #19460

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-28 14:21:38 +00:00
David Peter
2680f2ed81 [ty] Minor: test isolation (#19597)
## Summary

Split the "Generator functions" tests into two parts. The first part
(synchronous) refers to a function called `i` from a function `i2`. But
`i` is later redeclared in the asynchronous part, which was probably not
intended.
2025-07-28 15:52:59 +02:00
Micha Reiser
afdfa042f3 [ty] Remove AssertUnwindSafe from BackgroundRequestHandler api (#19598) 2025-07-28 13:28:09 +00:00
Micha Reiser
8c0743df97 [ty] Fix "peek definition" in playground (#19592) 2025-07-28 09:13:00 +01:00
Dimitri Papadopoulos Orfanos
13634ff433 Use PEP 639 license information for Ruff itself instead of classifier (#19499)
## Summary

Declare licenses using only these two fields, as per PEP 639:
* `license`: SPDX license expression consisting of one or more license
identifiers
* `license-files`: list of license file glob patterns

Supported by maturin ≥ 1.9.0:
https://www.maturin.rs/changelog.html

## Test Plan

N/A
2025-07-28 09:43:50 +02:00
renovate[bot]
7a541f597f Update NPM Development dependencies (#19588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 07:55:22 +01:00
renovate[bot]
2deb50f4e3 Update Rust crate get-size2 to 0.6.0 (#19590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 07:55:03 +01:00
renovate[bot]
85e22645aa Update pre-commit hook astral-sh/ruff-pre-commit to v0.12.5 (#19585)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.12.4` -> `v0.12.5` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.12.5`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.5)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.4...v0.12.5)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.5

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 08:48:14 +02:00
renovate[bot]
d3f6de8b0e Update cargo-bins/cargo-binstall action to v1.14.2 (#19583)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cargo-bins/cargo-binstall](https://redirect.github.com/cargo-bins/cargo-binstall)
| action | patch | `v1.14.1` -> `v1.14.2` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>cargo-bins/cargo-binstall (cargo-bins/cargo-binstall)</summary>

###
[`v1.14.2`](https://redirect.github.com/cargo-bins/cargo-binstall/releases/tag/v1.14.2)

[Compare
Source](https://redirect.github.com/cargo-bins/cargo-binstall/compare/v1.14.1...v1.14.2)

*Binstall is a tool to fetch and install Rust-based executables as
binaries. It aims to be a drop-in replacement for `cargo install` in
most cases. Install it today with `cargo install cargo-binstall`, from
the binaries below, or if you already have it, upgrade with `cargo
binstall cargo-binstall`.*

##### In this release:

- Upgrade dependencies

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 08:47:33 +02:00
renovate[bot]
9eb8174209 Update CodSpeedHQ/action action to v3.8.0 (#19587)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [CodSpeedHQ/action](https://redirect.github.com/CodSpeedHQ/action) |
action | minor | `v3.7.0` -> `v3.8.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>CodSpeedHQ/action (CodSpeedHQ/action)</summary>

###
[`v3.8.0`](https://redirect.github.com/CodSpeedHQ/action/releases/tag/v3.8.0)

[Compare
Source](https://redirect.github.com/CodSpeedHQ/action/compare/v3.7.0...v3.8.0)

##### What's Changed

##### <!-- 1 -->🐛 Bug Fixes

- Adjust offset for symbols of module loaded at preferred base by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias) in
[#&#8203;97](https://redirect.github.com/CodSpeedHQ/runner/pull/97)
- Run with --scope to allow perf to trace the benchmark process by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias)
- Run with bash to support complex scripts by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias)
- Execute pre- and post-bench scripts for non-perf walltime runner by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias) in
[#&#8203;96](https://redirect.github.com/CodSpeedHQ/runner/pull/96)

##### <!-- 2 -->🏗️ Refactor

- Process memory mappings in separate function by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias)

##### <!-- 7 -->⚙️ Internals

- Add debug logs for perf.map collection by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias)
- Add complex cmd and env tests by
[@&#8203;not-matthias](https://redirect.github.com/not-matthias)

**Full Changelog**:
https://github.com/CodSpeedHQ/action/compare/v3.7.0...v3.8.0
**Full Runner Changelog**:
https://github.com/CodSpeedHQ/runner/blob/main/CHANGELOG.md

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 08:47:05 +02:00
renovate[bot]
9c68616d91 Update Rust crate criterion to 0.7.0 (#19589)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [criterion](https://bheisler.github.io/criterion.rs/book/index.html)
([source](https://redirect.github.com/bheisler/criterion.rs)) |
workspace.dependencies | minor | `0.6.0` -> `0.7.0` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>bheisler/criterion.rs (criterion)</summary>

###
[`v0.7.0`](https://redirect.github.com/bheisler/criterion.rs/blob/HEAD/CHANGELOG.md#070---2025-07-25)

[Compare
Source](https://redirect.github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0)

- Bump version of criterion-plot to align dependencies.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 08:35:36 +02:00
renovate[bot]
9280c7e945 Update astral-sh/setup-uv action to v6.4.3 (#19586)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | minor | `v6.3.1` -> `v6.4.3` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>astral-sh/setup-uv (astral-sh/setup-uv)</summary>

###
[`v6.4.3`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.4.3):
🌈 fix relative paths starting with dots

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.4.2...v6.4.3)

#### 🐛 Bug fixes

- fix relative paths starting with dots
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;500](https://redirect.github.com/astral-sh/setup-uv/issues/500))

###
[`v6.4.2`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.4.2):
🌈 Interpret relative inputs as under working-directory

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.4.1...v6.4.2)

#### Changes

This release will interpret relative paths in inputs as relative
to the value of `working-directory` (default is `${{ github.workspace
}}`) .
This means the following configuration

```yaml
- uses: astral-sh/setup-uv@v6
   with:
     working-directory: /my/path
     cache-dependency-glob: uv.lock
```

will look for the `cache-dependency-glob` under `/my/path/uv.lock`

#### 🐛 Bug fixes

- interpret relative inputs as under working-directory
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;498](https://redirect.github.com/astral-sh/setup-uv/issues/498))

#### 🧰 Maintenance

- chore: update known versions for 0.8.1/0.8.2
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;497](https://redirect.github.com/astral-sh/setup-uv/issues/497))
- chore: update known versions for 0.8.0
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;491](https://redirect.github.com/astral-sh/setup-uv/issues/491))

###
[`v6.4.1`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.4.1):
🌈 Hotfix: Ignore deps starting with uv when finding uv version

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.4.0...v6.4.1)

##### Changes

Thank you [@&#8203;phpmypython](https://redirect.github.com/phpmypython)
for raising a PR to fix this issue!

##### 🐛 Bug fixes

- Ignore deps starting with uv when finding uv version
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;492](https://redirect.github.com/astral-sh/setup-uv/issues/492))

###
[`v6.4.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.4.0):
🌈 Add input `version-file`

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.3.1...v6.4.0)

##### Changes

You can now use the `version-file` input to specify a file that contains
the version of uv to install.
This can either be a `pyproject.toml` or `uv.toml` file which defines a
`required-version` or
uv defined as a dependency in `pyproject.toml` or `requirements.txt`.

```yaml
- name: Install uv based on the version defined in requirements.txt
  uses: astral-sh/setup-uv@v6
  with:
    version-file: "requirements.txt"
```

##### 🚀 Enhancements

- Add input version-file
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;486](https://redirect.github.com/astral-sh/setup-uv/issues/486))

##### 🧰 Maintenance

- chore: update known versions for 0.7.22
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;488](https://redirect.github.com/astral-sh/setup-uv/issues/488))
- Bump dependencies
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;487](https://redirect.github.com/astral-sh/setup-uv/issues/487))
- chore: update known versions for 0.7.21
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;483](https://redirect.github.com/astral-sh/setup-uv/issues/483))
- chore: update known versions for 0.7.20
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;480](https://redirect.github.com/astral-sh/setup-uv/issues/480))
- chore: update known versions for 0.7.19
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;475](https://redirect.github.com/astral-sh/setup-uv/issues/475))
- chore: update known versions for 0.7.18
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;473](https://redirect.github.com/astral-sh/setup-uv/issues/473))
- chore: update known versions for 0.7.17
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;468](https://redirect.github.com/astral-sh/setup-uv/issues/468))
- chore: update known versions for 0.7.16
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;466](https://redirect.github.com/astral-sh/setup-uv/issues/466))
- chore: update known versions for 0.7.15
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;463](https://redirect.github.com/astral-sh/setup-uv/issues/463))

##### 📚 Documentation

- Add FAQ on changed cache and cache upload behavior
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;477](https://redirect.github.com/astral-sh/setup-uv/issues/477))

##### ⬆️ Dependency updates

- Bump dependencies
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;487](https://redirect.github.com/astral-sh/setup-uv/issues/487))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS40MC4wIiwidXBkYXRlZEluVmVyIjoiNDEuNDAuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 08:33:44 +02:00
Dhruv Manilawala
e19145040f [ty] Add workflow to comment conformance tests diff (#19555)
## Summary

Follow-up to https://github.com/astral-sh/ruff/pull/19556, this PR adds
the workflow that computes the diagnostic diff which the workflow
introduced in the linked PR will add as a comment.

This workflow is similar to the [ty ecosystem-analyzer
workflow](d781a6ab3f/.github/workflows/ty-ecosystem-analyzer.yaml).

Closes: astral-sh/ty#212

## Test Plan

1. Initially there's no diff to show
2. This
[commit](d0db9937df)
comments out a rule which updates the comment with the diff
3. Later, that commit is reverted and the diff goes away

Use the comment history to look at the diff output where the order of
the history corresponds to the steps mentioned above in reverse order
i.e., the edit in the middle will contain the diff output:

<img width="1082" height="313" alt="Screenshot 2025-07-25 at 21 09 26"
src="https://github.com/user-attachments/assets/6aceb60c-1987-4b9a-9063-e3999844f035"
/>
2025-07-28 11:33:22 +05:30
262 changed files with 6170 additions and 1796 deletions

View File

@@ -429,7 +429,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -451,7 +451,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -652,7 +652,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -682,7 +682,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
- uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -722,7 +722,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
@@ -765,7 +765,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -897,7 +897,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show
@@ -911,7 +911,7 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
@@ -930,7 +930,7 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show
@@ -944,7 +944,7 @@ jobs:
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
with:
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -38,7 +38,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
@@ -81,7 +81,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -65,7 +65,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -117,7 +117,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -155,7 +155,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -29,7 +29,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:

View File

@@ -0,0 +1,109 @@
name: Run typing conformance
permissions: {}
on:
pull_request:
paths:
- "crates/ty*/**"
- "crates/ruff_db"
- "crates/ruff_python_ast"
- "crates/ruff_python_parser"
- ".github/workflows/typing_conformance.yaml"
- ".github/workflows/typing_conformance_comment.yaml"
- "Cargo.lock"
- "!**.md"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
jobs:
typing_conformance:
name: Compute diagnostic diff
runs-on: depot-ubuntu-22.04-32
timeout-minutes: 10
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: python/typing
ref: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
path: typing
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Compute diagnostic diff
shell: bash
run: |
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
# Build the executable for the old and new commit
(
cd ruff
echo "new commit"
git checkout -b new_commit "${{ github.event.pull_request.head.sha }}"
git rev-list --format=%s --max-count=1 new_commit
cargo build --release --bin ty
mv target/release/ty ty-new
echo "old commit (merge base)"
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
git rev-list --format=%s --max-count=1 old_commit
cargo build --release --bin ty
mv target/release/ty ty-old
)
(
cd typing/conformance/tests
echo "Running ty on old commit (merge base)"
"$RUFF_DIR/ty-old" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/old-output.txt" 2>&1 || true
echo "Running ty on new commit"
"$RUFF_DIR/ty-new" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/new-output.txt" 2>&1 || true
)
if ! diff -u old-output.txt new-output.txt > typing_conformance_diagnostics.diff; then
echo "Differences found between base and PR"
else
echo "No differences found"
touch typing_conformance_diagnostics.diff
fi
echo ${{ github.event.number }} > pr-number
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: typing_conformance_diagnostics_diff
path: typing_conformance_diagnostics.diff
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number

View File

@@ -81,7 +81,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.4
rev: v0.12.5
hooks:
- id: ruff-format
- id: ruff-check

27
Cargo.lock generated
View File

@@ -530,7 +530,7 @@ dependencies = [
"ciborium",
"clap",
"codspeed",
"criterion-plot",
"criterion-plot 0.5.0",
"is-terminal",
"itertools 0.10.5",
"num-traits",
@@ -713,15 +713,15 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"criterion-plot 0.6.0",
"itertools 0.13.0",
"num-traits",
"oorandom",
@@ -742,6 +742,16 @@ dependencies = [
"itertools 0.10.5",
]
[[package]]
name = "criterion-plot"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
dependencies = [
"cast",
"itertools 0.13.0",
]
[[package]]
name = "crossbeam"
version = "0.8.4"
@@ -1151,9 +1161,9 @@ dependencies = [
[[package]]
name = "get-size-derive2"
version = "0.5.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
dependencies = [
"attribute-derive",
"quote",
@@ -1162,9 +1172,9 @@ dependencies = [
[[package]]
name = "get-size2"
version = "0.5.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
dependencies = [
"compact_str",
"get-size-derive2",
@@ -2881,6 +2891,7 @@ dependencies = [
"tracing",
"tracing-subscriber",
"ty_static",
"unicode-width 0.2.1",
"web-time",
"zip",
]

View File

@@ -73,7 +73,7 @@ console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
countme = { version = "3.0.1" }
compact_str = "0.9.0"
criterion = { version = "0.6.0", default-features = false }
criterion = { version = "0.7.0", default-features = false }
crossbeam = { version = "0.8.4" }
dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
@@ -83,7 +83,7 @@ etcetera = { version = "0.10.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }
get-size2 = { version = "0.5.0", features = [
get-size2 = { version = "0.6.0", features = [
"derive",
"smallvec",
"hashbrown",

View File

@@ -57,33 +57,40 @@ fn dependencies() -> Result<()> {
.write_str(indoc::indoc! {r#"
def f(): pass
"#})?;
root.child("ruff")
.child("e.pyi")
.write_str(indoc::indoc! {r#"
def f() -> None: ...
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": [
"ruff/d.py"
],
"ruff/d.py": [
"ruff/e.py"
],
"ruff/e.py": []
}
assert_cmd_snapshot!(command().current_dir(&root), @r#"
success: true
exit_code: 0
----- stdout -----
{
"ruff/__init__.py": [],
"ruff/a.py": [
"ruff/b.py"
],
"ruff/b.py": [
"ruff/c.py"
],
"ruff/c.py": [
"ruff/d.py"
],
"ruff/d.py": [
"ruff/e.py",
"ruff/e.pyi"
],
"ruff/e.py": [],
"ruff/e.pyi": []
}
----- stderr -----
"###);
----- stderr -----
"#);
});
Ok(())

View File

@@ -42,6 +42,7 @@ serde_json = { workspace = true, optional = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
unicode-width = { workspace = true }
zip = { workspace = true }
[target.'cfg(target_arch="wasm32")'.dependencies]

View File

@@ -6,7 +6,9 @@ use ruff_source_file::{LineColumn, SourceCode, SourceFile};
use ruff_annotate_snippets::Level as AnnotateLevel;
use ruff_text_size::{Ranged, TextRange, TextSize};
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
pub use self::render::{
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
};
use crate::{Db, files::File};
mod render;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::path::Path;
@@ -7,7 +8,7 @@ use ruff_annotate_snippets::{
};
use ruff_notebook::{Notebook, NotebookIndex};
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextRange, TextSize};
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
use crate::{
@@ -520,7 +521,7 @@ impl<'r> RenderableSnippets<'r> {
#[derive(Debug)]
struct RenderableSnippet<'r> {
/// The actual snippet text.
snippet: &'r str,
snippet: Cow<'r, str>,
/// The absolute line number corresponding to where this
/// snippet begins.
line_start: OneIndexed,
@@ -580,6 +581,13 @@ impl<'r> RenderableSnippet<'r> {
.iter()
.map(|ann| RenderableAnnotation::new(snippet_start, ann))
.collect();
let EscapedSourceCode {
text: snippet,
annotations,
} = replace_whitespace_and_unprintable(snippet, annotations)
.fix_up_empty_spans_after_line_terminator();
RenderableSnippet {
snippet,
line_start,
@@ -590,7 +598,7 @@ impl<'r> RenderableSnippet<'r> {
/// Convert this to an "annotate" snippet.
fn to_annotate<'a>(&'a self, path: &'a str) -> AnnotateSnippet<'a> {
AnnotateSnippet::source(self.snippet)
AnnotateSnippet::source(&self.snippet)
.origin(path)
.line_start(self.line_start.get())
.annotations(
@@ -820,6 +828,230 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
path
}
/// Given some source code and annotation ranges, this routine replaces tabs
/// with ASCII whitespace, and unprintable characters with printable
/// representations of them.
///
/// The source code and annotations returned are updated to reflect changes made
/// to the source code (if any).
fn replace_whitespace_and_unprintable<'r>(
source: &'r str,
mut annotations: Vec<RenderableAnnotation<'r>>,
) -> EscapedSourceCode<'r> {
// Updates the annotation ranges given by the caller whenever a single byte (at `index` in
// `source`) is replaced with `len` bytes.
//
// When the index occurs before the start of the range, the range is
// offset by `len`. When the range occurs after or at the start but before
// the end, then the end of the range only is offset by `len`.
let mut update_ranges = |index: usize, len: u32| {
for ann in &mut annotations {
if index < usize::from(ann.range.start()) {
ann.range += TextSize::new(len - 1);
} else if index < usize::from(ann.range.end()) {
ann.range = ann.range.add_end(TextSize::new(len - 1));
}
}
};
// If `c` is an unprintable character, then this returns a printable
// representation of it (using a fancier Unicode codepoint).
let unprintable_replacement = |c: char| -> Option<char> {
match c {
'\x07' => Some('␇'),
'\x08' => Some('␈'),
'\x1b' => Some('␛'),
'\x7f' => Some('␡'),
_ => None,
}
};
const TAB_SIZE: usize = 4;
let mut width = 0;
let mut column = 0;
let mut last_end = 0;
let mut result = String::new();
for (index, c) in source.char_indices() {
let old_width = width;
match c {
'\n' | '\r' => {
width = 0;
column = 0;
}
'\t' => {
let tab_offset = TAB_SIZE - (column % TAB_SIZE);
width += tab_offset;
column += tab_offset;
let tab_width =
u32::try_from(width - old_width).expect("small width because of tab size");
result.push_str(&source[last_end..index]);
update_ranges(result.text_len().to_usize(), tab_width);
for _ in 0..tab_width {
result.push(' ');
}
last_end = index + 1;
}
_ => {
width += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0);
column += 1;
if let Some(printable) = unprintable_replacement(c) {
result.push_str(&source[last_end..index]);
let len = printable.text_len().to_u32();
update_ranges(result.text_len().to_usize(), len);
result.push(printable);
last_end = index + 1;
}
}
}
}
// No tabs or unprintable chars
if result.is_empty() {
EscapedSourceCode {
annotations,
text: Cow::Borrowed(source),
}
} else {
result.push_str(&source[last_end..]);
EscapedSourceCode {
annotations,
text: Cow::Owned(result),
}
}
}
struct EscapedSourceCode<'r> {
text: Cow<'r, str>,
annotations: Vec<RenderableAnnotation<'r>>,
}
impl<'r> EscapedSourceCode<'r> {
// This attempts to "fix up" the spans on each annotation in the case where
// it's an empty span immediately following a line terminator.
//
// At present, `annotate-snippets` (both upstream and our vendored copy)
// will render annotations of such spans to point to the space immediately
// following the previous line. But ideally, this should point to the space
// immediately preceding the next line.
//
// After attempting to fix `annotate-snippets` and giving up after a couple
// hours, this routine takes a different tact: it adjusts the span to be
// non-empty and it will cover the first codepoint of the following line.
// This forces `annotate-snippets` to point to the right place.
//
// See also: <https://github.com/astral-sh/ruff/issues/15509> and
// `ruff_linter::message::text::SourceCode::fix_up_empty_spans_after_line_terminator`,
// from which this was adapted.
fn fix_up_empty_spans_after_line_terminator(mut self) -> EscapedSourceCode<'r> {
for ann in &mut self.annotations {
let range = ann.range;
if !range.is_empty()
|| range.start() == TextSize::from(0)
|| range.start() >= self.text.text_len()
{
continue;
}
if !matches!(
self.text.as_bytes()[range.start().to_usize() - 1],
b'\n' | b'\r'
) {
continue;
}
let start = range.start();
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
ann.range = TextRange::new(start, end);
}
self
}
}
/// Finds the closest [`TextSize`] not less than the offset given for which
/// `is_char_boundary` is `true`. Unless the offset given is greater than
/// the length of the underlying contents, in which case, the length of the
/// contents is returned.
///
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
///
/// # Examples
///
/// From `std`:
///
/// ```
/// use ruff_db::diagnostic::ceil_char_boundary;
/// use ruff_text_size::{Ranged, TextLen, TextSize};
///
/// let source = "❤️🧡💛💚💙💜";
/// assert_eq!(source.text_len(), TextSize::from(26));
/// assert!(!source.is_char_boundary(13));
///
/// let closest = ceil_char_boundary(source, TextSize::from(13));
/// assert_eq!(closest, TextSize::from(14));
/// assert_eq!(&source[..closest.to_usize()], "❤️🧡💛");
/// ```
///
/// Additional examples:
///
/// ```
/// use ruff_db::diagnostic::ceil_char_boundary;
/// use ruff_text_size::{Ranged, TextRange, TextSize};
///
/// let source = "Hello";
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(5)),
/// TextSize::from(5)
/// );
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(6)),
/// TextSize::from(5)
/// );
///
/// let source = "α";
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(1)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(2)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// ceil_char_boundary(source, TextSize::from(3)),
/// TextSize::from(2)
/// );
/// ```
pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
let upper_bound = offset
.to_u32()
.saturating_add(4)
.min(text.text_len().to_u32());
(offset.to_u32()..upper_bound)
.map(TextSize::from)
.find(|offset| text.is_char_boundary(offset.to_usize()))
.unwrap_or_else(|| TextSize::from(upper_bound))
}
#[cfg(test)]
mod tests {
@@ -2359,7 +2591,7 @@ watermelon
}
/// Returns a builder for tersely constructing diagnostics.
fn builder(
pub(super) fn builder(
&mut self,
identifier: &'static str,
severity: Severity,
@@ -2426,7 +2658,7 @@ watermelon
///
/// See the docs on `TestEnvironment::span` for the meaning of
/// `path`, `line_offset_start` and `line_offset_end`.
fn primary(
pub(super) fn primary(
mut self,
path: &str,
line_offset_start: &str,

View File

@@ -1,8 +1,8 @@
#[cfg(test)]
mod tests {
use crate::diagnostic::{
DiagnosticFormat,
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
DiagnosticFormat, Severity,
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
};
#[test]
@@ -63,4 +63,118 @@ mod tests {
|
");
}
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit c9b99e4.
///
/// For example, without the fix, we get diagnostics like this:
///
/// ```
/// error[no-indented-block]: Expected an indented block
/// --> example.py:3:1
/// |
/// 2 | if False:
/// | ^
/// 3 | print()
/// |
/// ```
///
/// where the caret points to the end of the previous line instead of the start of the next.
#[test]
fn empty_span_after_line_terminator() {
let mut env = TestEnvironment::new();
env.add(
"example.py",
r#"
if False:
print()
"#,
);
env.format(DiagnosticFormat::Full);
let diagnostic = env
.builder(
"no-indented-block",
Severity::Error,
"Expected an indented block",
)
.primary("example.py", "3:0", "3:0", "")
.build();
insta::assert_snapshot!(env.render(&diagnostic), @r"
error[no-indented-block]: Expected an indented block
--> example.py:3:1
|
2 | if False:
3 | print()
| ^
|
");
}
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit 2922490.
///
/// For example, without the fix, we get diagnostics like this:
///
/// ```
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
/// --> example.py:1:25
/// |
/// 1 | nested_fstrings = f'␈{f'{f'␛'}'}'
/// | ^
/// |
/// ```
///
/// where the caret points to the `f` in the f-string instead of the start of the invalid
/// character (`^Z`).
#[test]
fn unprintable_characters() {
let mut env = TestEnvironment::new();
env.add("example.py", "nested_fstrings = f'{f'{f''}'}'");
env.format(DiagnosticFormat::Full);
let diagnostic = env
.builder(
"invalid-character-sub",
Severity::Error,
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
)
.primary("example.py", "1:24", "1:24", "")
.build();
insta::assert_snapshot!(env.render(&diagnostic), @r#"
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
--> example.py:1:25
|
1 | nested_fstrings = f'␈{f'{f'␛'}'}'
| ^
|
"#);
}
#[test]
fn multiple_unprintable_characters() -> std::io::Result<()> {
let mut env = TestEnvironment::new();
env.add("example.py", "");
env.format(DiagnosticFormat::Full);
let diagnostic = env
.builder(
"invalid-character-sub",
Severity::Error,
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
)
.primary("example.py", "1:1", "1:1", "")
.build();
insta::assert_snapshot!(env.render(&diagnostic), @r#"
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
--> example.py:1:2
|
1 | ␈
| ^
|
"#);
Ok(())
}
}

View File

@@ -42,13 +42,11 @@ impl ModuleImports {
// Resolve the imports.
let mut resolved_imports = ModuleImports::default();
for import in imports {
let Some(resolved) = Resolver::new(db).resolve(import) else {
continue;
};
let Some(path) = resolved.as_system_path() else {
continue;
};
resolved_imports.insert(path.to_path_buf());
for resolved in Resolver::new(db).resolve(import) {
if let Some(path) = resolved.as_system_path() {
resolved_imports.insert(path.to_path_buf());
}
}
}
Ok(resolved_imports)

View File

@@ -1,5 +1,5 @@
use ruff_db::files::FilePath;
use ty_python_semantic::resolve_module;
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
use crate::ModuleDb;
use crate::collector::CollectedImport;
@@ -16,24 +16,67 @@ impl<'a> Resolver<'a> {
}
/// Resolve the [`CollectedImport`] into a [`FilePath`].
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
match import {
CollectedImport::Import(import) => {
let module = resolve_module(self.db, &import)?;
Some(module.file(self.db)?.path(self.db))
// Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
let file = self.resolve_module(&import);
// If the file is a stub, look for the corresponding source file.
let source_file = file
.is_some_and(|file| file.extension() == Some("pyi"))
.then(|| self.resolve_real_module(&import))
.flatten();
std::iter::once(file)
.chain(std::iter::once(source_file))
.flatten()
}
CollectedImport::ImportFrom(import) => {
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
if let Some(file) = self.resolve_module(&import) {
// If the file is a stub, look for the corresponding source file.
let source_file = (file.extension() == Some("pyi"))
.then(|| self.resolve_real_module(&import))
.flatten();
return std::iter::once(Some(file))
.chain(std::iter::once(source_file))
.flatten();
}
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
let parent = import.parent();
let file = parent
.as_ref()
.and_then(|parent| self.resolve_module(parent));
let module = resolve_module(self.db, &import).or_else(|| {
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
// If the file is a stub, look for the corresponding source file.
let source_file = file
.is_some_and(|file| file.extension() == Some("pyi"))
.then(|| {
parent
.as_ref()
.and_then(|parent| self.resolve_real_module(parent))
})
.flatten();
resolve_module(self.db, &parent?)
})?;
Some(module.file(self.db)?.path(self.db))
std::iter::once(file)
.chain(std::iter::once(source_file))
.flatten()
}
}
}
/// Resolves a module name to a module.
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = resolve_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
/// Resolves a module name to a module (stubs not allowed).
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
let module = resolve_real_module(self.db, module_name)?;
Some(module.file(self.db)?.path(self.db))
}
}

View File

@@ -650,3 +650,17 @@ f"""This is a test. {
if True else
"Don't add a trailing comma here ->"
}"""
type X[
T
] = T
def f[
T
](): pass
class C[
T
]: pass
type X[T,] = T
def f[T,](): pass
class C[T,]: pass

View File

@@ -289,4 +289,19 @@ def f():
i = "xyz"
result = []
for i in range(3):
result.append((x for x in [i]))
result.append((x for x in [i]))
G_INDEX = None
def f():
global G_INDEX
result = []
for G_INDEX in range(3):
result.append(G_INDEX)
def f():
NL_INDEX = None
def x():
nonlocal NL_INDEX
result = []
for NL_INDEX in range(3):
result.append(NL_INDEX)

View File

@@ -55,3 +55,12 @@ _ = Decimal(0.1)
_ = Decimal(-0.5)
_ = Decimal(5.0)
_ = decimal.Decimal(4.2)
# Cases with int and bool - should produce safe fixes
_ = Decimal.from_float(1)
_ = Decimal.from_float(True)
# Cases with non-finite floats - should produce safe fixes
_ = Decimal.from_float(float("-nan"))
_ = Decimal.from_float(float("\x2dnan"))
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))

View File

@@ -3216,6 +3216,11 @@ impl<'a> LintContext<'a> {
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &Diagnostic> {
self.diagnostics.get_mut().iter()
}
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
pub(crate) const fn settings(&self) -> &LinterSettings {
self.settings
}
}
/// An abstraction for mutating a diagnostic.

View File

@@ -16,7 +16,6 @@ use crate::rules::{
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
};
use crate::settings::LinterSettings;
use super::ast::LintContext;
@@ -27,7 +26,6 @@ pub(crate) fn check_tokens(
locator: &Locator,
indexer: &Indexer,
stylist: &Stylist,
settings: &LinterSettings,
source_type: PySourceType,
cell_offsets: Option<&CellOffsets>,
context: &mut LintContext,
@@ -42,15 +40,8 @@ pub(crate) fn check_tokens(
Rule::BlankLinesAfterFunctionOrClass,
Rule::BlankLinesBeforeNestedDefinition,
]) {
BlankLinesChecker::new(
locator,
stylist,
settings,
source_type,
cell_offsets,
context,
)
.check_lines(tokens);
BlankLinesChecker::new(locator, stylist, source_type, cell_offsets, context)
.check_lines(tokens);
}
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
@@ -63,12 +54,12 @@ pub(crate) fn check_tokens(
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
for range in comment_ranges {
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
ruff::rules::ambiguous_unicode_character_comment(context, locator, range);
}
}
if context.is_rule_enabled(Rule::CommentedOutCode) {
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
eradicate::rules::commented_out_code(context, locator, comment_ranges);
}
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
@@ -110,7 +101,7 @@ pub(crate) fn check_tokens(
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]) {
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer);
}
if context.any_rule_enabled(&[

View File

@@ -188,7 +188,6 @@ pub fn check_path(
locator,
indexer,
stylist,
settings,
source_type,
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
&mut context,

View File

@@ -118,86 +118,6 @@ impl<'a> Locator<'a> {
}
}
/// Finds the closest [`TextSize`] not less than the offset given for which
/// `is_char_boundary` is `true`. Unless the offset given is greater than
/// the length of the underlying contents, in which case, the length of the
/// contents is returned.
///
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
///
/// # Examples
///
/// From `std`:
///
/// ```
/// use ruff_text_size::{Ranged, TextSize};
/// use ruff_linter::Locator;
///
/// let locator = Locator::new("❤️🧡💛💚💙💜");
/// assert_eq!(locator.text_len(), TextSize::from(26));
/// assert!(!locator.contents().is_char_boundary(13));
///
/// let closest = locator.ceil_char_boundary(TextSize::from(13));
/// assert_eq!(closest, TextSize::from(14));
/// assert_eq!(&locator.contents()[..closest.to_usize()], "❤️🧡💛");
/// ```
///
/// Additional examples:
///
/// ```
/// use ruff_text_size::{Ranged, TextRange, TextSize};
/// use ruff_linter::Locator;
///
/// let locator = Locator::new("Hello");
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(5)),
/// TextSize::from(5)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(6)),
/// TextSize::from(5)
/// );
///
/// let locator = Locator::new("α");
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(0)),
/// TextSize::from(0)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(1)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(2)),
/// TextSize::from(2)
/// );
///
/// assert_eq!(
/// locator.ceil_char_boundary(TextSize::from(3)),
/// TextSize::from(2)
/// );
/// ```
pub fn ceil_char_boundary(&self, offset: TextSize) -> TextSize {
let upper_bound = offset
.to_u32()
.saturating_add(4)
.min(self.text_len().to_u32());
(offset.to_u32()..upper_bound)
.map(TextSize::from)
.find(|offset| self.contents.is_char_boundary(offset.to_usize()))
.unwrap_or_else(|| TextSize::from(upper_bound))
}
/// Take the source code between the given [`TextRange`].
#[inline]
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {

View File

@@ -6,12 +6,13 @@ use bitflags::bitflags;
use colored::Colorize;
use ruff_annotate_snippets::{Level, Renderer, Snippet};
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
};
use ruff_notebook::NotebookIndex;
use ruff_source_file::OneIndexed;
use ruff_text_size::{TextLen, TextRange, TextSize};
use crate::Locator;
use crate::line_width::{IndentWidth, LineWidthBuilder};
use crate::message::diff::Diff;
use crate::message::{Emitter, EmitterContext};
@@ -376,9 +377,8 @@ impl<'a> SourceCode<'a> {
if self.text.as_bytes()[self.annotation_range.start().to_usize() - 1] != b'\n' {
return self;
}
let locator = Locator::new(&self.text);
let start = self.annotation_range.start();
let end = locator.ceil_char_boundary(start + TextSize::from(1));
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
SourceCode {
annotation_range: TextRange::new(start, end),
..self

View File

@@ -225,3 +225,8 @@ pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSet
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/19390
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@@ -5,7 +5,6 @@ use ruff_text_size::TextRange;
use crate::Locator;
use crate::checkers::ast::LintContext;
use crate::settings::LinterSettings;
use crate::{Edit, Fix, FixAvailability, Violation};
use crate::rules::eradicate::detection::comment_contains_code;
@@ -51,7 +50,6 @@ pub(crate) fn commented_out_code(
context: &LintContext,
locator: &Locator,
comment_ranges: &CommentRanges,
settings: &LinterSettings,
) {
let mut comments = comment_ranges.into_iter().peekable();
// Iterate over all comments in the document.
@@ -65,7 +63,9 @@ pub(crate) fn commented_out_code(
}
// Verify that the comment is on its own line, and that it contains code.
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
if is_own_line_comment(line)
&& comment_contains_code(line, &context.settings().task_tags[..])
{
if let Some(mut diagnostic) =
context.report_diagnostic_if_enabled(CommentedOutCode, range)
{

View File

@@ -27,4 +27,23 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Path::new("COM81.py"))]
#[test_case(Path::new("COM81_syntax_error.py"))]
fn preview_rules(path: &Path) -> Result<()> {
let snapshot = format!("preview__{}", path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_commas").join(path).as_path(),
&settings::LinterSettings {
preview: crate::settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rules(vec![
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
])
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -5,6 +5,8 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Locator;
use crate::checkers::ast::LintContext;
use crate::preview::is_trailing_comma_type_params_enabled;
use crate::settings::LinterSettings;
use crate::{AlwaysFixableViolation, Violation};
use crate::{Edit, Fix};
@@ -24,6 +26,8 @@ enum TokenType {
Def,
For,
Lambda,
Class,
Type,
Irrelevant,
}
@@ -69,6 +73,8 @@ impl From<(TokenKind, TextRange)> for SimpleToken {
TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
TokenKind::Rbrace => TokenType::ClosingBracket,
TokenKind::Def => TokenType::Def,
TokenKind::Class => TokenType::Class,
TokenKind::Type => TokenType::Type,
TokenKind::For => TokenType::For,
TokenKind::Lambda => TokenType::Lambda,
// Import treated like a function.
@@ -98,6 +104,8 @@ enum ContextType {
Dict,
/// Lambda parameter list, e.g. `lambda a, b`.
LambdaParameters,
/// Type parameter list, e.g. `def foo[T, U](): ...`
TypeParameters,
}
/// Comma context - described a comma-delimited "situation".
@@ -290,7 +298,7 @@ pub(crate) fn trailing_commas(
}
// Update the comma context stack.
let context = update_context(token, prev, prev_prev, &mut stack);
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
check_token(token, prev, prev_prev, context, locator, lint_context);
@@ -326,6 +334,7 @@ fn check_token(
ContextType::No => false,
ContextType::FunctionParameters => true,
ContextType::CallArguments => true,
ContextType::TypeParameters => true,
// `(1)` is not equivalent to `(1,)`.
ContextType::Tuple => context.num_commas != 0,
// `x[1]` is not equivalent to `x[1,]`.
@@ -408,6 +417,7 @@ fn update_context(
prev: SimpleToken,
prev_prev: SimpleToken,
stack: &mut Vec<Context>,
settings: &LinterSettings,
) -> Context {
let new_context = match token.ty {
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
@@ -417,6 +427,17 @@ fn update_context(
}
_ => Context::new(ContextType::Tuple),
},
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
match (prev.ty, prev_prev.ty) {
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
Context::new(ContextType::TypeParameters)
}
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
Context::new(ContextType::Subscript)
}
_ => Context::new(ContextType::List),
}
}
TokenType::OpeningSquareBracket => match prev.ty {
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
Context::new(ContextType::Subscript)

View File

@@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
---
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
2 | (
3 | *args
| ^^^^^
4 | )
|
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^
7 | pass
|
COM81_syntax_error.py:6:38: COM819 Trailing comma prohibited
|
4 | )
5 |
6 | def foo[(param1='test', param2='test',):
| ^ COM819
7 | pass
|
= help: Remove trailing comma

View File

@@ -11,7 +11,6 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Locator;
use crate::checkers::ast::LintContext;
use crate::settings::LinterSettings;
use crate::{Edit, Fix, FixAvailability, Violation};
/// ## What it does
@@ -108,13 +107,15 @@ pub(crate) fn implicit(
tokens: &Tokens,
locator: &Locator,
indexer: &Indexer,
settings: &LinterSettings,
) {
for (a_token, b_token) in tokens
.iter()
.filter(|token| {
token.kind() != TokenKind::Comment
&& (settings.flake8_implicit_str_concat.allow_multiline
&& (context
.settings()
.flake8_implicit_str_concat
.allow_multiline
|| token.kind() != TokenKind::NonLogicalNewline)
})
.tuple_windows()

View File

@@ -249,6 +249,11 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
.iter()
.find(|binding| for_stmt.target.range() == binding.range)
.unwrap();
// If the target variable is global (e.g., `global INDEX`) or nonlocal (e.g., `nonlocal INDEX`),
// then it is intended to be used elsewhere outside the for loop.
if target_binding.is_global() || target_binding.is_nonlocal() {
return;
}
// If any references to the loop target variable are after the loop,
// then converting it into a comprehension would cause a NameError
if target_binding

View File

@@ -263,5 +263,7 @@ PERF401.py:292:9: PERF401 Use a list comprehension to create a transformed list
291 | for i in range(3):
292 | result.append((x for x in [i]))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
293 |
294 | G_INDEX = None
|
= help: Replace for loop with list comprehension

View File

@@ -612,6 +612,8 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
291 | for i in range(3):
292 | result.append((x for x in [i]))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
293 |
294 | G_INDEX = None
|
= help: Replace for loop with list comprehension
@@ -623,3 +625,6 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
291 |- for i in range(3):
292 |- result.append((x for x in [i]))
290 |+ result = [(x for x in [i]) for i in range(3)]
293 291 |
294 292 | G_INDEX = None
295 293 | def f():

View File

@@ -21,7 +21,6 @@ use crate::checkers::ast::{DiagnosticGuard, LintContext};
use crate::checkers::logical_lines::expand_indent;
use crate::line_width::IndentWidth;
use crate::rules::pycodestyle::helpers::is_non_logical_token;
use crate::settings::LinterSettings;
use crate::{AlwaysFixableViolation, Edit, Fix, Locator, Violation};
/// Number of blank lines around top level classes and functions.
@@ -694,14 +693,12 @@ pub(crate) struct BlankLinesChecker<'a, 'b> {
source_type: PySourceType,
cell_offsets: Option<&'a CellOffsets>,
context: &'a LintContext<'b>,
settings: &'a LinterSettings,
}
impl<'a, 'b> BlankLinesChecker<'a, 'b> {
pub(crate) fn new(
locator: &'a Locator<'a>,
stylist: &'a Stylist<'a>,
settings: &'a LinterSettings,
source_type: PySourceType,
cell_offsets: Option<&'a CellOffsets>,
context: &'a LintContext<'b>,
@@ -712,7 +709,6 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
source_type,
cell_offsets,
context,
settings,
}
}
@@ -733,7 +729,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
let line_preprocessor = LinePreprocessor::new(
tokens,
self.locator,
self.settings.tab_size,
self.context.settings().tab_size,
self.cell_offsets,
);
@@ -879,7 +875,8 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
// `isort` defaults to 2 if before a class or function definition (except in stubs where it is one) and 1 otherwise.
// Defaulting to 2 (or 1 in stubs) here is correct because the variable is only used when testing the
// blank lines before a class or function definition.
u32::try_from(self.settings.isort.lines_after_imports).unwrap_or(max_lines_level)
u32::try_from(self.context.settings().isort.lines_after_imports)
.unwrap_or(max_lines_level)
} else {
max_lines_level
}
@@ -941,8 +938,10 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
(LogicalLineKind::Import, Follows::FromImport)
| (LogicalLineKind::FromImport, Follows::Import)
) {
max_lines_level
.max(u32::try_from(self.settings.isort.lines_between_types).unwrap_or(u32::MAX))
max_lines_level.max(
u32::try_from(self.context.settings().isort.lines_between_types)
.unwrap_or(u32::MAX),
)
} else {
expected_blank_lines_before_definition
};

View File

@@ -6,8 +6,9 @@ use ruff_python_ast::{
use ruff_python_semantic::{SemanticModel, analyze::typing};
use ruff_text_size::Ranged;
use crate::Violation;
use crate::checkers::ast::Checker;
use crate::fix;
use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
/// ## What it does
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
@@ -35,10 +36,14 @@ use crate::checkers::ast::Checker;
/// url = "www.example.com"
/// suffix = url.rsplit(".", maxsplit=1)[-1]
/// ```
///
/// ## Fix Safety
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `**kwargs`, as
/// adding a `maxsplit` keyword to such a call may lead to a duplicate keyword argument error.
#[derive(ViolationMetadata)]
pub(crate) struct MissingMaxsplitArg {
index: SliceBoundary,
actual_split_type: String,
suggested_split_type: String,
}
/// Represents the index of the slice used for this rule (which can only be 0 or -1)
@@ -47,25 +52,27 @@ enum SliceBoundary {
Last,
}
impl Violation for MissingMaxsplitArg {
impl AlwaysFixableViolation for MissingMaxsplitArg {
#[derive_message_formats]
fn message(&self) -> String {
let MissingMaxsplitArg {
index,
actual_split_type,
actual_split_type: _,
suggested_split_type,
} = self;
let suggested_split_type = match index {
SliceBoundary::First => "split",
SliceBoundary::Last => "rsplit",
};
format!("Replace with `{suggested_split_type}(..., maxsplit=1)`.")
}
fn fix_title(&self) -> String {
let MissingMaxsplitArg {
actual_split_type,
suggested_split_type,
} = self;
if actual_split_type == suggested_split_type {
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
} else {
format!(
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
)
format!("Use `str.{suggested_split_type}()` and pass `maxsplit=1`")
}
}
}
@@ -123,8 +130,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
};
// Check the function is "split" or "rsplit"
let attr = attr.as_str();
if !matches!(attr, "split" | "rsplit") {
let actual_split_type = attr.as_str();
if !matches!(actual_split_type, "split" | "rsplit") {
return;
}
@@ -161,11 +168,48 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
}
}
checker.report_diagnostic(
let suggested_split_type = match slice_boundary {
SliceBoundary::First => "split",
SliceBoundary::Last => "rsplit",
};
let maxsplit_argument_edit = fix::edits::add_argument(
"maxsplit=1",
arguments,
checker.comment_ranges(),
checker.locator().contents(),
);
// Only change `actual_split_type` if it doesn't match `suggested_split_type`
let split_type_edit: Option<Edit> = if actual_split_type == suggested_split_type {
None
} else {
Some(Edit::range_replacement(
suggested_split_type.to_string(),
attr.range(),
))
};
let mut diagnostic = checker.report_diagnostic(
MissingMaxsplitArg {
index: slice_boundary,
actual_split_type: attr.to_string(),
actual_split_type: actual_split_type.to_string(),
suggested_split_type: suggested_split_type.to_string(),
},
expr.range(),
);
diagnostic.set_fix(Fix::applicable_edits(
maxsplit_argument_edit,
split_type_edit,
// If keyword.arg is `None` (i.e. if the function call contains `**kwargs`), mark the fix as unsafe
if arguments
.keywords
.iter()
.any(|keyword| keyword.arg.is_none())
{
Applicability::Unsafe
} else {
Applicability::Safe
},
));
}

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
12 | # Errors
13 | ## Test split called directly on string literal
@@ -10,8 +10,19 @@ missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
11 11 |
12 12 | # Errors
13 13 | ## Test split called directly on string literal
14 |-"1,2,3".split(",")[0] # [missing-maxsplit-arg]
14 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
13 | ## Test split called directly on string literal
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
@@ -20,8 +31,19 @@ missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
12 12 | # Errors
13 13 | ## Test split called directly on string literal
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
15 |-"1,2,3".split(",")[-1] # [missing-maxsplit-arg]
15 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
18 18 |
missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
@@ -29,8 +51,19 @@ missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
13 13 | ## Test split called directly on string literal
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 |-"1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
16 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
18 18 |
19 19 | ## Test split called on string variable
missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
@@ -39,8 +72,19 @@ missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
18 |
19 | ## Test split called on string variable
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
17 |-"1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
17 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
18 18 |
19 19 | ## Test split called on string variable
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
19 | ## Test split called on string variable
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
@@ -48,8 +92,19 @@ missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
18 18 |
19 19 | ## Test split called on string variable
20 |-SEQ.split(",")[0] # [missing-maxsplit-arg]
20 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
19 | ## Test split called on string variable
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
@@ -58,8 +113,19 @@ missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
18 18 |
19 19 | ## Test split called on string variable
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
21 |-SEQ.split(",")[-1] # [missing-maxsplit-arg]
21 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
24 24 |
missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
@@ -67,8 +133,19 @@ missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^ PLC0207
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
19 19 | ## Test split called on string variable
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 |-SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
22 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
24 24 |
25 25 | ## Test split called on class attribute
missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -77,8 +154,19 @@ missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
24 |
25 | ## Test split called on class attribute
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
23 |-SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
23 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
24 24 |
25 25 | ## Test split called on class attribute
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
25 | ## Test split called on class attribute
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
@@ -86,8 +174,19 @@ missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
24 24 |
25 25 | ## Test split called on class attribute
26 |-Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
26 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
25 | ## Test split called on class attribute
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
@@ -96,8 +195,19 @@ missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
24 24 |
25 25 | ## Test split called on class attribute
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
27 |-Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
27 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
30 30 |
missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
@@ -105,8 +215,19 @@ missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
25 25 | ## Test split called on class attribute
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 |-Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
28 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
30 30 |
31 31 | ## Test split called on sliced string
missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -115,8 +236,19 @@ missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
30 |
31 | ## Test split called on sliced string
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
29 |-Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
29 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
30 30 |
31 31 | ## Test split called on sliced string
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
31 | ## Test split called on sliced string
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -124,8 +256,19 @@ missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
30 30 |
31 31 | ## Test split called on sliced string
32 |-"1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
32 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:33:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
31 | ## Test split called on sliced string
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -134,8 +277,19 @@ missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
30 30 |
31 31 | ## Test split called on sliced string
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
33 |-"1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
33 |+"1,2,3"[::-1][::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:34:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
@@ -144,8 +298,19 @@ missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
31 31 | ## Test split called on sliced string
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 |-SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
34 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:35:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
@@ -154,8 +319,19 @@ missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 |-Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
35 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
@@ -164,8 +340,19 @@ missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 |-"1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
36 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
39 39 |
missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
@@ -173,8 +360,19 @@ missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 |-SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
37 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
39 39 |
40 40 | ## Test sep given as named argument
missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
@@ -183,8 +381,19 @@ missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
39 |
40 | ## Test sep given as named argument
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
38 |-Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
38 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
39 39 |
40 40 | ## Test sep given as named argument
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
40 | ## Test sep given as named argument
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
@@ -192,8 +401,19 @@ missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
39 39 |
40 40 | ## Test sep given as named argument
41 |-"1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
41 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
40 | ## Test sep given as named argument
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
@@ -202,8 +422,19 @@ missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
39 39 |
40 40 | ## Test sep given as named argument
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
42 |-"1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
42 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
45 45 |
missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
@@ -211,8 +442,19 @@ missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
40 40 | ## Test sep given as named argument
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 |-"1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
43 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
45 45 |
46 46 | ## Special cases
missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
@@ -221,8 +463,19 @@ missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
45 |
46 | ## Special cases
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
44 |-"1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
44 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
45 45 |
46 46 | ## Special cases
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
46 | ## Special cases
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
@@ -230,8 +483,19 @@ missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
45 45 |
46 46 | ## Special cases
47 |-"1,2,3".split("\n")[0] # [missing-maxsplit-arg]
47 |+"1,2,3".split("\n", maxsplit=1)[0] # [missing-maxsplit-arg]
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
50 50 |
missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
46 | ## Special cases
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
@@ -239,8 +503,19 @@ missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
45 45 |
46 46 | ## Special cases
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
48 |-"1,2,3".split("split")[-1] # [missing-maxsplit-arg]
48 |+"1,2,3".rsplit("split", maxsplit=1)[-1] # [missing-maxsplit-arg]
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
50 50 |
51 51 | ## Test class attribute named split
missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
@@ -249,8 +524,19 @@ missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split
50 |
51 | ## Test class attribute named split
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
46 46 | ## Special cases
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
49 |-"1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
49 |+"1,2,3".split("rsplit", maxsplit=1)[0] # [missing-maxsplit-arg]
50 50 |
51 51 | ## Test class attribute named split
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
51 | ## Test class attribute named split
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
@@ -258,8 +544,19 @@ missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
Safe fix
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
50 50 |
51 51 | ## Test class attribute named split
52 |-Bar.split.split(",")[0] # [missing-maxsplit-arg]
52 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
51 | ## Test class attribute named split
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
@@ -268,8 +565,19 @@ missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.rsplit()` and pass `maxsplit=1`
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
Safe fix
50 50 |
51 51 | ## Test class attribute named split
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
53 |-Bar.split.split(",")[-1] # [missing-maxsplit-arg]
53 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
56 56 |
missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
@@ -277,8 +585,19 @@ missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
= help: Use `str.split()` and pass `maxsplit=1`
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
Safe fix
51 51 | ## Test class attribute named split
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 |-Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
54 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
56 56 |
57 57 | ## Test unpacked dict literal kwargs
missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
@@ -287,15 +606,37 @@ missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
56 |
57 | ## Test unpacked dict literal kwargs
|
= help: Pass `maxsplit=1` into `str.rsplit()`
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Safe fix
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
55 |-Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
55 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
56 56 |
57 57 | ## Test unpacked dict literal kwargs
58 58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
missing_maxsplit_arg.py:58:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
57 | ## Test unpacked dict literal kwargs
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Unsafe fix
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
56 56 |
57 57 | ## Test unpacked dict literal kwargs
58 |-"1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
58 |+"1,2,3".split(maxsplit=1, **{"sep": ","})[0] # [missing-maxsplit-arg]
59 59 |
60 60 |
61 61 | # OK
missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
177 | # Errors
178 | kwargs_without_maxsplit = {"seq": ","}
@@ -304,8 +645,19 @@ missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Unsafe fix
176 176 | ## TODO: These require the ability to resolve a dict variable name to a value
177 177 | # Errors
178 178 | kwargs_without_maxsplit = {"seq": ","}
179 |-"1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
179 |+"1,2,3".split(maxsplit=1, **kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
180 180 | # OK
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
180 | # OK
181 | kwargs_with_maxsplit = {"maxsplit": 1}
@@ -314,11 +666,29 @@ missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
= help: Pass `maxsplit=1` into `str.split()`
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
Unsafe fix
179 179 | "1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
180 180 | # OK
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
182 |-"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
182 |+"1,2,3".split(",", maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
184 184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
= help: Pass `maxsplit=1` into `str.split()`
Unsafe fix
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
184 |-"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
184 |+"1,2,3".split(maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive

View File

@@ -180,18 +180,18 @@ fn is_valid_argument_type(
typing::is_float(binding, semantic),
)
})
.unwrap_or_default()
.unwrap_or((false, false))
} else {
(false, false)
};
match (method_name, constructor) {
// Decimal.from_float accepts int, bool, float
// Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap)
(MethodName::FromFloat, Constructor::Decimal) => match resolved_type {
ResolvedPythonType::Atom(PythonType::Number(
NumberLike::Integer | NumberLike::Bool | NumberLike::Float,
NumberLike::Integer | NumberLike::Bool,
)) => true,
ResolvedPythonType::Unknown => is_int || is_float,
ResolvedPythonType::Unknown => is_int,
_ => false,
},
// Fraction.from_float accepts int, bool, float
@@ -286,10 +286,8 @@ fn handle_non_finite_float_special_case(
let [float_arg] = arguments.args.as_ref() else {
return None;
};
as_non_finite_float_string_literal(float_arg)?;
let replacement_arg = checker.locator().slice(float_arg).to_string();
let replacement_text = format!("{constructor_name}({replacement_arg})");
let normalized = as_non_finite_float_string_literal(float_arg)?;
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
Some(Edit::range_replacement(replacement_text, call.range()))
}

View File

@@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct
|
= help: Replace with `Decimal` constructor
Safe fix
Unsafe fix
10 10 | _ = fractions.Fraction.from_float(4.2)
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
@@ -179,7 +179,7 @@ FURB164.py:14:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
= help: Replace with `Decimal` constructor
Safe fix
Unsafe fix
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
@@ -200,7 +200,7 @@ FURB164.py:15:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
= help: Replace with `Decimal` constructor
Safe fix
Unsafe fix
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
14 14 | _ = Decimal.from_float(0.1)
@@ -221,7 +221,7 @@ FURB164.py:16:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
= help: Replace with `Decimal` constructor
Safe fix
Unsafe fix
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
14 14 | _ = Decimal.from_float(0.1)
15 15 | _ = Decimal.from_float(-0.5)
@@ -242,7 +242,7 @@ FURB164.py:17:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
= help: Replace with `Decimal` constructor
Safe fix
Unsafe fix
14 14 | _ = Decimal.from_float(0.1)
15 15 | _ = Decimal.from_float(-0.5)
16 16 | _ = Decimal.from_float(5.0)
@@ -310,7 +310,7 @@ FURB164.py:20:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
18 18 | _ = Decimal.from_float(float("inf"))
19 19 | _ = Decimal.from_float(float("-inf"))
20 |-_ = Decimal.from_float(float("Infinity"))
20 |+_ = Decimal("Infinity")
20 |+_ = Decimal("infinity")
21 21 | _ = Decimal.from_float(float("-Infinity"))
22 22 | _ = Decimal.from_float(float("nan"))
23 23 | _ = Decimal.from_float(float("-NaN"))
@@ -331,7 +331,7 @@ FURB164.py:21:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
19 19 | _ = Decimal.from_float(float("-inf"))
20 20 | _ = Decimal.from_float(float("Infinity"))
21 |-_ = Decimal.from_float(float("-Infinity"))
21 |+_ = Decimal("-Infinity")
21 |+_ = Decimal("-infinity")
22 22 | _ = Decimal.from_float(float("nan"))
23 23 | _ = Decimal.from_float(float("-NaN"))
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
@@ -373,7 +373,7 @@ FURB164.py:23:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
21 21 | _ = Decimal.from_float(float("-Infinity"))
22 22 | _ = Decimal.from_float(float("nan"))
23 |-_ = Decimal.from_float(float("-NaN"))
23 |+_ = Decimal("-NaN")
23 |+_ = Decimal("-nan")
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
25 25 | _ = Decimal.from_float(float(" iNf\n\t "))
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
@@ -394,7 +394,7 @@ FURB164.py:24:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
22 22 | _ = Decimal.from_float(float("nan"))
23 23 | _ = Decimal.from_float(float("-NaN"))
24 |-_ = Decimal.from_float(float(" \n+nan \t"))
24 |+_ = Decimal(" \n+nan \t")
24 |+_ = Decimal("+nan")
25 25 | _ = Decimal.from_float(float(" iNf\n\t "))
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
27 27 | _ = Decimal.from_float(float(" InfinIty\n\t "))
@@ -415,7 +415,7 @@ FURB164.py:25:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
23 23 | _ = Decimal.from_float(float("-NaN"))
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
25 |-_ = Decimal.from_float(float(" iNf\n\t "))
25 |+_ = Decimal(" iNf\n\t ")
25 |+_ = Decimal("inf")
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
27 27 | _ = Decimal.from_float(float(" InfinIty\n\t "))
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
@@ -436,7 +436,7 @@ FURB164.py:26:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
25 25 | _ = Decimal.from_float(float(" iNf\n\t "))
26 |-_ = Decimal.from_float(float(" -inF\n \t"))
26 |+_ = Decimal(" -inF\n \t")
26 |+_ = Decimal("-inf")
27 27 | _ = Decimal.from_float(float(" InfinIty\n\t "))
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
29 29 |
@@ -456,7 +456,7 @@ FURB164.py:27:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
25 25 | _ = Decimal.from_float(float(" iNf\n\t "))
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
27 |-_ = Decimal.from_float(float(" InfinIty\n\t "))
27 |+_ = Decimal(" InfinIty\n\t ")
27 |+_ = Decimal("infinity")
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
29 29 |
30 30 | # Cases with keyword arguments - should produce unsafe fixes
@@ -477,7 +477,7 @@ FURB164.py:28:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
27 27 | _ = Decimal.from_float(float(" InfinIty\n\t "))
28 |-_ = Decimal.from_float(float(" -InfinIty\n \t"))
28 |+_ = Decimal(" -InfinIty\n \t")
28 |+_ = Decimal("-infinity")
29 29 |
30 30 | # Cases with keyword arguments - should produce unsafe fixes
31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2"))
@@ -612,3 +612,96 @@ FURB164.py:45:5: FURB164 [*] Verbose method `from_float` in `Fraction` construct
46 46 |
47 47 | # OK - should not trigger the rule
48 48 | _ = Fraction(0.1)
FURB164.py:60:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
59 | # Cases with int and bool - should produce safe fixes
60 | _ = Decimal.from_float(1)
| ^^^^^^^^^^^^^^^^^^^^^ FURB164
61 | _ = Decimal.from_float(True)
|
= help: Replace with `Decimal` constructor
Safe fix
57 57 | _ = decimal.Decimal(4.2)
58 58 |
59 59 | # Cases with int and bool - should produce safe fixes
60 |-_ = Decimal.from_float(1)
60 |+_ = Decimal(1)
61 61 | _ = Decimal.from_float(True)
62 62 |
63 63 | # Cases with non-finite floats - should produce safe fixes
FURB164.py:61:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
59 | # Cases with int and bool - should produce safe fixes
60 | _ = Decimal.from_float(1)
61 | _ = Decimal.from_float(True)
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
62 |
63 | # Cases with non-finite floats - should produce safe fixes
|
= help: Replace with `Decimal` constructor
Safe fix
58 58 |
59 59 | # Cases with int and bool - should produce safe fixes
60 60 | _ = Decimal.from_float(1)
61 |-_ = Decimal.from_float(True)
61 |+_ = Decimal(True)
62 62 |
63 63 | # Cases with non-finite floats - should produce safe fixes
64 64 | _ = Decimal.from_float(float("-nan"))
FURB164.py:64:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
63 | # Cases with non-finite floats - should produce safe fixes
64 | _ = Decimal.from_float(float("-nan"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
65 | _ = Decimal.from_float(float("\x2dnan"))
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
= help: Replace with `Decimal` constructor
Safe fix
61 61 | _ = Decimal.from_float(True)
62 62 |
63 63 | # Cases with non-finite floats - should produce safe fixes
64 |-_ = Decimal.from_float(float("-nan"))
64 |+_ = Decimal("-nan")
65 65 | _ = Decimal.from_float(float("\x2dnan"))
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
FURB164.py:65:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
63 | # Cases with non-finite floats - should produce safe fixes
64 | _ = Decimal.from_float(float("-nan"))
65 | _ = Decimal.from_float(float("\x2dnan"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
= help: Replace with `Decimal` constructor
Safe fix
62 62 |
63 63 | # Cases with non-finite floats - should produce safe fixes
64 64 | _ = Decimal.from_float(float("-nan"))
65 |-_ = Decimal.from_float(float("\x2dnan"))
65 |+_ = Decimal("-nan")
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
FURB164.py:66:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
64 | _ = Decimal.from_float(float("-nan"))
65 | _ = Decimal.from_float(float("\x2dnan"))
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
= help: Replace with `Decimal` constructor
Safe fix
63 63 | # Cases with non-finite floats - should produce safe fixes
64 64 | _ = Decimal.from_float(float("-nan"))
65 65 | _ = Decimal.from_float(float("\x2dnan"))
66 |-_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
66 |+_ = Decimal("-nan")

View File

@@ -178,11 +178,10 @@ pub(crate) fn ambiguous_unicode_character_comment(
context: &LintContext,
locator: &Locator,
range: TextRange,
settings: &LinterSettings,
) {
let text = locator.slice(range);
for candidate in ambiguous_unicode_character(text, range, settings) {
candidate.into_diagnostic(Context::Comment, settings, context);
for candidate in ambiguous_unicode_character(text, range, context.settings()) {
candidate.into_diagnostic(Context::Comment, context);
}
}
@@ -342,13 +341,12 @@ impl Candidate {
}
}
fn into_diagnostic(
self,
context: Context,
settings: &LinterSettings,
lint_context: &LintContext,
) {
if !settings.allowed_confusables.contains(&self.confusable) {
fn into_diagnostic(self, context: Context, lint_context: &LintContext) {
if !lint_context
.settings()
.allowed_confusables
.contains(&self.confusable)
{
let char_range = TextRange::at(self.offset, self.confusable.text_len());
match context {
Context::String => lint_context.report_diagnostic_if_enabled(

View File

@@ -199,14 +199,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:13
@@ -228,14 +228,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:2:22
@@ -344,14 +344,14 @@ mod tests {
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:18
@@ -413,14 +413,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:2888:7
--> stdlib/builtins.pyi:2890:7
|
2886 | """See PEP 585"""
2887 |
2888 | class dict(MutableMapping[_KT, _VT]):
2888 | """See PEP 585"""
2889 |
2890 | class dict(MutableMapping[_KT, _VT]):
| ^^^^
2889 | """dict() -> new empty dictionary
2890 | dict(mapping) -> new dictionary initialized from a mapping object's
2891 | """dict() -> new empty dictionary
2892 | dict(mapping) -> new dictionary initialized from a mapping object's
|
info: Source
--> main.py:6:5
@@ -444,14 +444,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17
@@ -537,14 +537,14 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:4:27
@@ -568,13 +568,13 @@ f(**kwargs<CURSOR>)
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> stdlib/types.pyi:921:11
--> stdlib/types.pyi:922:11
|
919 | if sys.version_info >= (3, 10):
920 | @final
921 | class NoneType:
920 | if sys.version_info >= (3, 10):
921 | @final
922 | class NoneType:
| ^^^^^^^^
922 | """The type of the None singleton."""
923 | """The type of the None singleton."""
|
info: Source
--> main.py:3:17
@@ -585,14 +585,14 @@ f(**kwargs<CURSOR>)
|
info[goto-type-definition]: Type definition
--> stdlib/builtins.pyi:890:7
--> stdlib/builtins.pyi:892:7
|
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
889 |
890 | class str(Sequence[str]):
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
891 |
892 | class str(Sequence[str]):
| ^^^
891 | """str(object='') -> str
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
893 | """str(object='') -> str
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
info: Source
--> main.py:3:17

View File

@@ -6,20 +6,20 @@
//! types, and documentation. It supports multiple signatures for union types
//! and overloads.
use crate::{Db, docstring::get_parameter_documentation, find_node::covering_node};
use crate::{
Db, docstring::get_parameter_documentation, find_node::covering_node, stub_mapping::StubMapper,
};
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::semantic_index::definition::Definition;
use ty_python_semantic::types::{CallSignatureDetails, call_signature_details};
// Limitations of the current implementation:
// TODO - If the target function is declared in a stub file but defined (implemented)
// in a source file, the documentation will not reflect the a docstring that appears
// only in the implementation. To do this, we'll need to map the function or
// method in the stub to the implementation and extract the docstring from there.
// TODO: We may want to add special-case handling for calls to constructors
// so the class docstring is used in place of (or inaddition to) any docstring
// associated with the __new__ or __init__ call.
/// Information about a function parameter
#[derive(Debug, Clone)]
@@ -66,10 +66,6 @@ pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<Signa
// Get the call expression at the given position.
let (call_expr, current_arg_index) = get_call_expr(&parsed, offset)?;
if offset >= call_expr.end() {
return None;
}
// Get signature details from the semantic analyzer.
let signature_details: Vec<CallSignatureDetails<'_>> =
call_signature_details(db, file, call_expr);
@@ -101,11 +97,19 @@ fn get_call_expr(
parsed: &ruff_db::parsed::ParsedModuleRef,
offset: TextSize,
) -> Option<(&ast::ExprCall, usize)> {
let root_node: AnyNodeRef = parsed.syntax().into();
// Create a range from the offset for the covering_node function.
let range = TextRange::new(offset, offset);
// Use length 1 if it fits within the root node, otherwise use zero-length range.
let one_char_range = TextRange::at(offset, TextSize::from(1));
let range = if root_node.range().contains_range(one_char_range) {
one_char_range
} else {
TextRange::at(offset, TextSize::from(0))
};
// Find the covering node at the given position that is a function call.
let covering_node = covering_node(parsed.syntax().into(), range)
let covering_node = covering_node(root_node, range)
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
.ok()?;
@@ -181,11 +185,32 @@ fn create_signature_details_from_call_signature_details(
/// Determine appropriate documentation for a callable type based on its original type.
fn get_callable_documentation(db: &dyn crate::Db, definition: Option<Definition>) -> String {
// TODO: If the definition is located within a stub file and no docstring
// is present, try to map the symbol to an implementation file and extract
// the docstring from that location.
if let Some(definition) = definition {
definition.docstring(db).unwrap_or_default()
// First try to get the docstring from the original definition
let original_docstring = definition.docstring(db);
// If we got a docstring from the original definition, use it
if let Some(docstring) = original_docstring {
return docstring;
}
// If the definition is located within a stub file and no docstring
// is present, try to map the symbol to an implementation file and extract
// the docstring from that location.
let stub_mapper = StubMapper::new(db);
let resolved_definition = ResolvedDefinition::Definition(definition);
// Try to find the corresponding implementation definition
for mapped_definition in stub_mapper.map_definition(resolved_definition) {
if let ResolvedDefinition::Definition(impl_definition) = mapped_definition {
if let Some(impl_docstring) = impl_definition.docstring(db) {
return impl_docstring;
}
}
}
// Fall back to empty string if no docstring found anywhere
String::new()
} else {
String::new()
}
@@ -683,6 +708,95 @@ mod tests {
);
}
#[test]
fn signature_help_after_closing_paren() {
let test = cursor_test(
r#"
def func1(v: str) -> str:
return v
r = func1("")<CURSOR>
print(r)
"#,
);
let result = test.signature_help();
assert!(
result.is_none(),
"Signature help should return None after closing paren"
);
}
#[test]
fn signature_help_after_nested_closing_paren() {
let test = cursor_test(
r#"
def inner_func(x: str) -> str:
return x.upper()
def outer_func(a: int, b: str) -> str:
return f"{a}: {b}"
result = outer_func(42, inner_func("hello")<CURSOR>
"#,
);
// Should return signature help for the outer function call
// even though cursor is after the closing paren of the inner call
let result = test
.signature_help()
.expect("Should have signature help for outer function");
assert_eq!(result.signatures.len(), 1);
let signature = &result.signatures[0];
assert!(signature.label.contains("a: int") && signature.label.contains("b: str"));
// Should be on the second parameter (b: str) since we're after the inner call
assert_eq!(signature.active_parameter, Some(1));
assert_eq!(result.active_signature, Some(0));
}
#[test]
fn signature_help_stub_to_implementation_mapping() {
// Test that when a function is called from a stub file with no docstring,
// the signature help includes the docstring from the corresponding implementation file
let test = CursorTest::builder()
.source(
"main.py",
r#"
from lib import func
result = func(<CURSOR>
"#,
)
.source(
"lib.pyi",
r#"
def func() -> str: ...
"#,
)
.source(
"lib.py",
r#"
def func() -> str:
"""This function does something."""
return ""
"#,
)
.build();
let result = test.signature_help().expect("Should have signature help");
assert_eq!(result.signatures.len(), 1);
let signature = &result.signatures[0];
assert_eq!(signature.label, "() -> str");
let expected_docstring = "This function does something.";
assert_eq!(
signature.documentation,
Some(expected_docstring.to_string())
);
}
impl CursorTest {
fn signature_help(&self) -> Option<SignatureHelpInfo> {
crate::signature_help::signature_help(&self.db, self.cursor.file, self.cursor.offset)

View File

@@ -559,6 +559,22 @@ class Answer(Enum):
reveal_type(enum_members(Answer))
```
## Subclasses of `enum.Flag`
```py
from enum import Flag, auto
class KeyModifier(Flag):
SHIFT = auto()
CTRL = auto()
ALT = auto()
reveal_type(KeyModifier.SHIFT) # revealed: Literal[KeyModifier.SHIFT]
# TODO: this should be `KeyModifier`
reveal_type(KeyModifier.SHIFT | KeyModifier.CTRL) # revealed: Literal[KeyModifier.CTRL]
```
## Custom enum types
Enum classes can also be defined using a subclass of `enum.Enum` or any class that uses

View File

@@ -433,6 +433,8 @@ def f(cond: bool) -> str:
<!-- snapshot-diagnostics -->
### Synchronous
A function with a `yield` or `yield from` expression anywhere in its body is a
[generator function](https://docs.python.org/3/glossary.html#term-generator). A generator function
implicitly returns an instance of `types.GeneratorType` even if it does not contain any `return`
@@ -461,6 +463,8 @@ def j() -> str: # error: [invalid-return-type]
yield 42
```
### Asynchronous
If it is an `async` function with a `yield` statement in its body, it is an
[asynchronous generator function](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
An asynchronous generator function implicitly returns an instance of `types.AsyncGeneratorType` even

View File

@@ -29,16 +29,16 @@ error[invalid-argument-type]: Argument to function `loads` is incorrect
| ^ Expected `str | bytes | bytearray`, found `Literal[5]`
|
info: Function defined here
--> stdlib/json/__init__.pyi:218:5
--> stdlib/json/__init__.pyi:220:5
|
216 | """
217 |
218 | def loads(
218 | """
219 |
220 | def loads(
| ^^^^^
219 | s: str | bytes | bytearray,
221 | s: str | bytes | bytearray,
| -------------------------- Parameter declared here
220 | *,
221 | cls: type[JSONDecoder] | None = None,
222 | *,
223 | cls: type[JSONDecoder] | None = None,
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -0,0 +1,50 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: return_type.md - Function return type - Generator functions - Asynchronous
mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
---
# Python source files
## mdtest_snippet.py
```
1 | import types
2 | import typing
3 |
4 | async def f() -> types.AsyncGeneratorType:
5 | yield 42
6 |
7 | async def g() -> typing.AsyncGenerator:
8 | yield 42
9 |
10 | async def h() -> typing.AsyncIterator:
11 | yield 42
12 |
13 | async def i() -> typing.AsyncIterable:
14 | yield 42
15 |
16 | async def j() -> str: # error: [invalid-return-type]
17 | yield 42
```
# Diagnostics
```
error[invalid-return-type]: Return type does not match returned value
--> src/mdtest_snippet.py:16:18
|
14 | yield 42
15 |
16 | async def j() -> str: # error: [invalid-return-type]
| ^^^ expected `str`, found `types.AsyncGeneratorType`
17 | yield 42
|
info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function
info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details
info: rule `invalid-return-type` is enabled by default
```

View File

@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: return_type.md - Function return type - Generator functions
mdtest name: return_type.md - Function return type - Generator functions - Synchronous
mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
---
@@ -32,23 +32,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md
18 |
19 | def j() -> str: # error: [invalid-return-type]
20 | yield 42
21 | import types
22 | import typing
23 |
24 | async def f() -> types.AsyncGeneratorType:
25 | yield 42
26 |
27 | async def g() -> typing.AsyncGenerator:
28 | yield 42
29 |
30 | async def h() -> typing.AsyncIterator:
31 | yield 42
32 |
33 | async def i() -> typing.AsyncIterable:
34 | yield 42
35 |
36 | async def j() -> str: # error: [invalid-return-type]
37 | yield 42
```
# Diagnostics
@@ -62,26 +45,9 @@ error[invalid-return-type]: Return type does not match returned value
19 | def j() -> str: # error: [invalid-return-type]
| ^^^ expected `str`, found `types.GeneratorType`
20 | yield 42
21 | import types
|
info: Function is inferred as returning `types.GeneratorType` because it is a generator function
info: See https://docs.python.org/3/glossary.html#term-generator for more details
info: rule `invalid-return-type` is enabled by default
```
```
error[invalid-return-type]: Return type does not match returned value
--> src/mdtest_snippet.py:36:18
|
34 | yield 42
35 |
36 | async def j() -> str: # error: [invalid-return-type]
| ^^^ expected `str`, found `types.AsyncGeneratorType`
37 | yield 42
|
info: Function is inferred as returning `types.AsyncGeneratorType` because it is an async generator function
info: See https://docs.python.org/3/glossary.html#term-asynchronous-generator for more details
info: rule `invalid-return-type` is enabled by default
```

View File

@@ -1026,10 +1026,14 @@ impl<'db> Bindings<'db> {
// `tuple(range(42))` => `tuple[int, ...]`
// BUT `tuple((1, 2))` => `tuple[Literal[1], Literal[2]]` rather than `tuple[Literal[1, 2], ...]`
if let [Some(argument)] = overload.parameter_types() {
let tuple_spec = argument.try_iterate(db).expect(
"try_iterate() should not fail on a type \
assignable to `Iterable`",
);
let Ok(tuple_spec) = argument.try_iterate(db) else {
tracing::debug!(
"type" = %argument.display(db),
"try_iterate() should not fail on a type \
assignable to `Iterable`",
);
continue;
};
overload.set_return_type(Type::tuple(TupleType::new(
db,
tuple_spec.as_ref(),

View File

@@ -383,7 +383,9 @@ impl<'db> Signature<'db> {
inherited_generic_context: self
.inherited_generic_context
.map(|ctx| ctx.normalized_impl(db, visitor)),
definition: self.definition,
// Discard the definition when normalizing, so that two equivalent signatures
// with different `Definition`s share the same Salsa ID when normalized
definition: None,
parameters: self
.parameters
.iter()

View File

@@ -218,7 +218,10 @@ where
return;
}
let result = ruff_db::panic::catch_unwind(|| R::run(snapshot, client, params));
let result = ruff_db::panic::catch_unwind(|| {
let snapshot = snapshot;
R::run(snapshot.0, client, params)
});
if let Some(response) = request_result_to_response::<R>(&id, client, result, retry) {
respond::<R>(&id, response, client);

View File

@@ -1,5 +1,3 @@
use std::panic::AssertUnwindSafe;
use lsp_types::request::WorkspaceDiagnosticRequest;
use lsp_types::{
FullDocumentDiagnosticReport, Url, WorkspaceDiagnosticParams, WorkspaceDiagnosticReport,
@@ -25,7 +23,7 @@ impl RequestHandler for WorkspaceDiagnosticRequestHandler {
impl BackgroundRequestHandler for WorkspaceDiagnosticRequestHandler {
fn run(
snapshot: AssertUnwindSafe<SessionSnapshot>,
snapshot: SessionSnapshot,
_client: &Client,
_params: WorkspaceDiagnosticParams,
) -> Result<WorkspaceDiagnosticReportResult> {

View File

@@ -1,5 +1,3 @@
use std::panic::AssertUnwindSafe;
use lsp_types::request::WorkspaceSymbolRequest;
use lsp_types::{WorkspaceSymbolParams, WorkspaceSymbolResponse};
use ty_ide::{WorkspaceSymbolInfo, workspace_symbols};
@@ -21,7 +19,7 @@ impl RequestHandler for WorkspaceSymbolRequestHandler {
impl BackgroundRequestHandler for WorkspaceSymbolRequestHandler {
fn run(
snapshot: AssertUnwindSafe<SessionSnapshot>,
snapshot: SessionSnapshot,
_client: &Client,
params: WorkspaceSymbolParams,
) -> crate::server::Result<Option<WorkspaceSymbolResponse>> {

View File

@@ -35,8 +35,6 @@
use std::borrow::Cow;
use std::panic::AssertUnwindSafe;
use crate::session::client::Client;
use crate::session::{DocumentSnapshot, Session, SessionSnapshot};
@@ -109,7 +107,7 @@ pub(super) trait BackgroundDocumentRequestHandler: RetriableRequestHandler {
/// diagnostics.
pub(super) trait BackgroundRequestHandler: RetriableRequestHandler {
fn run(
snapshot: AssertUnwindSafe<SessionSnapshot>,
snapshot: SessionSnapshot,
client: &Client,
params: <<Self as RequestHandler>::RequestType as Request>::Params,
) -> super::Result<<<Self as RequestHandler>::RequestType as Request>::Result>;

View File

@@ -25,9 +25,9 @@ Typeshed supports Python versions 3.9 to 3.14.
## Using
If you're just using a type checker ([mypy](https://github.com/python/mypy/),
[pyright](https://github.com/microsoft/pyright),
[pytype](https://github.com/google/pytype/), PyCharm, ...), as opposed to
If you're just using a type checker (e.g. [mypy](https://github.com/python/mypy/),
[pyright](https://github.com/microsoft/pyright), or PyCharm's built-in type
checker), as opposed to
developing it, you don't need to interact with the typeshed repo at
all: a copy of standard library part of typeshed is bundled with type checkers.
And type stubs for third party packages and modules you are using can

View File

@@ -1 +1 @@
08225953c98cfd375d80bc88865e5aae77d2c07f
9ab7fde0a0cd24ed7a72837fcb21093b811b80d8

View File

@@ -124,6 +124,7 @@ compileall: 3.0-
compression: 3.14-
concurrent: 3.2-
concurrent.futures.interpreter: 3.14-
concurrent.interpreters: 3.14-
configparser: 3.0-
contextlib: 3.0-
contextvars: 3.7-

View File

@@ -142,7 +142,7 @@ class _PyCPointerType(_CTypeBaseType):
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
def set_type(self, type: Any, /) -> None: ...
def set_type(self, type: _CTypeBaseType, /) -> None: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]

View File

@@ -69,6 +69,7 @@ if sys.version_info >= (3, 11):
on entry to the with-statement and restore the previous default context when
exiting the with-statement. If no context is specified, a copy of the current
default context is used.
"""
else:
@@ -77,6 +78,7 @@ else:
on entry to the with-statement and restore the previous default context when
exiting the with-statement. If no context is specified, a copy of the current
default context is used.
"""
if sys.version_info >= (3, 14):
@@ -84,6 +86,7 @@ if sys.version_info >= (3, 14):
"""Return a context object initialized to the proper values for one of the
IEEE interchange formats. The argument must be a multiple of 32 and less
than IEEE_CONTEXT_MAX_BITS.
"""
DefaultContext: Context

View File

@@ -32,6 +32,7 @@ def __import__(
being imported (e.g. ``from module import <fromlist>``). The 'level'
argument represents the package location to import from in a relative
import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
"""
def spec_from_loader(
@@ -80,6 +81,7 @@ class ModuleSpec:
Only finders (see importlib.abc.MetaPathFinder and
importlib.abc.PathEntryFinder) should modify ModuleSpec instances.
"""
def __init__(
@@ -109,6 +111,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
All methods are either class or static methods to avoid the need to
instantiate the class.
"""
# MetaPathFinder
@@ -120,6 +123,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
If 'path' is ever specified then the search is considered a failure.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
@@ -136,6 +140,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
"""Load the specified module into sys.modules and return it.
This method is deprecated. Use loader.exec_module() instead.
"""
@classmethod
@@ -152,6 +157,7 @@ class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader)
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
if sys.version_info >= (3, 10):
@staticmethod
@@ -175,6 +181,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
All methods are either class or static methods to avoid the need to
instantiate the class.
"""
# MetaPathFinder
@@ -184,6 +191,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
"""Find a frozen module.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
@@ -200,6 +208,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
"""Load a frozen module.
This method is deprecated. Use exec_module() instead.
"""
@classmethod
@@ -216,6 +225,7 @@ class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
if sys.version_info >= (3, 10):
@staticmethod

View File

@@ -51,6 +51,7 @@ def cache_from_source(path: StrPath, debug_override: bool | None = None, *, opti
while a False value is equivalent to setting 'optimization' to '1'.
If sys.implementation.cache_tag is None then NotImplementedError is raised.
"""
def source_from_cache(path: StrPath) -> str:
@@ -60,6 +61,7 @@ def source_from_cache(path: StrPath) -> str:
the .py file calculated to correspond to the .pyc file. If path does
not conform to PEP 3147/488 format, ValueError will be raised. If
sys.implementation.cache_tag is None then NotImplementedError is raised.
"""
def decode_source(source_bytes: ReadableBuffer) -> str:
@@ -83,6 +85,7 @@ def spec_from_file_location(
import system.
The loader must take a spec as its only __init__() arg.
"""
@deprecated(
@@ -98,6 +101,7 @@ class WindowsRegistryFinder(importlib.abc.MetaPathFinder):
"""Find module named in the registry.
This method is deprecated. Use find_spec() instead.
"""
@classmethod
@@ -123,7 +127,8 @@ class PathFinder(importlib.abc.MetaPathFinder):
if sys.version_info >= (3, 10):
@staticmethod
def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]:
"""Find distributions.
"""
Find distributions.
Return an iterable of all Distribution instances capable of
loading the metadata for packages matching ``context.name``
@@ -133,7 +138,8 @@ class PathFinder(importlib.abc.MetaPathFinder):
else:
@classmethod
def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]:
"""Find distributions.
"""
Find distributions.
Return an iterable of all Distribution instances capable of
loading the metadata for packages matching ``context.name``
@@ -156,6 +162,7 @@ class PathFinder(importlib.abc.MetaPathFinder):
sys.path_importer_cache.
This method is deprecated. Use find_spec() instead.
"""
SOURCE_SUFFIXES: list[str]
@@ -169,6 +176,7 @@ class FileFinder(importlib.abc.PathEntryFinder):
Interactions with the file system are cached for performance, being
refreshed when the directory the finder is handling has been modified.
"""
path: str
@@ -188,6 +196,7 @@ class FileFinder(importlib.abc.PathEntryFinder):
If the path called on the closure is not a directory, ImportError is
raised.
"""
class _LoaderBasics:
@@ -252,6 +261,7 @@ class SourceLoader(_LoaderBasics):
Reading of bytecode requires path_stats to be implemented. To write
bytecode, set_data must also be implemented.
"""
class FileLoader:
@@ -276,6 +286,7 @@ class FileLoader:
"""Load a module from a file.
This method is deprecated. Use exec_module() instead.
"""
if sys.version_info >= (3, 10):
def get_resource_reader(self, name: str | None = None) -> importlib.readers.FileReader: ...
@@ -318,6 +329,7 @@ class ExtensionFileLoader(FileLoader, _LoaderBasics, importlib.abc.ExecutionLoad
"""Loader for extension modules.
The constructor is designed to work with FileFinder.
"""
def __init__(self, name: str, path: str) -> None: ...
@@ -356,6 +368,7 @@ if sys.version_info >= (3, 11):
"""Load a namespace module.
This method is deprecated. Use exec_module() instead.
"""
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
@@ -366,6 +379,7 @@ if sys.version_info >= (3, 11):
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
_NamespaceLoader = NamespaceLoader
@@ -386,6 +400,7 @@ else:
"""Load a namespace module.
This method is deprecated. Use exec_module() instead.
"""
if sys.version_info >= (3, 10):
@staticmethod
@@ -394,6 +409,7 @@ else:
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
@@ -404,6 +420,7 @@ else:
"""Return repr for the module.
The method is deprecated. The import machinery does the job itself.
"""
if sys.version_info >= (3, 13):

View File

@@ -4,9 +4,11 @@ The 'interpreters' module provides a more convenient interface.
import types
from collections.abc import Callable
from typing import Any, Final, Literal, SupportsIndex
from typing import Any, Final, Literal, SupportsIndex, TypeVar
from typing_extensions import TypeAlias
_R = TypeVar("_R")
_Configs: TypeAlias = Literal["default", "isolated", "legacy", "empty", ""]
_SharedDict: TypeAlias = dict[str, Any] # many objects can be shared
@@ -88,7 +90,7 @@ def get_config(id: SupportsIndex, *, restrict: bool = False) -> types.SimpleName
Return a representation of the config used to initialize the interpreter.
"""
def whence(id: SupportsIndex) -> int:
def whence(id: SupportsIndex) -> _Whence:
"""whence(id) -> int
Return an identifier for where the interpreter was created.
@@ -120,12 +122,12 @@ def exec(
def call(
id: SupportsIndex,
callable: Callable[..., object],
callable: Callable[..., _R],
args: tuple[object, ...] | None = None,
kwargs: dict[str, object] | None = None,
*,
restrict: bool = False,
) -> object:
) -> tuple[_R, types.SimpleNamespace]:
"""call(id, callable, args=None, kwargs=None, *, restrict=False)
Call the provided object in the identified interpreter.
@@ -182,6 +184,7 @@ def capture_exception(exc: BaseException | None = None) -> types.SimpleNamespace
The returned snapshot is the same as what _interpreters.exec() returns.
"""
_Whence: TypeAlias = Literal[0, 1, 2, 3, 4, 5]
WHENCE_UNKNOWN: Final = 0
WHENCE_RUNTIME: Final = 1
WHENCE_LEGACY_CAPI: Final = 2

View File

@@ -82,7 +82,7 @@ def encode_basestring_ascii(s: str, /) -> str:
Return an ASCII-only JSON representation of a Python string
"""
def scanstring(string: str, end: int, strict: bool = ...) -> tuple[str, int]:
def scanstring(string: str, end: int, strict: bool = True) -> tuple[str, int]:
"""scanstring(string, end, strict=True) -> (string, end)
Scan the string s for a JSON string. End is the index of the

View File

@@ -11,7 +11,7 @@ import sys
from _typeshed import SupportsGetItem
from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence
from operator import attrgetter as attrgetter, itemgetter as itemgetter, methodcaller as methodcaller
from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload
from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload, type_check_only
from typing_extensions import ParamSpec, TypeAlias, TypeIs
_R = TypeVar("_R")
@@ -25,12 +25,15 @@ _P = ParamSpec("_P")
# operators can be overloaded to return an arbitrary object. For example,
# the numpy.array comparison dunders return another numpy.array.
@type_check_only
class _SupportsDunderLT(Protocol):
def __lt__(self, other: Any, /) -> Any: ...
@type_check_only
class _SupportsDunderGT(Protocol):
def __gt__(self, other: Any, /) -> Any: ...
@type_check_only
class _SupportsDunderLE(Protocol):
def __le__(self, other: Any, /) -> Any: ...
@@ -39,12 +42,15 @@ class _SupportsDunderGE(Protocol):
_SupportsComparison: TypeAlias = _SupportsDunderLE | _SupportsDunderGE | _SupportsDunderGT | _SupportsDunderLT
@type_check_only
class _SupportsInversion(Protocol[_T_co]):
def __invert__(self) -> _T_co: ...
@type_check_only
class _SupportsNeg(Protocol[_T_co]):
def __neg__(self) -> _T_co: ...
@type_check_only
class _SupportsPos(Protocol[_T_co]):
def __pos__(self) -> _T_co: ...

View File

@@ -56,7 +56,8 @@ def _check_for_unavailable_sdk(_config_vars: dict[str, str]) -> dict[str, str]:
"""Remove references to any SDKs not available"""
def compiler_fixup(compiler_so: Iterable[str], cc_args: Sequence[str]) -> list[str]:
"""This function will strip '-isysroot PATH' and '-arch ARCH' from the
"""
This function will strip '-isysroot PATH' and '-arch ARCH' from the
compile flags if the user has specified one them in extra_compile_flags.
This is needed because '-arch ARCH' adds another architecture to the

View File

@@ -16,8 +16,9 @@ from sqlite3 import (
ProgrammingError as ProgrammingError,
Row as Row,
Warning as Warning,
_IsolationLevel,
)
from typing import Any, Final, Literal, TypeVar, overload
from typing import Any, Final, TypeVar, overload
from typing_extensions import TypeAlias
if sys.version_info >= (3, 11):
@@ -228,7 +229,7 @@ if sys.version_info >= (3, 12):
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
isolation_level: _IsolationLevel = "DEFERRED",
check_same_thread: bool = True,
cached_statements: int = 128,
uri: bool = False,
@@ -251,7 +252,7 @@ if sys.version_info >= (3, 12):
database: StrOrBytesPath,
timeout: float,
detect_types: int,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
isolation_level: _IsolationLevel,
check_same_thread: bool,
factory: type[_ConnectionT],
cached_statements: int = 128,
@@ -264,7 +265,7 @@ if sys.version_info >= (3, 12):
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
isolation_level: _IsolationLevel = "DEFERRED",
check_same_thread: bool = True,
*,
factory: type[_ConnectionT],
@@ -279,7 +280,7 @@ else:
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
isolation_level: _IsolationLevel = "DEFERRED",
check_same_thread: bool = True,
cached_statements: int = 128,
uri: bool = False,
@@ -295,7 +296,7 @@ else:
database: StrOrBytesPath,
timeout: float,
detect_types: int,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
isolation_level: _IsolationLevel,
check_same_thread: bool,
factory: type[_ConnectionT],
cached_statements: int = 128,
@@ -306,7 +307,7 @@ else:
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
isolation_level: _IsolationLevel = "DEFERRED",
check_same_thread: bool = True,
*,
factory: type[_ConnectionT],

View File

@@ -253,7 +253,7 @@ def start_new(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]
def start_new(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
if sys.version_info >= (3, 10):
def interrupt_main(signum: signal.Signals = ..., /) -> None:
def interrupt_main(signum: signal.Signals = signal.SIGINT, /) -> None:
"""Simulate the arrival of the given signal in the main thread,
where the corresponding signal handler will be executed.
If *signum* is omitted, SIGINT is assumed.

View File

@@ -131,7 +131,9 @@ if sys.version_info >= (3, 13):
use: str | None = None,
/,
):
"""wantTk
"""
wantTk
if false, then Tk_Init() doesn't get called
sync
if true, then pass -sync to wish
@@ -151,7 +153,9 @@ else:
use: str | None = None,
/,
):
"""wantTk
"""
wantTk
if false, then Tk_Init() doesn't get called
sync
if true, then pass -sync to wish

View File

@@ -65,10 +65,10 @@ MaybeNone: TypeAlias = Any # stable
# In cases where the sentinel object is exported and can be used by user code,
# a construct like this is better:
#
# _SentinelType = NewType("_SentinelType", object)
# sentinel: _SentinelType
# _SentinelType = NewType("_SentinelType", object) # does not exist at runtime
# sentinel: Final[_SentinelType]
# def foo(x: int | None | _SentinelType = ...) -> None: ...
sentinel: Any
sentinel: Any # stable
# stable
class IdentityFunction(Protocol):

View File

@@ -81,6 +81,7 @@ class abstractclassmethod(classmethod[_T, _P, _R_co]):
@abstractmethod
def my_abstract_classmethod(cls, ...):
...
"""
__isabstractmethod__: Literal[True]
@@ -97,6 +98,7 @@ class abstractstaticmethod(staticmethod[_P, _R_co]):
@abstractmethod
def my_abstract_staticmethod(...):
...
"""
__isabstractmethod__: Literal[True]
@@ -113,6 +115,7 @@ class abstractproperty(property):
@abstractmethod
def my_abstract_property(self):
...
"""
__isabstractmethod__: Literal[True]

View File

@@ -39,6 +39,7 @@ if sys.version_info >= (3, 14):
* owner: The owning object (module, class, or function).
* is_argument: Does nothing, retained for compatibility.
* is_class: True if the forward reference was created in class scope.
"""
__forward_is_argument__: bool
@@ -132,6 +133,7 @@ if sys.version_info >= (3, 14):
class, or function that the __annotate__ function derives from). With the
FORWARDREF format, it is used to provide better evaluation capabilities
on the generated ForwardRef objects.
"""
@overload
@@ -231,6 +233,7 @@ if sys.version_info >= (3, 14):
This is intended as a helper for tools that support the STRING format but do
not have access to the code that originally produced the annotations. It uses
repr() for most objects.
"""
def annotations_to_string(annotations: SupportsItems[str, object]) -> dict[str, str]:

View File

@@ -65,7 +65,7 @@ import sys
from _typeshed import SupportsWrite, sentinel
from collections.abc import Callable, Generator, Iterable, Sequence
from re import Pattern
from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeVar, overload
from typing import IO, Any, ClassVar, Final, Generic, NoReturn, Protocol, TypeVar, overload, type_check_only
from typing_extensions import Self, TypeAlias, deprecated
__all__ = [
@@ -168,7 +168,8 @@ class _ActionsContainer:
version: str = ...,
**kwargs: Any,
) -> Action:
"""add_argument(dest, ..., name=value, ...)
"""
add_argument(dest, ..., name=value, ...)
add_argument(option_string, option_string, ..., name=value, ...)
"""
@@ -193,6 +194,7 @@ class _ActionsContainer:
def _handle_conflict_error(self, action: Action, conflicting_actions: Iterable[tuple[str, Action]]) -> NoReturn: ...
def _handle_conflict_resolve(self, action: Action, conflicting_actions: Iterable[tuple[str, Action]]) -> None: ...
@type_check_only
class _FormatterClass(Protocol):
def __call__(self, *, prog: str) -> HelpFormatter: ...
@@ -401,7 +403,7 @@ class HelpFormatter:
if sys.version_info >= (3, 14):
def __init__(
self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = False
self, prog: str, indent_increment: int = 2, max_help_position: int = 24, width: int | None = None, color: bool = True
) -> None: ...
else:
def __init__(

View File

@@ -2170,7 +2170,8 @@ if sys.version_info >= (3, 13):
feature_version: None | int | tuple[int, int] = None,
optimize: Literal[-1, 0, 1, 2] = -1,
) -> Module:
"""Parse the source into an AST node.
"""
Parse the source into an AST node.
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
Pass type_comments=True to get back type comments where the syntax allows.
"""
@@ -2253,7 +2254,8 @@ else:
type_comments: bool = False,
feature_version: None | int | tuple[int, int] = None,
) -> Module:
"""Parse the source into an AST node.
"""
Parse the source into an AST node.
Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
Pass type_comments=True to get back type comments where the syntax allows.
"""
@@ -2320,7 +2322,8 @@ else:
) -> mod: ...
def literal_eval(node_or_string: str | AST) -> Any:
"""Evaluate an expression node or a string containing only a Python
"""
Evaluate an expression node or a string containing only a Python
expression. The string or node provided may only consist of the following
Python literal structures: strings, bytes, numbers, tuples, lists, dicts,
sets, booleans, and None.
@@ -2337,7 +2340,8 @@ if sys.version_info >= (3, 13):
indent: int | str | None = None,
show_empty: bool = False,
) -> str:
"""Return a formatted dump of the tree in node. This is mainly useful for
"""
Return a formatted dump of the tree in node. This is mainly useful for
debugging purposes. If annotate_fields is true (by default),
the returned string will show the names and the values for fields.
If annotate_fields is false, the result string will be more compact by
@@ -2354,7 +2358,8 @@ else:
def dump(
node: AST, annotate_fields: bool = True, include_attributes: bool = False, *, indent: int | str | None = None
) -> str:
"""Return a formatted dump of the tree in node. This is mainly useful for
"""
Return a formatted dump of the tree in node. This is mainly useful for
debugging purposes. If annotate_fields is true (by default),
the returned string will show the names and the values for fields.
If annotate_fields is false, the result string will be more compact by
@@ -2366,12 +2371,14 @@ else:
"""
def copy_location(new_node: _T, old_node: AST) -> _T:
"""Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
"""
Copy source location (`lineno`, `col_offset`, `end_lineno`, and `end_col_offset`
attributes) from *old_node* to *new_node* if possible, and return *new_node*.
"""
def fix_missing_locations(node: _T) -> _T:
"""When you compile a node tree with compile(), the compiler expects lineno and
"""
When you compile a node tree with compile(), the compiler expects lineno and
col_offset attributes for every node that supports them. This is rather
tedious to fill in for generated nodes, so this helper adds these attributes
recursively where not already set, by setting them to the values of the
@@ -2379,23 +2386,27 @@ def fix_missing_locations(node: _T) -> _T:
"""
def increment_lineno(node: _T, n: int = 1) -> _T:
"""Increment the line number and end line number of each node in the tree
"""
Increment the line number and end line number of each node in the tree
starting at *node* by *n*. This is useful to "move code" to a different
location in a file.
"""
def iter_fields(node: AST) -> Iterator[tuple[str, Any]]:
"""Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
"""
Yield a tuple of ``(fieldname, value)`` for each field in ``node._fields``
that is present on *node*.
"""
def iter_child_nodes(node: AST) -> Iterator[AST]:
"""Yield all direct child nodes of *node*, that is, all fields that are nodes
"""
Yield all direct child nodes of *node*, that is, all fields that are nodes
and all items of fields that are lists of nodes.
"""
def get_docstring(node: AsyncFunctionDef | FunctionDef | ClassDef | Module, clean: bool = True) -> str | None:
"""Return the docstring for the given node or None if no docstring can
"""
Return the docstring for the given node or None if no docstring can
be found. If the node provided does not have docstrings a TypeError
will be raised.
@@ -2414,7 +2425,8 @@ def get_source_segment(source: str, node: AST, *, padded: bool = False) -> str |
"""
def walk(node: AST) -> Iterator[AST]:
"""Recursively yield all descendant nodes in the tree starting at *node*
"""
Recursively yield all descendant nodes in the tree starting at *node*
(including *node* itself), in no specified order. This is useful if you
only want to modify nodes in place and don't care about the context.
"""
@@ -2431,7 +2443,8 @@ if sys.version_info >= (3, 14):
"""
class NodeVisitor:
"""A node visitor base class that walks the abstract syntax tree and calls a
"""
A node visitor base class that walks the abstract syntax tree and calls a
visitor function for every node found. This function may return a value
which is forwarded by the `visit` method.
@@ -2595,7 +2608,8 @@ class NodeVisitor:
def visit_Ellipsis(self, node: Ellipsis) -> Any: ... # type: ignore[deprecated]
class NodeTransformer(NodeVisitor):
"""A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
"""
A :class:`NodeVisitor` subclass that walks the abstract syntax tree and
allows modification of nodes.
The `NodeTransformer` will walk the AST and use the return value of the

View File

@@ -43,14 +43,11 @@ if sys.platform == "win32":
"Server", # from base_events
"iscoroutinefunction", # from coroutines
"iscoroutine", # from coroutines
"_AbstractEventLoopPolicy", # from events
"AbstractEventLoop", # from events
"AbstractServer", # from events
"Handle", # from events
"TimerHandle", # from events
"_get_event_loop_policy", # from events
"get_event_loop_policy", # from events
"_set_event_loop_policy", # from events
"set_event_loop_policy", # from events
"get_event_loop", # from events
"set_event_loop", # from events
@@ -519,14 +516,11 @@ else:
"Server", # from base_events
"iscoroutinefunction", # from coroutines
"iscoroutine", # from coroutines
"_AbstractEventLoopPolicy", # from events
"AbstractEventLoop", # from events
"AbstractServer", # from events
"Handle", # from events
"TimerHandle", # from events
"_get_event_loop_policy", # from events
"get_event_loop_policy", # from events
"_set_event_loop_policy", # from events
"set_event_loop_policy", # from events
"get_event_loop", # from events
"set_event_loop", # from events
@@ -612,7 +606,6 @@ else:
"DatagramTransport", # from transports
"SubprocessTransport", # from transports
"SelectorEventLoop", # from unix_events
"_DefaultEventLoopPolicy", # from unix_events
"EventLoop", # from unix_events
)
elif sys.version_info >= (3, 13):

View File

@@ -1,20 +1,25 @@
from _asyncio import Future
from collections.abc import Callable, Sequence
from contextvars import Context
from typing import Any, Final
from typing_extensions import TypeIs
from . import futures
__all__ = ()
# asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py
# but it leads to circular import error in pytype tool.
# That's why the import order is reversed.
from .futures import isfuture as isfuture
_PENDING: Final = "PENDING" # undocumented
_CANCELLED: Final = "CANCELLED" # undocumented
_FINISHED: Final = "FINISHED" # undocumented
def isfuture(obj: object) -> TypeIs[Future[Any]]:
"""Check for a Future.
This returns True when obj is a Future instance or is advertising
itself as duck-type compatible by setting _asyncio_future_blocking.
See comment in Future for more details.
"""
def _format_callbacks(cb: Sequence[tuple[Callable[[futures.Future[Any]], None], Context]]) -> str: # undocumented
"""helper function for Future.__repr__"""

View File

@@ -30,14 +30,11 @@ if sys.version_info < (3, 14):
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.version_info >= (3, 14):
__all__ = (
"_AbstractEventLoopPolicy",
"AbstractEventLoop",
"AbstractServer",
"Handle",
"TimerHandle",
"_get_event_loop_policy",
"get_event_loop_policy",
"_set_event_loop_policy",
"set_event_loop_policy",
"get_event_loop",
"set_event_loop",

View File

@@ -43,7 +43,8 @@ class SendfileNotAvailableError(RuntimeError):
"""
class IncompleteReadError(EOFError):
"""Incomplete read error. Attributes:
"""
Incomplete read error. Attributes:
- partial: read bytes string before the end of stream was reached
- expected: total number of expected bytes (or None if unknown)

View File

@@ -3,9 +3,9 @@
import sys
from _asyncio import Future as Future
from concurrent.futures._base import Future as _ConcurrentFuture
from typing import Any, TypeVar
from typing_extensions import TypeIs
from typing import TypeVar
from .base_futures import isfuture as isfuture
from .events import AbstractEventLoop
# Keep asyncio.__all__ updated with any changes to __all__ here
@@ -18,16 +18,5 @@ else:
_T = TypeVar("_T")
# asyncio defines 'isfuture()' in base_futures.py and re-imports it in futures.py
# but it leads to circular import error in pytype tool.
# That's why the import order is reversed.
def isfuture(obj: object) -> TypeIs[Future[Any]]:
"""Check for a Future.
This returns True when obj is a Future instance or is advertising
itself as duck-type compatible by setting _asyncio_future_blocking.
See comment in Future for more details.
"""
def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]:
"""Wrap concurrent.futures.Future object."""

View File

@@ -80,6 +80,7 @@ class Lock(_ContextManagerMixin, _LoopBoundMixin):
else:
# lock is acquired
...
"""
_waiters: deque[Future[Any]] | None

View File

@@ -39,6 +39,7 @@ if sys.version_info >= (3, 11):
unittest runners, console tools, -- everywhere when async code
is called from existing sync framework and where the preferred single
asyncio.run() call doesn't work.
"""
def __init__(self, *, debug: bool | None = None, loop_factory: Callable[[], AbstractEventLoop] | None = None) -> None: ...

View File

@@ -66,7 +66,8 @@ if sys.version_info < (3, 11):
_handshake_cb: Callable[[BaseException | None], None] | None
_shutdown_cb: Callable[[], None] | None
def __init__(self, context: ssl.SSLContext, server_side: bool, server_hostname: str | None = None) -> None:
"""The *context* argument specifies the ssl.SSLContext to use.
"""
The *context* argument specifies the ssl.SSLContext to use.
The *server_side* argument indicates whether this is a server side or
client side transport.
@@ -95,7 +96,8 @@ if sys.version_info < (3, 11):
@property
def wrapped(self) -> bool:
"""Whether a security layer is currently in effect.
"""
Whether a security layer is currently in effect.
Return False during handshake.
"""

View File

@@ -58,4 +58,5 @@ async def staggered_race(
``len(exceptions)`` is equal to the number of coroutines actually
started, and the order is the same as in ``coro_fns``. The winning
coroutine's entry is ``None``.
"""

View File

@@ -40,7 +40,8 @@ class CycleFoundException(Exception):
def get_all_awaited_by(pid: SupportsIndex) -> list[_AwaitedInfo]: ...
def build_async_tree(result: Iterable[_AwaitedInfo], task_emoji: str = "(T)", cor_emoji: str = "") -> list[list[str]]:
"""Build a list of strings for pretty-print an async call tree.
"""
Build a list of strings for pretty-print an async call tree.
The call tree is produced by `get_all_async_stacks()`, prefixing tasks
with `task_emoji` and coroutine frames with `cor_emoji`.

View File

@@ -18,7 +18,7 @@ _Ts = TypeVarTuple("_Ts")
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.platform != "win32":
if sys.version_info >= (3, 14):
__all__ = ("SelectorEventLoop", "_DefaultEventLoopPolicy", "EventLoop")
__all__ = ("SelectorEventLoop", "EventLoop")
elif sys.version_info >= (3, 13):
# Adds EventLoop
__all__ = (
@@ -131,6 +131,7 @@ if sys.version_info < (3, 14):
Return True if the watcher is installed and ready to handle process exit
notifications.
"""
else:
@@ -214,6 +215,7 @@ if sys.version_info < (3, 14):
Return True if the watcher is installed and ready to handle process exit
notifications.
"""
if sys.platform != "win32":

View File

@@ -93,10 +93,13 @@ def urlsafe_b64decode(s: str | ReadableBuffer) -> bytes:
"""
def b32encode(s: ReadableBuffer) -> bytes:
"""Encode the bytes-like objects using base32 and return a bytes object."""
"""
Encode the bytes-like objects using base32 and return a bytes object.
"""
def b32decode(s: str | ReadableBuffer, casefold: bool = False, map01: str | ReadableBuffer | None = None) -> bytes:
"""Decode the base32 encoded bytes-like object or ASCII string s.
"""
Decode the base32 encoded bytes-like object or ASCII string s.
Optional casefold is a flag specifying whether a lowercase alphabet is
acceptable as input. For security purposes, the default is False.
@@ -130,10 +133,13 @@ def b16decode(s: str | ReadableBuffer, casefold: bool = False) -> bytes:
if sys.version_info >= (3, 10):
def b32hexencode(s: ReadableBuffer) -> bytes:
"""Encode the bytes-like objects using base32hex and return a bytes object."""
"""
Encode the bytes-like objects using base32hex and return a bytes object.
"""
def b32hexdecode(s: str | ReadableBuffer, casefold: bool = False) -> bytes:
"""Decode the base32hex encoded bytes-like object or ASCII string s.
"""
Decode the base32hex encoded bytes-like object or ASCII string s.
Optional casefold is a flag specifying whether a lowercase alphabet is
acceptable as input. For security purposes, the default is False.

View File

@@ -278,6 +278,7 @@ class Bdb:
return string contains the canonical filename, the function name
or '<lambda>', the input arguments, the return value, and the
line of code (if it exists).
"""
def run(self, cmd: str | CodeType, globals: dict[str, Any] | None = None, locals: Mapping[str, Any] | None = None) -> None:
@@ -374,6 +375,7 @@ class Breakpoint:
The information includes the breakpoint number, temporary
status, file:line position, break condition, number of times to
ignore, and number of times hit.
"""
def checkfuncname(b: Breakpoint, frame: FrameType) -> bool:

View File

@@ -881,9 +881,11 @@ class complex:
def from_number(cls, number: complex | SupportsComplex | SupportsFloat | SupportsIndex, /) -> Self:
"""Convert number to a complex floating-point number."""
@type_check_only
class _FormatMapMapping(Protocol):
def __getitem__(self, key: str, /) -> Any: ...
@type_check_only
class _TranslateTable(Protocol):
def __getitem__(self, key: int, /) -> str | int | None: ...
@@ -3380,6 +3382,7 @@ if sys.version_info >= (3, 10):
def aiter(async_iterable: SupportsAiter[_SupportsAnextT_co], /) -> _SupportsAnextT_co:
"""Return an AsyncIterator for an AsyncIterable object."""
@type_check_only
class _SupportsSynchronousAnext(Protocol[_AwaitableT_co]):
def __anext__(self) -> _AwaitableT_co: ...
@@ -3700,7 +3703,6 @@ def iter(object: Callable[[], _T | None], sentinel: None, /) -> Iterator[_T]: ..
@overload
def iter(object: Callable[[], _T], sentinel: object, /) -> Iterator[_T]: ...
# Keep this alias in sync with unittest.case._ClassInfo
if sys.version_info >= (3, 10):
_ClassInfo: TypeAlias = type | types.UnionType | tuple[_ClassInfo, ...]
else:

View File

@@ -75,6 +75,7 @@ def open(
For text mode, a BZ2File object is created, and wrapped in an
io.TextIOWrapper instance with the specified encoding, error
handling behavior, and line ending(s).
"""
@overload

View File

@@ -87,7 +87,8 @@ def monthrange(year: int, month: int) -> tuple[int, int]:
"""
class Calendar:
"""Base calendar class. This class doesn't do any formatting. It simply
"""
Base calendar class. This class doesn't do any formatting. It simply
provides data to subclasses.
"""
@@ -96,137 +97,183 @@ class Calendar:
def getfirstweekday(self) -> int: ...
def setfirstweekday(self, firstweekday: int) -> None: ...
def iterweekdays(self) -> Iterable[int]:
"""Return an iterator for one week of weekday numbers starting with the
"""
Return an iterator for one week of weekday numbers starting with the
configured first one.
"""
def itermonthdates(self, year: int, month: int) -> Iterable[datetime.date]:
"""Return an iterator for one month. The iterator will yield datetime.date
"""
Return an iterator for one month. The iterator will yield datetime.date
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
"""
def itermonthdays2(self, year: int, month: int) -> Iterable[tuple[int, int]]:
"""Like itermonthdates(), but will yield (day number, weekday number)
"""
Like itermonthdates(), but will yield (day number, weekday number)
tuples. For days outside the specified month the day number is 0.
"""
def itermonthdays(self, year: int, month: int) -> Iterable[int]:
"""Like itermonthdates(), but will yield day numbers. For days outside
"""
Like itermonthdates(), but will yield day numbers. For days outside
the specified month the day number is 0.
"""
def monthdatescalendar(self, year: int, month: int) -> list[list[datetime.date]]:
"""Return a matrix (list of lists) representing a month's calendar.
"""
Return a matrix (list of lists) representing a month's calendar.
Each row represents a week; week entries are datetime.date values.
"""
def monthdays2calendar(self, year: int, month: int) -> list[list[tuple[int, int]]]:
"""Return a matrix representing a month's calendar.
"""
Return a matrix representing a month's calendar.
Each row represents a week; week entries are
(day number, weekday number) tuples. Day numbers outside this month
are zero.
"""
def monthdayscalendar(self, year: int, month: int) -> list[list[int]]:
"""Return a matrix representing a month's calendar.
"""
Return a matrix representing a month's calendar.
Each row represents a week; days outside this month are zero.
"""
def yeardatescalendar(self, year: int, width: int = 3) -> list[list[list[list[datetime.date]]]]:
"""Return the data for the specified year ready for formatting. The return
"""
Return the data for the specified year ready for formatting. The return
value is a list of month rows. Each month row contains up to width months.
Each month contains between 4 and 6 weeks and each week contains 1-7
days. Days are datetime.date objects.
"""
def yeardays2calendar(self, year: int, width: int = 3) -> list[list[list[list[tuple[int, int]]]]]:
"""Return the data for the specified year ready for formatting (similar to
"""
Return the data for the specified year ready for formatting (similar to
yeardatescalendar()). Entries in the week lists are
(day number, weekday number) tuples. Day numbers outside this month are
zero.
"""
def yeardayscalendar(self, year: int, width: int = 3) -> list[list[list[list[int]]]]:
"""Return the data for the specified year ready for formatting (similar to
"""
Return the data for the specified year ready for formatting (similar to
yeardatescalendar()). Entries in the week lists are day numbers.
Day numbers outside this month are zero.
"""
def itermonthdays3(self, year: int, month: int) -> Iterable[tuple[int, int, int]]:
"""Like itermonthdates(), but will yield (year, month, day) tuples. Can be
"""
Like itermonthdates(), but will yield (year, month, day) tuples. Can be
used for dates outside of datetime.date range.
"""
def itermonthdays4(self, year: int, month: int) -> Iterable[tuple[int, int, int, int]]:
"""Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
"""
Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples.
Can be used for dates outside of datetime.date range.
"""
class TextCalendar(Calendar):
"""Subclass of Calendar that outputs a calendar as a simple plain text
"""
Subclass of Calendar that outputs a calendar as a simple plain text
similar to the UNIX program cal.
"""
def prweek(self, theweek: int, width: int) -> None:
"""Print a single week (no newline)."""
"""
Print a single week (no newline).
"""
def formatday(self, day: int, weekday: int, width: int) -> str:
"""Returns a formatted day."""
"""
Returns a formatted day.
"""
def formatweek(self, theweek: int, width: int) -> str:
"""Returns a single week in a string (no newline)."""
"""
Returns a single week in a string (no newline).
"""
def formatweekday(self, day: int, width: int) -> str:
"""Returns a formatted week day name."""
"""
Returns a formatted week day name.
"""
def formatweekheader(self, width: int) -> str:
"""Return a header for a week."""
"""
Return a header for a week.
"""
def formatmonthname(self, theyear: int, themonth: int, width: int, withyear: bool = True) -> str:
"""Return a formatted month name."""
"""
Return a formatted month name.
"""
def prmonth(self, theyear: int, themonth: int, w: int = 0, l: int = 0) -> None:
"""Print a month's calendar."""
"""
Print a month's calendar.
"""
def formatmonth(self, theyear: int, themonth: int, w: int = 0, l: int = 0) -> str:
"""Return a month's calendar string (multi-line)."""
"""
Return a month's calendar string (multi-line).
"""
def formatyear(self, theyear: int, w: int = 2, l: int = 1, c: int = 6, m: int = 3) -> str:
"""Returns a year's calendar as a multi-line string."""
"""
Returns a year's calendar as a multi-line string.
"""
def pryear(self, theyear: int, w: int = 0, l: int = 0, c: int = 6, m: int = 3) -> None:
"""Print a year's calendar."""
def firstweekday() -> int: ...
def monthcalendar(year: int, month: int) -> list[list[int]]:
"""Return a matrix representing a month's calendar.
"""
Return a matrix representing a month's calendar.
Each row represents a week; days outside this month are zero.
"""
def prweek(theweek: int, width: int) -> None:
"""Print a single week (no newline)."""
"""
Print a single week (no newline).
"""
def week(theweek: int, width: int) -> str:
"""Returns a single week in a string (no newline)."""
"""
Returns a single week in a string (no newline).
"""
def weekheader(width: int) -> str:
"""Return a header for a week."""
"""
Return a header for a week.
"""
def prmonth(theyear: int, themonth: int, w: int = 0, l: int = 0) -> None:
"""Print a month's calendar."""
"""
Print a month's calendar.
"""
def month(theyear: int, themonth: int, w: int = 0, l: int = 0) -> str:
"""Return a month's calendar string (multi-line)."""
"""
Return a month's calendar string (multi-line).
"""
def calendar(theyear: int, w: int = 2, l: int = 1, c: int = 6, m: int = 3) -> str:
"""Returns a year's calendar as a multi-line string."""
"""
Returns a year's calendar as a multi-line string.
"""
def prcal(theyear: int, w: int = 0, l: int = 0, c: int = 6, m: int = 3) -> None:
"""Print a year's calendar."""
class HTMLCalendar(Calendar):
"""This calendar returns complete HTML pages."""
"""
This calendar returns complete HTML pages.
"""
cssclasses: ClassVar[list[str]]
cssclass_noday: ClassVar[str]
@@ -236,30 +283,46 @@ class HTMLCalendar(Calendar):
cssclass_year: ClassVar[str]
cssclass_year_head: ClassVar[str]
def formatday(self, day: int, weekday: int) -> str:
"""Return a day as a table cell."""
"""
Return a day as a table cell.
"""
def formatweek(self, theweek: int) -> str:
"""Return a complete week as a table row."""
"""
Return a complete week as a table row.
"""
def formatweekday(self, day: int) -> str:
"""Return a weekday name as a table header."""
"""
Return a weekday name as a table header.
"""
def formatweekheader(self) -> str:
"""Return a header for a week as a table row."""
"""
Return a header for a week as a table row.
"""
def formatmonthname(self, theyear: int, themonth: int, withyear: bool = True) -> str:
"""Return a month name as a table row."""
"""
Return a month name as a table row.
"""
def formatmonth(self, theyear: int, themonth: int, withyear: bool = True) -> str:
"""Return a formatted month as a table."""
"""
Return a formatted month as a table.
"""
def formatyear(self, theyear: int, width: int = 3) -> str:
"""Return a formatted year as a table of tables."""
"""
Return a formatted year as a table of tables.
"""
def formatyearpage(
self, theyear: int, width: int = 3, css: str | None = "calendar.css", encoding: str | None = None
) -> bytes:
"""Return a formatted year as a complete HTML page."""
"""
Return a formatted year as a complete HTML page.
"""
class different_locale:
def __init__(self, locale: _LocaleType) -> None: ...
@@ -267,14 +330,16 @@ class different_locale:
def __exit__(self, *args: Unused) -> None: ...
class LocaleTextCalendar(TextCalendar):
"""This class can be passed a locale name in the constructor and will return
"""
This class can be passed a locale name in the constructor and will return
month and weekday names in the specified locale.
"""
def __init__(self, firstweekday: int = 0, locale: _LocaleType | None = None) -> None: ...
class LocaleHTMLCalendar(HTMLCalendar):
"""This class can be passed a locale name in the constructor and will return
"""
This class can be passed a locale name in the constructor and will return
month and weekday names in the specified locale.
"""

View File

@@ -14,7 +14,7 @@ from builtins import list as _list, type as _type
from collections.abc import Iterable, Iterator, Mapping
from email.message import Message
from types import TracebackType
from typing import IO, Any, Protocol
from typing import IO, Any, Protocol, type_check_only
from typing_extensions import Self
__all__ = [
@@ -78,6 +78,7 @@ def parse_multipart(
is a list of strings.
"""
@type_check_only
class _Environ(Protocol):
def __getitem__(self, k: str, /) -> str: ...
def keys(self) -> Iterable[str]: ...
@@ -86,6 +87,7 @@ def parse_header(line: str) -> tuple[str, dict[str, str]]:
"""Parse a Content-type like header.
Return the main content-type and a dictionary of options.
"""
def test(environ: _Environ = ...) -> None:
@@ -93,6 +95,7 @@ def test(environ: _Environ = ...) -> None:
Write minimal HTTP headers and dump all information provided to
the script in HTML form.
"""
def print_environ(environ: _Environ = ...) -> None:
@@ -164,6 +167,7 @@ class FieldStorage:
a file open for reading and writing. This makes it possible to
override the default choice of storing all files in a temporary
directory and unlinking them as soon as they have been opened.
"""
FieldStorageClass: _type | None
@@ -243,6 +247,7 @@ class FieldStorage:
max_num_fields: int. If set, then __init__ throws a ValueError
if there are more than n fields read by parse_qsl().
"""
def __enter__(self) -> Self: ...
@@ -294,6 +299,7 @@ class FieldStorage:
that is nevertheless automatically deleted when the script
terminates, try defining a __del__ method in a derived class
which unlinks the temporary files you have created.
"""
def print_exception(

View File

@@ -61,6 +61,7 @@ class Cmd:
framework. There is no good reason to instantiate Cmd itself; rather,
it's useful as a superclass of an interpreter class you define yourself
in order to inherit Cmd's methods and encapsulate action methods.
"""
prompt: str
@@ -87,17 +88,20 @@ class Cmd:
is done automatically. The optional arguments stdin and stdout
specify alternate input and output file objects; if not specified,
sys.stdin and sys.stdout are used.
"""
old_completer: Callable[[str, int], str | None] | None
def cmdloop(self, intro: Any | None = None) -> None:
"""Repeatedly issue a prompt, accept input, parse an initial prefix
off the received input, and dispatch to action methods, passing them
the remainder of the line as argument.
"""
def precmd(self, line: str) -> str:
"""Hook method executed just before the command line is
interpreted, but after the input prompt is generated and issued.
"""
def postcmd(self, stop: bool, line: str) -> bool:
@@ -109,6 +113,7 @@ class Cmd:
def postloop(self) -> None:
"""Hook method executed once when the cmdloop() method is about to
return.
"""
def parseline(self, line: str) -> tuple[str | None, str | None, str]:
@@ -125,6 +130,7 @@ class Cmd:
see the precmd() and postcmd() methods for useful execution hooks.
The return value is a flag indicating whether interpretation of
commands by the interpreter should stop.
"""
def emptyline(self) -> bool:
@@ -132,6 +138,7 @@ class Cmd:
If this method is not overridden, it repeats the last nonempty
command entered.
"""
def default(self, line: str) -> None:
@@ -139,6 +146,7 @@ class Cmd:
If this method is not overridden, it prints an error message and
returns.
"""
def completedefault(self, *ignored: Any) -> list[str]:
@@ -146,6 +154,7 @@ class Cmd:
complete_*() method is available.
By default, it returns an empty list.
"""
def completenames(self, text: str, *ignored: Any) -> list[str]: ...

View File

@@ -14,6 +14,7 @@ class InteractiveInterpreter:
This class deals with parsing and interpreter state (the user's
namespace); it doesn't deal with input buffering or prompting or
input file naming (the filename is always passed in explicitly).
"""
locals: dict[str, Any] # undocumented
@@ -25,6 +26,7 @@ class InteractiveInterpreter:
namespace in which code will be executed; it defaults to a newly
created dictionary with key "__name__" set to "__console__" and
key "__doc__" set to None.
"""
def runsource(self, source: str, filename: str = "<input>", symbol: str = "single") -> bool:
@@ -49,6 +51,7 @@ class InteractiveInterpreter:
an exception is raised). The return value can be used to
decide whether to use sys.ps1 or sys.ps2 to prompt the next
line.
"""
def runcode(self, code: CodeType) -> None:
@@ -61,6 +64,7 @@ class InteractiveInterpreter:
A note about KeyboardInterrupt: this exception may occur
elsewhere in this code, and may not always be caught. The
caller should be prepared to deal with it.
"""
if sys.version_info >= (3, 13):
def showsyntaxerror(self, filename: str | None = None, *, source: str = "") -> None:
@@ -73,6 +77,7 @@ class InteractiveInterpreter:
"<string>" when reading from a string).
The output is written by self.write(), below.
"""
else:
def showsyntaxerror(self, filename: str | None = None) -> None:
@@ -85,6 +90,7 @@ class InteractiveInterpreter:
"<string>" when reading from a string).
The output is written by self.write(), below.
"""
def showtraceback(self) -> None:
@@ -93,6 +99,7 @@ class InteractiveInterpreter:
We remove the first stack item because it is our own code.
The output is written by self.write(), below.
"""
def write(self, data: str) -> None:
@@ -100,6 +107,7 @@ class InteractiveInterpreter:
The base implementation writes to sys.stderr; a subclass may
replace this with a different implementation.
"""
class InteractiveConsole(InteractiveInterpreter):
@@ -107,6 +115,7 @@ class InteractiveConsole(InteractiveInterpreter):
This class builds on InteractiveInterpreter and adds prompting
using the familiar sys.ps1 and sys.ps2, and input buffering.
"""
buffer: list[str] # undocumented
@@ -122,6 +131,7 @@ class InteractiveConsole(InteractiveInterpreter):
The optional filename argument should specify the (file)name
of the input stream; it will show up in tracebacks.
"""
def push(self, line: str, filename: str | None = None) -> bool:
@@ -136,6 +146,7 @@ class InteractiveConsole(InteractiveInterpreter):
is left as it was after the line was appended. The return
value is 1 if more input is required, 0 if the line was dealt
with in some way (this is the same as runsource()).
"""
else:
def __init__(self, locals: dict[str, Any] | None = None, filename: str = "<console>") -> None:
@@ -146,6 +157,7 @@ class InteractiveConsole(InteractiveInterpreter):
The optional filename argument should specify the (file)name
of the input stream; it will show up in tracebacks.
"""
def push(self, line: str) -> bool:
@@ -160,6 +172,7 @@ class InteractiveConsole(InteractiveInterpreter):
is left as it was after the line was appended. The return
value is 1 if more input is required, 0 if the line was dealt
with in some way (this is the same as runsource()).
"""
def interact(self, banner: str | None = None, exitmsg: str | None = None) -> None:
@@ -176,6 +189,7 @@ class InteractiveConsole(InteractiveInterpreter):
printed when exiting. Pass the empty string to suppress
printing an exit message. If exitmsg is not given or None,
a default message is printed.
"""
def resetbuffer(self) -> None:
@@ -190,6 +204,7 @@ class InteractiveConsole(InteractiveInterpreter):
The base implementation uses the built-in function
input(); a subclass may replace this with a different
implementation.
"""
if sys.version_info >= (3, 13):
@@ -213,6 +228,7 @@ if sys.version_info >= (3, 13):
local -- passed to InteractiveInterpreter.__init__()
exitmsg -- passed to InteractiveConsole.interact()
local_exit -- passed to InteractiveConsole.__init__()
"""
else:
@@ -234,4 +250,5 @@ else:
readfunc -- if not None, replaces InteractiveConsole.raw_input()
local -- passed to InteractiveInterpreter.__init__()
exitmsg -- passed to InteractiveConsole.interact()
"""

View File

@@ -156,6 +156,7 @@ def getencoder(encoding: str) -> _Encoder:
its encoder function.
Raises a LookupError in case the encoding cannot be found.
"""
def getdecoder(encoding: str) -> _Decoder:
@@ -163,6 +164,7 @@ def getdecoder(encoding: str) -> _Decoder:
its decoder function.
Raises a LookupError in case the encoding cannot be found.
"""
def getincrementalencoder(encoding: str) -> _IncrementalEncoder:
@@ -171,6 +173,7 @@ def getincrementalencoder(encoding: str) -> _IncrementalEncoder:
Raises a LookupError in case the encoding cannot be found
or the codecs doesn't provide an incremental encoder.
"""
@overload
@@ -180,6 +183,7 @@ def getincrementaldecoder(encoding: _BufferedEncoding) -> _BufferedIncrementalDe
Raises a LookupError in case the encoding cannot be found
or the codecs doesn't provide an incremental decoder.
"""
@overload
@@ -189,6 +193,7 @@ def getreader(encoding: str) -> _StreamReader:
its StreamReader class or factory function.
Raises a LookupError in case the encoding cannot be found.
"""
def getwriter(encoding: str) -> _StreamWriter:
@@ -196,6 +201,7 @@ def getwriter(encoding: str) -> _StreamWriter:
its StreamWriter class or factory function.
Raises a LookupError in case the encoding cannot be found.
"""
def open(
@@ -252,10 +258,12 @@ def EncodedFile(file: _Stream, data_encoding: str, file_encoding: str | None = N
.data_encoding and .file_encoding which reflect the given
parameters of the same name. The attributes can be used for
introspection by Python programs.
"""
def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -> Generator[bytes, None, None]:
"""Encoding iterator.
"""
Encoding iterator.
Encodes the input strings from the iterator using an IncrementalEncoder.
@@ -264,7 +272,8 @@ def iterencode(iterator: Iterable[str], encoding: str, errors: str = "strict") -
"""
def iterdecode(iterator: Iterable[bytes], encoding: str, errors: str = "strict") -> Generator[str, None, None]:
"""Decoding iterator.
"""
Decoding iterator.
Decodes the input strings from the iterator using an IncrementalDecoder.
@@ -322,6 +331,7 @@ class Codec:
(only for encoding).
The set of allowed values can be extended via register_error.
"""
# These are sort of @abstractmethod but sort of not.
@@ -340,6 +350,7 @@ class Codec:
The encoder must be able to handle zero length input and
return an empty object of the output object type in this
situation.
"""
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]:
@@ -360,17 +371,20 @@ class Codec:
The decoder must be able to handle zero length input and
return an empty object of the output object type in this
situation.
"""
class IncrementalEncoder:
"""An IncrementalEncoder encodes an input in multiple steps. The input can
"""
An IncrementalEncoder encodes an input in multiple steps. The input can
be passed piece by piece to the encode() method. The IncrementalEncoder
remembers the state of the encoding process between calls to encode().
"""
errors: str
def __init__(self, errors: str = "strict") -> None:
"""Creates an IncrementalEncoder instance.
"""
Creates an IncrementalEncoder instance.
The IncrementalEncoder may use different error handling schemes by
providing the errors keyword argument. See the module docstring
@@ -379,28 +393,37 @@ class IncrementalEncoder:
@abstractmethod
def encode(self, input: str, final: bool = False) -> bytes:
"""Encodes input and returns the resulting object."""
"""
Encodes input and returns the resulting object.
"""
def reset(self) -> None:
"""Resets the encoder to the initial state."""
"""
Resets the encoder to the initial state.
"""
# documentation says int but str is needed for the subclass.
def getstate(self) -> int | str:
"""Return the current state of the encoder."""
"""
Return the current state of the encoder.
"""
def setstate(self, state: int | str) -> None:
"""Set the current state of the encoder. state must have been
"""
Set the current state of the encoder. state must have been
returned by getstate().
"""
class IncrementalDecoder:
"""An IncrementalDecoder decodes an input in multiple steps. The input can
"""
An IncrementalDecoder decodes an input in multiple steps. The input can
be passed piece by piece to the decode() method. The IncrementalDecoder
remembers the state of the decoding process between calls to decode().
"""
errors: str
def __init__(self, errors: str = "strict") -> None:
"""Create an IncrementalDecoder instance.
"""
Create an IncrementalDecoder instance.
The IncrementalDecoder may use different error handling schemes by
providing the errors keyword argument. See the module docstring
@@ -409,13 +432,18 @@ class IncrementalDecoder:
@abstractmethod
def decode(self, input: ReadableBuffer, final: bool = False) -> str:
"""Decode input and returns the resulting object."""
"""
Decode input and returns the resulting object.
"""
def reset(self) -> None:
"""Reset the decoder to the initial state."""
"""
Reset the decoder to the initial state.
"""
def getstate(self) -> tuple[bytes, int]:
"""Return the current state of the decoder.
"""
Return the current state of the decoder.
This must be a (buffered_input, additional_state_info) tuple.
buffered_input must be a bytes object containing bytes that
@@ -427,7 +455,8 @@ class IncrementalDecoder:
"""
def setstate(self, state: tuple[bytes, int]) -> None:
"""Set the current state of the decoder.
"""
Set the current state of the decoder.
state must have been returned by getstate(). The effect of
setstate((b"", 0)) must be equivalent to reset().
@@ -435,7 +464,8 @@ class IncrementalDecoder:
# These are not documented but used in encodings/*.py implementations.
class BufferedIncrementalEncoder(IncrementalEncoder):
"""This subclass of IncrementalEncoder can be used as the baseclass for an
"""
This subclass of IncrementalEncoder can be used as the baseclass for an
incremental encoder if the encoder must keep some of the output in a
buffer between calls to encode().
"""
@@ -447,7 +477,8 @@ class BufferedIncrementalEncoder(IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class BufferedIncrementalDecoder(IncrementalDecoder):
"""This subclass of IncrementalDecoder can be used as the baseclass for an
"""
This subclass of IncrementalDecoder can be used as the baseclass for an
incremental decoder if the decoder must be able to handle incomplete
byte sequences.
"""
@@ -500,6 +531,7 @@ class StreamWriter(Codec):
output is put into a clean state, that allows appending
of new fresh data without having to rescan the whole
stream to recover state.
"""
def seek(self, offset: int, whence: int = 0) -> None: ...
@@ -564,6 +596,7 @@ class StreamReader(Codec):
size, if given, is passed as size argument to the
read() method.
"""
def readlines(self, sizehint: int | None = None, keepends: bool = True) -> list[str]:
@@ -574,7 +607,8 @@ class StreamReader(Codec):
method and are included in the list entries.
sizehint, if given, is ignored since there is no efficient
way to finding the true end-of-line.
way of finding the true end-of-line.
"""
def reset(self) -> None:
@@ -583,6 +617,7 @@ class StreamReader(Codec):
Note that no stream repositioning should take place.
This method is primarily intended to be able to recover
from decoding errors.
"""
def seek(self, offset: int, whence: int = 0) -> None:
@@ -609,6 +644,7 @@ class StreamReaderWriter(TextIO):
The design is such that one can use the factory functions
returned by the codec.lookup() function to construct the
instance.
"""
stream: _Stream
@@ -622,6 +658,7 @@ class StreamReaderWriter(TextIO):
Error handling is done in the same way as defined for the
StreamWriter/Readers.
"""
def read(self, size: int = -1) -> str: ...
@@ -664,6 +701,7 @@ class StreamRecoder(BinaryIO):
In the other direction, data is read from the underlying stream using
a Reader instance and then encoded and returned to the caller.
"""
data_encoding: str
@@ -693,6 +731,7 @@ class StreamRecoder(BinaryIO):
Error handling is done in the same way as defined for the
StreamWriter/Readers.
"""
def read(self, size: int = -1) -> bytes: ...

View File

@@ -77,6 +77,7 @@ def namedtuple(
Point(x=11, y=22)
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)
"""
class UserDict(MutableMapping[_KT, _VT]):
@@ -404,6 +405,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c['b'] -= 2 # reduce the count of 'b' by two
>>> c.most_common() # 'b' is still in, but its count is zero
[('a', 3), ('c', 1), ('b', 0)]
"""
@overload
@@ -416,6 +418,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c = Counter('gallahad') # a new counter from an iterable
>>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping
>>> c = Counter(a=4, b=2) # a new counter from keyword args
"""
@overload
@@ -443,6 +446,7 @@ class Counter(dict[_T, int], Generic[_T]):
Note, if an element's count has been set to zero or is a negative
number, elements() will ignore it.
"""
def most_common(self, n: int | None = None) -> list[tuple[_T, int]]:
@@ -451,6 +455,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> Counter('abracadabra').most_common(3)
[('a', 5), ('b', 2), ('r', 2)]
"""
@classmethod
@@ -470,6 +475,7 @@ class Counter(dict[_T, int], Generic[_T]):
0
>>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
-1
"""
@overload
@@ -494,6 +500,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c.update(d) # add elements from another counter
>>> c['h'] # four 'h' in which, witch, and watch
4
"""
@overload
@@ -517,6 +524,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> Counter('abbb') + Counter('bcc')
Counter({'b': 4, 'c': 2, 'a': 1})
"""
def __sub__(self, other: Counter[_T]) -> Counter[_T]:
@@ -524,6 +532,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> Counter('abbbc') - Counter('bccd')
Counter({'b': 2, 'a': 1})
"""
def __and__(self, other: Counter[_T]) -> Counter[_T]:
@@ -531,6 +540,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> Counter('abbb') & Counter('bcc')
Counter({'b': 1})
"""
def __or__(self, other: Counter[_S]) -> Counter[_T | _S]: # type: ignore[override]
@@ -538,6 +548,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> Counter('abbb') | Counter('bcc')
Counter({'b': 3, 'c': 2, 'a': 1})
"""
def __pos__(self) -> Counter[_T]:
@@ -546,6 +557,7 @@ class Counter(dict[_T, int], Generic[_T]):
def __neg__(self) -> Counter[_T]:
"""Subtracts from an empty counter. Strips positive and zero counts,
and flips the sign on negative counts.
"""
# several type: ignores because __iadd__ is supposedly incompatible with __add__, etc.
def __iadd__(self, other: SupportsItems[_T, int]) -> Self: # type: ignore[misc]
@@ -555,6 +567,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c += Counter('bcc')
>>> c
Counter({'b': 4, 'c': 2, 'a': 1})
"""
def __isub__(self, other: SupportsItems[_T, int]) -> Self:
@@ -564,6 +577,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c -= Counter('bccd')
>>> c
Counter({'b': 2, 'a': 1})
"""
def __iand__(self, other: SupportsItems[_T, int]) -> Self:
@@ -573,6 +587,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c &= Counter('bcc')
>>> c
Counter({'b': 1})
"""
def __ior__(self, other: SupportsItems[_T, int]) -> Self: # type: ignore[override,misc]
@@ -582,6 +597,7 @@ class Counter(dict[_T, int], Generic[_T]):
>>> c |= Counter('bcc')
>>> c
Counter({'b': 3, 'c': 2, 'a': 1})
"""
if sys.version_info >= (3, 10):
def total(self) -> int:
@@ -779,12 +795,14 @@ class ChainMap(MutableMapping[_KT, _VT]):
Lookups search the underlying mappings successively until a key is found.
In contrast, writes, updates, and deletions only operate on the first
mapping.
"""
maps: list[MutableMapping[_KT, _VT]]
def __init__(self, *maps: MutableMapping[_KT, _VT]) -> None:
"""Initialize a ChainMap by setting *maps* to the given mappings.
If no mappings are provided, a single empty dictionary is used.
"""
def new_child(self, m: MutableMapping[_KT, _VT] | None = None) -> Self:

View File

@@ -14,10 +14,11 @@ See module py_compile for details of the actual byte-compilation.
import sys
from _typeshed import StrPath
from py_compile import PycInvalidationMode
from typing import Any, Protocol
from typing import Any, Protocol, type_check_only
__all__ = ["compile_dir", "compile_file", "compile_path"]
@type_check_only
class _SupportsSearch(Protocol):
def search(self, string: str, /) -> Any: ...

View File

@@ -21,7 +21,7 @@ from .thread import ThreadPoolExecutor as ThreadPoolExecutor
if sys.version_info >= (3, 14):
from .interpreter import InterpreterPoolExecutor as InterpreterPoolExecutor
__all__ = (
__all__ = [
"FIRST_COMPLETED",
"FIRST_EXCEPTION",
"ALL_COMPLETED",
@@ -36,7 +36,7 @@ if sys.version_info >= (3, 14):
"ProcessPoolExecutor",
"ThreadPoolExecutor",
"InterpreterPoolExecutor",
)
]
elif sys.version_info >= (3, 13):
__all__ = (

View File

@@ -35,7 +35,9 @@ class InvalidStateError(Error):
"""The operation is not allowed in this state."""
class BrokenExecutor(RuntimeError):
"""Raised when a executor has become non-functional after a severe failure."""
"""
Raised when a executor has become non-functional after a severe failure.
"""
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

View File

@@ -58,7 +58,9 @@ if sys.version_info >= (3, 14):
def run(self, task: _Task) -> None: ... # type: ignore[override]
class BrokenInterpreterPool(BrokenThreadPool):
"""Raised when a worker thread in an InterpreterPoolExecutor failed initializing."""
"""
Raised when a worker thread in an InterpreterPoolExecutor failed initializing.
"""
class InterpreterPoolExecutor(ThreadPoolExecutor):
BROKEN: type[BrokenInterpreterPool]

View File

@@ -151,6 +151,7 @@ def _process_chunk(fn: Callable[..., _T], chunk: Iterable[tuple[Any, ...]]) -> l
iterable passed to map.
This function is run in a separate process.
"""
if sys.version_info >= (3, 11):
@@ -247,13 +248,15 @@ _system_limited: bool | None
def _check_system_limits() -> None: ...
def _chain_from_iterable_of_lists(iterable: Iterable[MutableSequence[Any]]) -> Any:
"""Specialized implementation of itertools.chain.from_iterable.
"""
Specialized implementation of itertools.chain.from_iterable.
Each item in *iterable* should be a list. This function is
careful not to keep references to yielded objects.
"""
class BrokenProcessPool(BrokenExecutor):
"""Raised when a process in a ProcessPoolExecutor terminated abruptly
"""
Raised when a process in a ProcessPoolExecutor terminated abruptly
while a future was in the running state.
"""

Some files were not shown because too many files have changed in this diff Show More