Compare commits

..

52 Commits

Author SHA1 Message Date
Dhruv Manilawala
1dc0a523de Separate TOC from the navigation 2024-09-05 16:08:53 +05:30
Dhruv Manilawala
e1e9143c47 [red-knot] Handle multiple comprehension targets (#13213)
## Summary

Part of #13085, this PR updates the comprehension definition to handle
multiple targets.

## Test Plan

Update existing semantic index test case for comprehension with multiple
targets. Running corpus tests shouldn't panic.
2024-09-04 11:18:58 +05:30
Carl Meyer
3c4ec82aee [red-knot] support non-local name lookups (#13177)
Add support for non-local name lookups.

There's one TODO around annotated assignments without a RHS; these need
a fair amount of attention, which they'll get in an upcoming PR about
declared vs inferred types.

Fixes #11663
2024-09-03 14:18:05 -07:00
Carl Meyer
29c36a56b2 [red-knot] fix scope inference with deferred types (#13204)
Test coverage for #13131 wasn't as good as I thought it was, because
although we infer a lot of types in stubs in typeshed, we don't check
typeshed, and therefore we don't do scope-level inference and pull all
types for a scope. So we didn't really have good test coverage for
scope-level inference in a stub. And because of this, I got the code for
supporting that wrong, meaning that if we did scope-level inference with
deferred types, we'd end up never populating the deferred types in the
scope's `TypeInference`, which causes panics like #13160.

Here I both add test coverage by running the corpus tests both as `.py`
and as `.pyi` (which reveals the panic), and I fix the code to support
deferred types in scope inference.

This also revealed a problem with deferred types in generic functions,
which effectively span two scopes. That problem will require a bit more
thought, and I don't want to block this PR on it, so for now I just
don't defer annotations on generic functions.

Fixes #13160.
2024-09-03 11:20:43 -07:00
Alex Waygood
dfee65882b [red-knot] Inline Type::is_literal (#13230) 2024-09-03 15:02:50 +01:00
Alex Waygood
50c8ee5175 Fix virtual environment details in knot_benchmark (#13228) 2024-09-03 14:35:45 +01:00
Micha Reiser
c2aac5f826 Enable multithreading for pyright (#13227) 2024-09-03 11:24:42 +00:00
Alex Waygood
387af831f9 Improve detection of whether a symbol refers to a builtin exception (#13215) 2024-09-03 10:33:03 +00:00
Alex Waygood
9d517061f2 [red-knot] Reduce some repetitiveness in tests (#13135) 2024-09-03 11:26:44 +01:00
Dhruv Manilawala
facf6febf0 [red-knot] Remove match pattern definition visitor (#13209)
## Summary

This PR is based on this discussion:
https://github.com/astral-sh/ruff/pull/13147#discussion_r1739408653.

**Todo**

- [x] Add documentation for `MatchPatternState`

## Test Plan

`cargo insta test` and `cargo clippy`
2024-09-03 08:53:35 +00:00
Simon
46e687e8d1 [red-knot] Condense literals display by types (#13185)
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-09-03 07:23:28 +00:00
Micha Reiser
599103c933 Add a few missing #[return_ref] attributes (#13223) 2024-09-03 09:15:43 +02:00
Charlie Marsh
54df960a4a Use | for page separator in meta titles (#13221)
## Summary

Same as https://github.com/astral-sh/uv/pull/6953.
2024-09-03 00:46:15 +00:00
Charlie Marsh
3463683632 Update URL in structured schema (#13220) 2024-09-03 00:21:45 +00:00
Charlie Marsh
6b973b2556 Point docs to Astral favicon (#13219)
## Summary

Same as https://github.com/astral-sh/uv/pull/6951. Unfortunately we have
to use a single favicon for the docs.
2024-09-02 20:11:39 -04:00
Alex Waygood
c0e2c13d0d [flake8-pyi] Teach various rules that annotations might be stringized (#12951) 2024-09-02 13:40:06 +00:00
Ruben van Eldik
591a7a152c Handle singular case for incompatible rules warning (#13212)
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-09-02 15:16:06 +02:00
Alex Waygood
b7c7b4b387 Add a method to Checker for cached parsing of stringified type annotations (#13158) 2024-09-02 12:44:20 +00:00
Tom Kuson
ea0246c51a [ruff] Implement post-init-default (RUF033) (#13192)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-09-02 13:10:55 +01:00
Ewoud Samuels
0f85769976 Fix example in PLE1520 documentation (#13210) 2024-09-02 13:19:43 +02:00
Dhruv Manilawala
47f0b45be3 Implement AstNode for Identifier (#13207)
## Summary

Follow-up to #13147, this PR implements the `AstNode` for `Identifier`.
This makes it easier to create the `NodeKey` in red knot because it uses
a generic method to construct the key from `AnyNodeRef` and is important
for definitions that are created only on identifiers instead of
`ExprName`.

## Test Plan

`cargo test` and `cargo clippy`
2024-09-02 16:27:12 +05:30
Dhruv Manilawala
f4bed22b05 Ignore invalid notebook in ecosystem checks (#13211) 2024-09-02 16:22:51 +05:30
Dhruv Manilawala
17eb65b26f Add definitions for match statement (#13147)
## Summary

This PR adds definition for match patterns.

## Test Plan

Update the existing test case for match statement symbols to verify that
the definitions are added as well.
2024-09-02 14:40:09 +05:30
Micha Reiser
9986397d56 Avoid allocating OrderedSet in UnionBuilder::simplify (#13206)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-09-02 09:07:53 +00:00
Alex Waygood
58c641c92f Optimize some SemanticModel methods (#13091) 2024-09-02 10:03:52 +01:00
renovate[bot]
227fa4e035 Update Rust crate quick-junit to 0.5.0 (#13203)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [quick-junit](https://redirect.github.com/nextest-rs/quick-junit) |
workspace.dependencies | minor | `0.4.0` -> `0.5.0` |

---

### Release Notes

<details>
<summary>nextest-rs/quick-junit (quick-junit)</summary>

###
[`v0.5.0`](https://redirect.github.com/nextest-rs/quick-junit/blob/HEAD/CHANGELOG.md#050---2024-09-01)

[Compare
Source](https://redirect.github.com/nextest-rs/quick-junit/compare/quick-junit-0.4.0...quick-junit-0.5.0)

##### Changed

- The `Output` type, which strips invalid XML characters from a string,
has been renamed to
    `XmlString`.
-   All internal storage now uses `XmlString` rather than `String`.

</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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-09-02 06:58:58 +00:00
renovate[bot]
2b21b77ee6 Update dependency mkdocs to v1.6.1 (#13199)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [mkdocs](https://redirect.github.com/mkdocs/mkdocs)
([changelog](https://www.mkdocs.org/about/release-notes/)) | `==1.6.0`
-> `==1.6.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/mkdocs/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/mkdocs/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/mkdocs/1.6.0/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/mkdocs/1.6.0/1.6.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>mkdocs/mkdocs (mkdocs)</summary>

###
[`v1.6.1`](https://redirect.github.com/mkdocs/mkdocs/releases/tag/1.6.1)

[Compare
Source](https://redirect.github.com/mkdocs/mkdocs/compare/1.6.0...1.6.1)

### Version 1.6.1 (Friday 30th August, 2024)

##### Fixed

- Fix build error when environment variable `SOURCE_DATE_EPOCH=0` is
set.
[#&#8203;3795](https://redirect.github.com/mkdocs/mkdocs/issues/3795)
- Fix build error when `mkdocs_theme.yml` config is empty.
[#&#8203;3700](https://redirect.github.com/mkdocs/mkdocs/issues/3700)
- Support `python -W` and `PYTHONWARNINGS` instead of overriding the
configuration.
[#&#8203;3809](https://redirect.github.com/mkdocs/mkdocs/issues/3809)
- Support running with Docker under strict mode, by removing `0.0.0.0`
dev server warning.
[#&#8203;3784](https://redirect.github.com/mkdocs/mkdocs/issues/3784)
- Drop unnecessary `changefreq` from `sitemap.xml`.
[#&#8203;3629](https://redirect.github.com/mkdocs/mkdocs/issues/3629)
- Fix JavaScript console error when closing menu dropdown.
[#&#8203;3774](https://redirect.github.com/mkdocs/mkdocs/issues/3774)
- Fix JavaScript console error that occur on repeated clicks.
[#&#8203;3730](https://redirect.github.com/mkdocs/mkdocs/issues/3730)
- Fix JavaScript console error that can occur on dropdown selections.
[#&#8203;3694](https://redirect.github.com/mkdocs/mkdocs/issues/3694)

##### Added

- Added translations for Dutch.
[#&#8203;3804](https://redirect.github.com/mkdocs/mkdocs/issues/3804)
- Added and updated translations for Chinese (Simplified).
[#&#8203;3684](https://redirect.github.com/mkdocs/mkdocs/issues/3684)

</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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 12:14:19 +05:30
renovate[bot]
ba272b093c Update dependency ruff to v0.6.3 (#13201)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [ruff](https://docs.astral.sh/ruff)
([source](https://redirect.github.com/astral-sh/ruff),
[changelog](https://redirect.github.com/astral-sh/ruff/blob/main/CHANGELOG.md))
| `==0.6.2` -> `==0.6.3` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ruff/0.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/ruff/0.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/ruff/0.6.2/0.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ruff/0.6.2/0.6.3?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

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

###
[`v0.6.3`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#063)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.6.2...0.6.3)

##### Preview features

- \[`flake8-simplify`] Extend `open-file-with-context-handler` to work
with `dbm.sqlite3` (`SIM115`)
([#&#8203;13104](https://redirect.github.com/astral-sh/ruff/pull/13104))
- \[`pycodestyle`] Disable `E741` in stub files (`.pyi`)
([#&#8203;13119](https://redirect.github.com/astral-sh/ruff/pull/13119))
- \[`pydoclint`] Avoid `DOC201` on explicit returns in functions that
only return `None`
([#&#8203;13064](https://redirect.github.com/astral-sh/ruff/pull/13064))

##### Rule changes

- \[`flake8-async`] Disable check for `asyncio` before Python 3.11
(`ASYNC109`)
([#&#8203;13023](https://redirect.github.com/astral-sh/ruff/pull/13023))

##### Bug fixes

- \[`FastAPI`] Avoid introducing invalid syntax in fix for
`fast-api-non-annotated-dependency` (`FAST002`)
([#&#8203;13133](https://redirect.github.com/astral-sh/ruff/pull/13133))
- \[`flake8-implicit-str-concat`] Normalize octals before merging
concatenated strings in `single-line-implicit-string-concatenation`
(`ISC001`)
([#&#8203;13118](https://redirect.github.com/astral-sh/ruff/pull/13118))
- \[`flake8-pytest-style`] Improve help message for
`pytest-incorrect-mark-parentheses-style` (`PT023`)
([#&#8203;13092](https://redirect.github.com/astral-sh/ruff/pull/13092))
- \[`pylint`] Avoid autofix for calls that aren't `min` or `max` as
starred expression (`PLW3301`)
([#&#8203;13089](https://redirect.github.com/astral-sh/ruff/pull/13089))
- \[`ruff`] Add `datetime.time`, `datetime.tzinfo`, and
`datetime.timezone` as immutable function calls (`RUF009`)
([#&#8203;13109](https://redirect.github.com/astral-sh/ruff/pull/13109))
- \[`ruff`] Extend comment deletion for `RUF100` to include trailing
text from `noqa` directives while preserving any following comments on
the same line, if any
([#&#8203;13105](https://redirect.github.com/astral-sh/ruff/pull/13105))
- Fix dark theme on initial page load for the Ruff playground
([#&#8203;13077](https://redirect.github.com/astral-sh/ruff/pull/13077))

</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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 12:08:52 +05:30
renovate[bot]
8972e5d175 Update NPM Development dependencies (#13198)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[@types/react](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react)
([source](https://redirect.github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react))
| [`18.3.4` ->
`18.3.5`](https://renovatebot.com/diffs/npm/@types%2freact/18.3.4/18.3.5)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@types%2freact/18.3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@types%2freact/18.3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@types%2freact/18.3.4/18.3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@types%2freact/18.3.4/18.3.5?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@typescript-eslint/eslint-plugin](https://typescript-eslint.io/packages/eslint-plugin)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin))
| [`8.2.0` ->
`8.3.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/8.2.0/8.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2feslint-plugin/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2feslint-plugin/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2feslint-plugin/8.2.0/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2feslint-plugin/8.2.0/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[@typescript-eslint/parser](https://typescript-eslint.io/packages/parser)
([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser))
| [`8.2.0` ->
`8.3.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/8.2.0/8.3.0)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fparser/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2fparser/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2fparser/8.2.0/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fparser/8.2.0/8.3.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [postcss](https://postcss.org/)
([source](https://redirect.github.com/postcss/postcss)) | [`8.4.41` ->
`8.4.43`](https://renovatebot.com/diffs/npm/postcss/8.4.41/8.4.43) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/postcss/8.4.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/postcss/8.4.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/postcss/8.4.41/8.4.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/postcss/8.4.41/8.4.43?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
| [wrangler](https://redirect.github.com/cloudflare/workers-sdk)
([source](https://redirect.github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler))
| [`3.72.2` ->
`3.73.0`](https://renovatebot.com/diffs/npm/wrangler/3.72.2/3.73.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/wrangler/3.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/wrangler/3.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/wrangler/3.72.2/3.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/wrangler/3.72.2/3.73.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>typescript-eslint/typescript-eslint
(@&#8203;typescript-eslint/eslint-plugin)</summary>

###
[`v8.3.0`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#830-2024-08-26)

[Compare
Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.2.0...v8.3.0)

##### 🚀 Features

-   **eslint-plugin:** \[no-deprecation] add rule

##### 🩹 Fixes

- **eslint-plugin:** \[no-unnecessary-template-expression] add missing
parentheses in autofix

- **eslint-plugin:** \[no-unnecessary-type-parameters] check mapped
alias type arguments

-   **utils:** add `TSDeclareFunction` to `functionTypeTypes`

-   **ast-spec:** use `Expression` in argument of `ThrowStatement`

##### ❤️  Thank You

-   Abraham Guo
-   Daichi Kamiyama
-   Josh Goldberg 
-   Kim Sang Du
-   Sukka
-   Vida Xie

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

</details>

<details>
<summary>typescript-eslint/typescript-eslint
(@&#8203;typescript-eslint/parser)</summary>

###
[`v8.3.0`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#830-2024-08-26)

[Compare
Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.2.0...v8.3.0)

This was a version bump only for parser to align it with other projects,
there were no code changes.

You can read about our [versioning
strategy](https://main--typescript-eslint.netlify.app/users/versioning)
and
[releases](https://main--typescript-eslint.netlify.app/users/releases)
on our website.

</details>

<details>
<summary>postcss/postcss (postcss)</summary>

###
[`v8.4.43`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8443)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.4.42...8.4.43)

-   Fixed `markClean is not a function` error.

###
[`v8.4.42`](https://redirect.github.com/postcss/postcss/blob/HEAD/CHANGELOG.md#8442)

[Compare
Source](https://redirect.github.com/postcss/postcss/compare/8.4.41...8.4.42)

- Fixed CSS syntax error on long minified files (by
[@&#8203;varpstar](https://redirect.github.com/varpstar)).

</details>

<details>
<summary>cloudflare/workers-sdk (wrangler)</summary>

###
[`v3.73.0`](https://redirect.github.com/cloudflare/workers-sdk/blob/HEAD/packages/wrangler/CHANGELOG.md#3730)

[Compare
Source](https://redirect.github.com/cloudflare/workers-sdk/compare/wrangler@3.72.3...wrangler@3.73.0)

##### Minor Changes

-
[#&#8203;6571](https://redirect.github.com/cloudflare/workers-sdk/pull/6571)
[`a7e1bfe`](a7e1bfea3e)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! - feat:
Add deployment http targets to wrangler deploy logs, and add url to
pages deploy logs

-
[#&#8203;6497](https://redirect.github.com/cloudflare/workers-sdk/pull/6497)
[`3bd833c`](3bd833cbe2)
Thanks [@&#8203;WalshyDev](https://redirect.github.com/WalshyDev)! -
chore: move `wrangler versions ...`, `wrangler deployments ...`,
`wrangler rollback` and `wrangler triggers ...` out of experimental and
open beta.
These are now available to use without the --x-versions flag, you can
continue to pass this however without issue to keep compatibility with
all the usage today.

A few of the commands had an output that wasn't guarded by
`--x-versions` those have been updated to use the newer output, we have
tried to keep compatibility where possible (for example: `wrangler
rollback` will continue to output "Worker Version ID:" so users can
continue to grab the ID).
If you wish to use the old versions of the commands you can pass the
`--no-x-versions` flag. Note, these will be removed in the future so
please work on migrating.

-
[#&#8203;6586](https://redirect.github.com/cloudflare/workers-sdk/pull/6586)
[`72ea742`](72ea74214d)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! - feat:
Inject a 404 response for browser requested `favicon.ico` files when
loading the `/__scheduled` page for scheduled-only Workers

-
[#&#8203;6497](https://redirect.github.com/cloudflare/workers-sdk/pull/6497)
[`3bd833c`](3bd833cbe2)
Thanks [@&#8203;WalshyDev](https://redirect.github.com/WalshyDev)! -
feat: update `wrangler deploy` to use the new versions and deployments
API.
This should have zero user-facing impact but sets up the most used
command to deploy Workers to use the new recommended APIs and move away
from the old ones.
We will still call the old upload path where required (e.g. Durable
Object migration or Service Worker format).

##### Patch Changes

-
[#&#8203;6563](https://redirect.github.com/cloudflare/workers-sdk/pull/6563)
[`da48a70`](da48a70369)
Thanks
[@&#8203;threepointone](https://redirect.github.com/threepointone)! -
chore: remove the warning about local mode flag being removed in the
future

-
[#&#8203;6595](https://redirect.github.com/cloudflare/workers-sdk/pull/6595)
[`0a76d7e`](0a76d7e550)
Thanks [@&#8203;vicb](https://redirect.github.com/vicb)! - feat: update
unenv to the latest available version

-
[#&#8203;5738](https://redirect.github.com/cloudflare/workers-sdk/pull/5738)
[`c2460c4`](c2460c4d89)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! - fix:
Prevent spaces in names when validating

-
[#&#8203;6586](https://redirect.github.com/cloudflare/workers-sdk/pull/6586)
[`72ea742`](72ea74214d)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! -
chore: Improve Miniflare CRON warning wording

-
[#&#8203;6593](https://redirect.github.com/cloudflare/workers-sdk/pull/6593)
[`f097cb7`](f097cb73be)
Thanks [@&#8203;vicb](https://redirect.github.com/vicb)! - fix: remove
`experimental:` prefix requirement for nodejs_compat_v2

    See https://jira.cfdata.org/browse/DEVDASH-218

-
[#&#8203;6572](https://redirect.github.com/cloudflare/workers-sdk/pull/6572)
[`0d83428`](0d834284d0)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! - fix:
Show a clearer user error when trying to use a python worker without the
`python_workers` compatibility flag specified

-
[#&#8203;6589](https://redirect.github.com/cloudflare/workers-sdk/pull/6589)
[`f4c8cea`](f4c8cea142)
Thanks [@&#8203;vicb](https://redirect.github.com/vicb)! - feat: update
unenv to the latest available version

- Updated dependencies
\[[`45ad2e0`](45ad2e0c83)]:
-
[@&#8203;cloudflare/workers-shared](https://redirect.github.com/cloudflare/workers-shared)[@&#8203;0](https://redirect.github.com/0).4.1

###
[`v3.72.3`](https://redirect.github.com/cloudflare/workers-sdk/blob/HEAD/packages/wrangler/CHANGELOG.md#3723)

[Compare
Source](https://redirect.github.com/cloudflare/workers-sdk/compare/wrangler@3.72.2...wrangler@3.72.3)

##### Patch Changes

-
[#&#8203;6548](https://redirect.github.com/cloudflare/workers-sdk/pull/6548)
[`439e63a`](439e63a4ac)
Thanks [@&#8203;garvit-gupta](https://redirect.github.com/garvit-gupta)!
- fix: Fix Vectorize getVectors, deleteVectors payload in Wrangler
Client; VS-271

-
[#&#8203;6554](https://redirect.github.com/cloudflare/workers-sdk/pull/6554)
[`46aee5d`](46aee5d16c)
Thanks [@&#8203;andyjessop](https://redirect.github.com/andyjessop)! -
fix: nodejs_compat flags no longer error when running wrangler types
--x-include-runtime

-
[#&#8203;6548](https://redirect.github.com/cloudflare/workers-sdk/pull/6548)
[`439e63a`](439e63a4ac)
Thanks [@&#8203;garvit-gupta](https://redirect.github.com/garvit-gupta)!
- fix: Add content-type header to Vectorize POST operations;
[#&#8203;6516](https://redirect.github.com/cloudflare/workers-sdk/issues/6516)/VS-269

-
[#&#8203;6566](https://redirect.github.com/cloudflare/workers-sdk/pull/6566)
[`669ec1c`](669ec1c4d1)
Thanks [@&#8203;penalosa](https://redirect.github.com/penalosa)! - fix:
Ensure esbuild warnings are logged when running wrangler deploy

- Updated dependencies
\[[`6c057d1`](6c057d10b2)]:
-
[@&#8203;cloudflare/workers-shared](https://redirect.github.com/cloudflare/workers-shared)[@&#8203;0](https://redirect.github.com/0).4.0

</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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 12:08:21 +05:30
renovate[bot]
9ac2e61bad Update dependency react-resizable-panels to v2.1.2 (#13200)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[react-resizable-panels](https://redirect.github.com/bvaughn/react-resizable-panels)
| [`2.1.1` ->
`2.1.2`](https://renovatebot.com/diffs/npm/react-resizable-panels/2.1.1/2.1.2)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/react-resizable-panels/2.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/react-resizable-panels/2.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/react-resizable-panels/2.1.1/2.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/react-resizable-panels/2.1.1/2.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>bvaughn/react-resizable-panels
(react-resizable-panels)</summary>

###
[`v2.1.2`](https://redirect.github.com/bvaughn/react-resizable-panels/compare/2.1.1...2.1.2)

[Compare
Source](https://redirect.github.com/bvaughn/react-resizable-panels/compare/2.1.1...2.1.2)

</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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-02 12:06:11 +05:30
renovate[bot]
6deb056117 Update pre-commit dependencies (#13202)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://togithub.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.6.2` -> `v0.6.3` |
| [crate-ci/typos](https://togithub.com/crate-ci/typos) | repository |
patch | `v1.24.1` -> `v1.24.3` |

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://togithub.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.6.3`](https://togithub.com/astral-sh/ruff-pre-commit/releases/tag/v0.6.3)

[Compare
Source](https://togithub.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3)

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

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.24.3`](https://togithub.com/crate-ci/typos/releases/tag/v1.24.3)

[Compare
Source](https://togithub.com/crate-ci/typos/compare/v1.24.2...v1.24.3)

#### \[1.24.3] - 2024-08-30

##### Fixes

- Updated the dictionary with the [August
2024](https://togithub.com/crate-ci/typos/issues/1069) changes

###
[`v1.24.2`](https://togithub.com/crate-ci/typos/releases/tag/v1.24.2)

[Compare
Source](https://togithub.com/crate-ci/typos/compare/v1.24.1...v1.24.2)

#### \[1.24.2] - 2024-08-30

##### Performance

- Cap unbounded parsing to avoid worst case performance (hit with test
data)

</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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config help](https://togithub.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiIzOC41Ni4wIiwidXBkYXRlZEluVmVyIjoiMzguNTYuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-09-02 06:33:39 +00:00
Charlie Marsh
c4aad4b161 Use dynamic builtins list based on Python version (#13172)
## Summary

Closes https://github.com/astral-sh/ruff/issues/13037.
2024-09-01 17:03:44 +00:00
Alex Waygood
3abd5c08a5 [pylint] Recurse into subscript subexpressions when searching for list/dict lookups (PLR1733, PLR1736) (#13186)
## Summary

The `SequenceIndexVisitor` currently does not recurse into
subexpressions of subscripts when searching for subscript accesses that
would trigger this rule. That means that we don't currently detect
violations of the rule on snippets like this:

```py
data = {"a": 1, "b": 2}
column_names = ["a", "b"]
for index, column_name in enumerate(column_names):
    _ = data[column_names[index]]
```

Fixes #13183

## Test Plan

`cargo test -p ruff_linter`
2024-09-01 17:22:45 +01:00
Alex Waygood
2014cba87f [red-knot] Fix call expression inference edge case for decorated functions (#13191) 2024-09-01 16:19:40 +01:00
Alex Waygood
5661353334 Fix typo in pydoclint enum variant name (#13193) 2024-09-01 11:58:47 +00:00
Luo Peng
dd5d0d523c Enrich messages of SARIF results (#13180) 2024-09-01 12:13:22 +01:00
James Braza
1be8c2e340 Expand docs for ASYNC109 (#13146)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-09-01 10:16:57 +00:00
Dylan
52d8847b60 [red-knot] Literal[True,False] normalized to builtins.bool (#13178)
The `UnionBuilder` builds `builtins.bool` when handed `Literal[True]`
and `Literal[False]`.

Caveat: If the builtins module is unfindable somehow, the builder falls
back to the union type of these two literals.

First task from #12694

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-08-31 22:57:50 -07:00
Alex Waygood
d3b6e8f58b Remove pylint::helpers::CmpOpExt (#13189) 2024-09-01 01:55:24 +00:00
Tom Kuson
bf620dcb38 [pydoclint] Permit yielding None in DOC402 and DOC403 (#13148)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-09-01 02:03:39 +01:00
Alex Waygood
fae0573817 [red-knot] Fix async function edge case for inference of call expressions (#13187) 2024-09-01 01:58:35 +01:00
github-actions[bot]
0c23b868dc Sync vendored typeshed stubs (#13188)
Co-authored-by: typeshedbot <>
2024-09-01 01:41:27 +01:00
Dylan
3ceedf76b8 [red-knot] Infer type of class constructor call expression (#13171)
This tiny PR implements the following type inference: the type of
`Foo(...)` will be `Foo`.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-08-30 16:48:06 -07:00
Charlie Marsh
828871dc5c [pyupgrade] Detect aiofiles.open calls in UP015 (#13173)
## Summary

Closes https://github.com/astral-sh/ruff/issues/12879.
2024-08-30 19:39:00 -04:00
Charlie Marsh
ee21fc7fd8 Mark sys.version_info[0] < 3 and similar comparisons as outdated (#13175)
## Summary

Closes https://github.com/astral-sh/ruff/issues/12993.
2024-08-30 19:38:46 -04:00
Chris Krycho
28ab5f4065 [red-knot] implement basic call expression inference (#13164)
## Summary

Adds basic support for inferring the type resulting from a call
expression. This only works for the *result* of call expressions; it
performs no inference on parameters. It also intentionally does nothing
with class instantiation, `__call__` implementors, or lambdas.

## Test Plan

Adds a test that it infers the right thing!

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-08-30 12:51:29 -07:00
Charlie Marsh
a73bebcf15 Avoid no-self-use for attrs-style validators (#13166)
## Summary

Closes https://github.com/astral-sh/ruff/issues/12568.
2024-08-30 12:39:05 -04:00
Charlie Marsh
34dafb67a2 Treat sep arguments with effects as unsafe removals (#13165)
## Summary

Closes https://github.com/astral-sh/ruff/issues/13126.
2024-08-30 12:17:47 -04:00
Chris Krycho
f8656ff35e [red-knot] infer basic (name-based) annotation expressions (#13130)
## Summary

- Introduce methods for inferring annotation and type expressions.
- Correctly infer explicit return types from functions where they are
simple names that can be resolved in scope.

Contributes to #12701 by way of helping unlock call expressions (this
does not remotely finish that, as it stands, but it gets us moving that
direction).

## Test Plan

Added a test for function return types which use the name form of an
annotation expression, since this is aiming toward call expressions.
When we extend this to working for other annotation and type expression
positions, we should add explicit tests for those as well.

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-08-30 08:24:36 -07:00
Alex Waygood
34b4732c46 [flake8-pyi] Respect pep8_naming.classmethod-decorators settings when determining if a method is a classmethod in custom-type-var-return-type (PYI019) (#13162) 2024-08-30 14:24:01 +01:00
zhoufanjin
ce68f1cc1b Fix some typos in comments (#13157) 2024-08-30 10:42:23 +00:00
Alex Waygood
281e6d9791 [pydocstyle] Improve heuristics for detecting Google-style docstrings (#13142) 2024-08-29 16:33:18 +01:00
170 changed files with 5170 additions and 1890 deletions

View File

@@ -34,10 +34,10 @@ jobs:
- name: "Set docs version"
run: |
version="${{ (inputs.plan != '' && fromJson(inputs.plan).announcement_tag) || inputs.ref }}"
# if version is missing, exit with error
if [[ -z "$version" ]]; then
echo "Can't build docs without a version."
exit 1
# if version is missing, use 'latest'
if [ -z "$version" ]; then
echo "Using 'latest' as version"
version="latest"
fi
# Use version as display name for now
@@ -145,6 +145,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
run: |
branch_name="${{ env.branch_name }}"
# auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
# give the PR a few seconds to be created before trying to auto-merge it
sleep 10

View File

@@ -45,7 +45,7 @@ repos:
)$
- repo: https://github.com/crate-ci/typos
rev: v1.24.1
rev: v1.24.3
hooks:
- id: typos
@@ -59,7 +59,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
rev: v0.6.3
hooks:
- id: ruff-format
- id: ruff

9
Cargo.lock generated
View File

@@ -1803,9 +1803,9 @@ dependencies = [
[[package]]
name = "quick-junit"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc1a6a5406a114913df2df8507998c755311b55b78584bed5f6e88f6417c4d4"
checksum = "62ffd2f9a162cfae131bed6d9d1ed60adced33be340a94f96952897d7cb0c240"
dependencies = [
"chrono",
"indexmap",
@@ -1818,9 +1818,9 @@ dependencies = [
[[package]]
name = "quick-xml"
version = "0.31.0"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
dependencies = [
"memchr",
]
@@ -1997,6 +1997,7 @@ dependencies = [
"ruff_text_size",
"rustc-hash 2.0.0",
"salsa",
"tempfile",
"tracing",
]

View File

@@ -102,7 +102,7 @@ pep440_rs = { version = "0.6.0", features = ["serde"] }
pretty_assertions = "1.3.0"
proc-macro2 = { version = "1.0.79" }
pyproject-toml = { version = "0.9.0" }
quick-junit = { version = "0.4.0" }
quick-junit = { version = "0.5.0" }
quote = { version = "1.0.23" }
rand = { version = "0.8.5" }
rayon = { version = "1.10.0" }

View File

@@ -31,10 +31,10 @@ impl<T> AstNodeRef<T> {
/// which the `AstNodeRef` belongs.
///
/// ## Safety
///
/// Dereferencing the `node` can result in undefined behavior if `parsed` isn't the
/// [`ParsedModule`] to which `node` belongs. It's the caller's responsibility to ensure that
/// the invariant `node belongs to parsed` is upheld.
pub(super) unsafe fn new(parsed: ParsedModule, node: &T) -> Self {
Self {
_parsed: parsed,

View File

@@ -23,3 +23,4 @@ pub(crate) mod site_packages;
pub mod types;
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;

View File

@@ -666,7 +666,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
fn comprehension_scope() {
let TestCase { db, file } = test_case(
"
[x for x in iter1]
[x for x, y in iter1]
",
);
@@ -690,7 +690,22 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
let comprehension_symbol_table = index.symbol_table(comprehension_scope_id);
assert_eq!(names(&comprehension_symbol_table), vec!["x"]);
assert_eq!(names(&comprehension_symbol_table), vec!["x", "y"]);
let use_def = index.use_def_map(comprehension_scope_id);
for name in ["x", "y"] {
let definition = use_def
.first_public_definition(
comprehension_symbol_table
.symbol_id_by_name(name)
.expect("symbol exists"),
)
.unwrap();
assert!(matches!(
definition.node(&db),
DefinitionKind::Comprehension(_)
));
}
}
/// Test case to validate that the `x` variable used in the comprehension is referencing the
@@ -730,8 +745,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
let DefinitionKind::Comprehension(comprehension) = definition.node(&db) else {
panic!("expected generator definition")
};
let ast::Comprehension { target, .. } = comprehension.node();
let name = target.as_name_expr().unwrap().id().as_str();
let target = comprehension.target();
let name = target.id().as_str();
assert_eq!(name, "x");
assert_eq!(target.range(), TextRange::new(23.into(), 24.into()));
@@ -1073,7 +1088,7 @@ def x():
}
#[test]
fn match_stmt_symbols() {
fn match_stmt() {
let TestCase { db, file } = test_case(
"
match subject:
@@ -1087,13 +1102,73 @@ match subject:
",
);
let global_table = symbol_table(&db, global_scope(&db, file));
let global_scope_id = global_scope(&db, file);
let global_table = symbol_table(&db, global_scope_id);
assert!(global_table.symbol_by_name("Foo").unwrap().is_used());
assert_eq!(
names(&global_table),
vec!["subject", "a", "b", "c", "d", "f", "e", "h", "g", "Foo", "i", "j", "k", "l"]
vec!["subject", "a", "b", "c", "d", "e", "f", "g", "h", "Foo", "i", "j", "k", "l"]
);
let use_def = use_def_map(&db, global_scope_id);
for (name, expected_index) in [
("a", 0),
("b", 0),
("c", 1),
("d", 2),
("e", 0),
("f", 1),
("g", 0),
("h", 1),
("i", 0),
("j", 1),
("k", 0),
("l", 1),
] {
let definition = use_def
.first_public_definition(
global_table.symbol_id_by_name(name).expect("symbol exists"),
)
.expect("Expected with item definition for {name}");
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
assert_eq!(pattern.index(), expected_index);
} else {
panic!("Expected match pattern definition for {name}");
}
}
}
#[test]
fn nested_match_case() {
let TestCase { db, file } = test_case(
"
match 1:
case first:
match 2:
case second:
pass
",
);
let global_scope_id = global_scope(&db, file);
let global_table = symbol_table(&db, global_scope_id);
assert_eq!(names(&global_table), vec!["first", "second"]);
let use_def = use_def_map(&db, global_scope_id);
for (name, expected_index) in [("first", 0), ("second", 0)] {
let definition = use_def
.first_public_definition(
global_table.symbol_id_by_name(name).expect("symbol exists"),
)
.expect("Expected with item definition for {name}");
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
assert_eq!(pattern.index(), expected_index);
} else {
panic!("Expected match pattern definition for {name}");
}
}
}
#[test]

View File

@@ -26,7 +26,7 @@ use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
use crate::semantic_index::SemanticIndex;
use crate::Db;
use super::definition::WithItemDefinitionNodeRef;
use super::definition::{MatchPatternDefinitionNodeRef, WithItemDefinitionNodeRef};
pub(super) struct SemanticIndexBuilder<'db> {
// Builder state
@@ -36,6 +36,8 @@ pub(super) struct SemanticIndexBuilder<'db> {
scope_stack: Vec<FileScopeId>,
/// The assignment we're currently visiting.
current_assignment: Option<CurrentAssignment<'db>>,
/// The match case we're currently visiting.
current_match_case: Option<CurrentMatchCase<'db>>,
/// Flow states at each `break` in the current loop.
loop_break_states: Vec<FlowSnapshot>,
@@ -59,6 +61,7 @@ impl<'db> SemanticIndexBuilder<'db> {
module: parsed,
scope_stack: Vec::new(),
current_assignment: None,
current_match_case: None,
loop_break_states: vec![],
scopes: IndexVec::new(),
@@ -282,6 +285,7 @@ impl<'db> SemanticIndexBuilder<'db> {
// The `iter` of the first generator is evaluated in the outer scope, while all subsequent
// nodes are evaluated in the inner scope.
self.add_standalone_expression(&generator.iter);
self.visit_expr(&generator.iter);
self.push_scope(scope);
@@ -297,6 +301,7 @@ impl<'db> SemanticIndexBuilder<'db> {
}
for generator in generators_iter {
self.add_standalone_expression(&generator.iter);
self.visit_expr(&generator.iter);
self.current_assignment = Some(CurrentAssignment::Comprehension {
@@ -600,6 +605,17 @@ where
self.visit_body(body);
self.visit_body(orelse);
}
ast::Stmt::Match(ast::StmtMatch {
subject,
cases,
range: _,
}) => {
self.add_standalone_expression(subject);
self.visit_expr(subject);
for case in cases {
self.visit_match_case(case);
}
}
_ => {
walk_stmt(self, stmt);
}
@@ -664,7 +680,11 @@ where
Some(CurrentAssignment::Comprehension { node, first }) => {
self.add_definition(
symbol,
ComprehensionDefinitionNodeRef { node, first },
ComprehensionDefinitionNodeRef {
iterable: &node.iter,
target: name_node,
first,
},
);
}
Some(CurrentAssignment::WithItem(with_item)) => {
@@ -794,7 +814,7 @@ where
}
}
fn visit_parameters(&mut self, parameters: &'ast ruff_python_ast::Parameters) {
fn visit_parameters(&mut self, parameters: &'ast ast::Parameters) {
// Intentionally avoid walking default expressions, as we handle them in the enclosing
// scope.
for parameter in parameters.iter().map(ast::AnyParameterRef::as_parameter) {
@@ -802,23 +822,58 @@ where
}
}
fn visit_match_case(&mut self, match_case: &'ast ast::MatchCase) {
debug_assert!(self.current_match_case.is_none());
self.current_match_case = Some(CurrentMatchCase::new(&match_case.pattern));
self.visit_pattern(&match_case.pattern);
self.current_match_case = None;
if let Some(expr) = &match_case.guard {
self.visit_expr(expr);
}
self.visit_body(&match_case.body);
}
fn visit_pattern(&mut self, pattern: &'ast ast::Pattern) {
if let ast::Pattern::MatchAs(ast::PatternMatchAs {
name: Some(name), ..
})
| ast::Pattern::MatchStar(ast::PatternMatchStar {
if let ast::Pattern::MatchStar(ast::PatternMatchStar {
name: Some(name),
range: _,
}) = pattern
{
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
let state = self.current_match_case.as_ref().unwrap();
self.add_definition(
symbol,
MatchPatternDefinitionNodeRef {
pattern: state.pattern,
identifier: name,
index: state.index,
},
);
}
walk_pattern(self, pattern);
if let ast::Pattern::MatchAs(ast::PatternMatchAs {
name: Some(name), ..
})
| ast::Pattern::MatchMapping(ast::PatternMatchMapping {
rest: Some(name), ..
}) = pattern
{
// TODO(dhruvmanila): Add definition
self.add_or_update_symbol(name.id.clone(), SymbolFlags::IS_DEFINED);
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
let state = self.current_match_case.as_ref().unwrap();
self.add_definition(
symbol,
MatchPatternDefinitionNodeRef {
pattern: state.pattern,
identifier: name,
index: state.index,
},
);
}
walk_pattern(self, pattern);
self.current_match_case.as_mut().unwrap().index += 1;
}
}
@@ -871,3 +926,27 @@ impl<'a> From<&'a ast::WithItem> for CurrentAssignment<'a> {
Self::WithItem(value)
}
}
struct CurrentMatchCase<'a> {
/// The pattern that's part of the current match case.
pattern: &'a ast::Pattern,
/// The index of the sub-pattern that's being currently visited within the pattern.
///
/// For example:
/// ```py
/// match subject:
/// case a as b: ...
/// case [a, b]: ...
/// case a | b: ...
/// ```
///
/// In all of the above cases, the index would be 0 for `a` and 1 for `b`.
index: u32,
}
impl<'a> CurrentMatchCase<'a> {
fn new(pattern: &'a ast::Pattern) -> Self {
Self { pattern, index: 0 }
}
}

View File

@@ -49,6 +49,7 @@ pub(crate) enum DefinitionNodeRef<'a> {
Comprehension(ComprehensionDefinitionNodeRef<'a>),
Parameter(ast::AnyParameterRef<'a>),
WithItem(WithItemDefinitionNodeRef<'a>),
MatchPattern(MatchPatternDefinitionNodeRef<'a>),
}
impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> {
@@ -123,6 +124,12 @@ impl<'a> From<ast::AnyParameterRef<'a>> for DefinitionNodeRef<'a> {
}
}
impl<'a> From<MatchPatternDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
fn from(node: MatchPatternDefinitionNodeRef<'a>) -> Self {
Self::MatchPattern(node)
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct ImportFromDefinitionNodeRef<'a> {
pub(crate) node: &'a ast::StmtImportFrom,
@@ -149,10 +156,22 @@ pub(crate) struct ForStmtDefinitionNodeRef<'a> {
#[derive(Copy, Clone, Debug)]
pub(crate) struct ComprehensionDefinitionNodeRef<'a> {
pub(crate) node: &'a ast::Comprehension,
pub(crate) iterable: &'a ast::Expr,
pub(crate) target: &'a ast::ExprName,
pub(crate) first: bool,
}
#[derive(Copy, Clone, Debug)]
pub(crate) struct MatchPatternDefinitionNodeRef<'a> {
/// The outermost pattern node in which the identifier being defined occurs.
pub(crate) pattern: &'a ast::Pattern,
/// The identifier being defined.
pub(crate) identifier: &'a ast::Identifier,
/// The index of the identifier in the pattern when visiting the `pattern` node in evaluation
/// order.
pub(crate) index: u32,
}
impl DefinitionNodeRef<'_> {
#[allow(unsafe_code)]
pub(super) unsafe fn into_owned(self, parsed: ParsedModule) -> DefinitionKind {
@@ -193,12 +212,15 @@ impl DefinitionNodeRef<'_> {
target: AstNodeRef::new(parsed, target),
})
}
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef { node, first }) => {
DefinitionKind::Comprehension(ComprehensionDefinitionKind {
node: AstNodeRef::new(parsed, node),
first,
})
}
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef {
iterable,
target,
first,
}) => DefinitionKind::Comprehension(ComprehensionDefinitionKind {
iterable: AstNodeRef::new(parsed.clone(), iterable),
target: AstNodeRef::new(parsed, target),
first,
}),
DefinitionNodeRef::Parameter(parameter) => match parameter {
ast::AnyParameterRef::Variadic(parameter) => {
DefinitionKind::Parameter(AstNodeRef::new(parsed, parameter))
@@ -213,6 +235,15 @@ impl DefinitionNodeRef<'_> {
target: AstNodeRef::new(parsed, target),
})
}
DefinitionNodeRef::MatchPattern(MatchPatternDefinitionNodeRef {
pattern,
identifier,
index,
}) => DefinitionKind::MatchPattern(MatchPatternDefinitionKind {
pattern: AstNodeRef::new(parsed.clone(), pattern),
identifier: AstNodeRef::new(parsed, identifier),
index,
}),
}
}
@@ -235,12 +266,15 @@ impl DefinitionNodeRef<'_> {
iterable: _,
target,
}) => target.into(),
Self::Comprehension(ComprehensionDefinitionNodeRef { node, first: _ }) => node.into(),
Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => target.into(),
Self::Parameter(node) => match node {
ast::AnyParameterRef::Variadic(parameter) => parameter.into(),
ast::AnyParameterRef::NonVariadic(parameter) => parameter.into(),
},
Self::WithItem(WithItemDefinitionNodeRef { node: _, target }) => target.into(),
Self::MatchPattern(MatchPatternDefinitionNodeRef { identifier, .. }) => {
identifier.into()
}
}
}
}
@@ -260,17 +294,41 @@ pub enum DefinitionKind {
Parameter(AstNodeRef<ast::Parameter>),
ParameterWithDefault(AstNodeRef<ast::ParameterWithDefault>),
WithItem(WithItemDefinitionKind),
MatchPattern(MatchPatternDefinitionKind),
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct MatchPatternDefinitionKind {
pattern: AstNodeRef<ast::Pattern>,
identifier: AstNodeRef<ast::Identifier>,
index: u32,
}
impl MatchPatternDefinitionKind {
pub(crate) fn pattern(&self) -> &ast::Pattern {
self.pattern.node()
}
pub(crate) fn index(&self) -> u32 {
self.index
}
}
#[derive(Clone, Debug)]
pub struct ComprehensionDefinitionKind {
node: AstNodeRef<ast::Comprehension>,
iterable: AstNodeRef<ast::Expr>,
target: AstNodeRef<ast::ExprName>,
first: bool,
}
impl ComprehensionDefinitionKind {
pub(crate) fn node(&self) -> &ast::Comprehension {
self.node.node()
pub(crate) fn iterable(&self) -> &ast::Expr {
self.iterable.node()
}
pub(crate) fn target(&self) -> &ast::ExprName {
self.target.node()
}
pub(crate) fn is_first(&self) -> bool {
@@ -393,12 +451,6 @@ impl From<&ast::StmtFor> for DefinitionNodeKey {
}
}
impl From<&ast::Comprehension> for DefinitionNodeKey {
fn from(node: &ast::Comprehension) -> Self {
Self(NodeKey::from_node(node))
}
}
impl From<&ast::Parameter> for DefinitionNodeKey {
fn from(node: &ast::Parameter) -> Self {
Self(NodeKey::from_node(node))
@@ -410,3 +462,9 @@ impl From<&ast::ParameterWithDefault> for DefinitionNodeKey {
Self(NodeKey::from_node(node))
}
}
impl From<&ast::Identifier> for DefinitionNodeKey {
fn from(identifier: &ast::Identifier) -> Self {
Self(NodeKey::from_node(identifier))
}
}

View File

@@ -149,6 +149,10 @@ impl FileScopeId {
FileScopeId::from_u32(0)
}
pub fn is_global(self) -> bool {
self == FileScopeId::global()
}
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
let index = semantic_index(db, file);
index.scope_ids_by_scope[self]

View File

@@ -152,9 +152,9 @@ pub(crate) fn definitions_ty<'db>(
);
let mut all_types = unbound_ty.into_iter().chain(def_types);
let Some(first) = all_types.next() else {
panic!("definitions_ty should never be called with zero definitions and no unbound_ty.")
};
let first = all_types
.next()
.expect("definitions_ty should never be called with zero definitions and no unbound_ty.");
if let Some(second) = all_types.next() {
let mut builder = UnionBuilder::new(db);
@@ -171,7 +171,7 @@ pub(crate) fn definitions_ty<'db>(
}
/// Unique ID for a type.
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Type<'db> {
/// the dynamic type: a statically-unknown set of values
Any,
@@ -216,14 +216,82 @@ impl<'db> Type<'db> {
matches!(self, Type::Unbound)
}
pub const fn is_unknown(&self) -> bool {
matches!(self, Type::Unknown)
}
pub const fn is_never(&self) -> bool {
matches!(self, Type::Never)
}
pub const fn into_class_type(self) -> Option<ClassType<'db>> {
match self {
Type::Class(class_type) => Some(class_type),
_ => None,
}
}
pub fn expect_class(self) -> ClassType<'db> {
self.into_class_type()
.expect("Expected a Type::Class variant")
}
pub const fn into_module_type(self) -> Option<File> {
match self {
Type::Module(file) => Some(file),
_ => None,
}
}
pub fn expect_module(self) -> File {
self.into_module_type()
.expect("Expected a Type::Module variant")
}
pub const fn into_union_type(self) -> Option<UnionType<'db>> {
match self {
Type::Union(union_type) => Some(union_type),
_ => None,
}
}
pub fn expect_union(self) -> UnionType<'db> {
self.into_union_type()
.expect("Expected a Type::Union variant")
}
pub const fn into_intersection_type(self) -> Option<IntersectionType<'db>> {
match self {
Type::Intersection(intersection_type) => Some(intersection_type),
_ => None,
}
}
pub fn expect_intersection(self) -> IntersectionType<'db> {
self.into_intersection_type()
.expect("Expected a Type::Intersection variant")
}
pub const fn into_function_type(self) -> Option<FunctionType<'db>> {
match self {
Type::Function(function_type) => Some(function_type),
_ => None,
}
}
pub fn expect_function(self) -> FunctionType<'db> {
self.into_function_type()
.expect("Expected a Type::Function variant")
}
pub const fn into_int_literal_type(self) -> Option<i64> {
match self {
Type::IntLiteral(value) => Some(value),
_ => None,
}
}
pub fn expect_int_literal(self) -> i64 {
self.into_int_literal_type()
.expect("Expected a Type::IntLiteral variant")
}
pub fn may_be_unbound(&self, db: &'db dyn Db) -> bool {
match self {
Type::Unbound => true,
@@ -320,8 +388,35 @@ impl<'db> Type<'db> {
}
}
/// Return the type resulting from calling an object of this type.
///
/// Returns `None` if `self` is not a callable type.
#[must_use]
pub fn instance(&self) -> Type<'db> {
pub fn call(&self, db: &'db dyn Db) -> Option<Type<'db>> {
match self {
Type::Function(function_type) => Some(function_type.return_type(db)),
// TODO annotated return type on `__new__` or metaclass `__call__`
Type::Class(class) => Some(Type::Instance(*class)),
// TODO: handle classes which implement the Callable protocol
Type::Instance(_instance_ty) => Some(Type::Unknown),
// `Any` is callable, and its return type is also `Any`.
Type::Any => Some(Type::Any),
Type::Unknown => Some(Type::Unknown),
// TODO: union and intersection types, if they reduce to `Callable`
Type::Union(_) => Some(Type::Unknown),
Type::Intersection(_) => Some(Type::Unknown),
_ => None,
}
}
#[must_use]
pub fn to_instance(&self) -> Type<'db> {
match self {
Type::Any => Type::Any,
Type::Unknown => Type::Unknown,
@@ -334,8 +429,11 @@ impl<'db> Type<'db> {
#[salsa::interned]
pub struct FunctionType<'db> {
/// name of the function at definition
#[return_ref]
pub name: ast::name::Name,
definition: Definition<'db>,
/// types of all decorators on this function
decorators: Vec<Type<'db>>,
}
@@ -344,11 +442,42 @@ impl<'db> FunctionType<'db> {
pub fn has_decorator(self, db: &dyn Db, decorator: Type<'_>) -> bool {
self.decorators(db).contains(&decorator)
}
/// inferred return type for this function
pub fn return_type(&self, db: &'db dyn Db) -> Type<'db> {
let definition = self.definition(db);
let DefinitionKind::Function(function_stmt_node) = definition.node(db) else {
panic!("Function type definition must have `DefinitionKind::Function`")
};
// TODO if a function `bar` is decorated by `foo`,
// where `foo` is annotated as returning a type `X` that is a subtype of `Callable`,
// we need to infer the return type from `X`'s return annotation
// rather than from `bar`'s return annotation
// in order to determine the type that `bar` returns
if !function_stmt_node.decorator_list.is_empty() {
return Type::Unknown;
}
function_stmt_node
.returns
.as_ref()
.map(|returns| {
if function_stmt_node.is_async {
// TODO: generic `types.CoroutineType`!
Type::Unknown
} else {
definition_expression_ty(db, definition, returns.as_ref())
}
})
.unwrap_or(Type::Unknown)
}
}
#[salsa::interned]
pub struct ClassType<'db> {
/// Name of the class at definition
#[return_ref]
pub name: ast::name::Name,
definition: Definition<'db>,
@@ -405,6 +534,7 @@ impl<'db> ClassType<'db> {
#[salsa::interned]
pub struct UnionType<'db> {
/// The union type includes values in any of these types.
#[return_ref]
elements: FxOrderSet<Type<'db>>,
}
@@ -417,6 +547,7 @@ impl<'db> UnionType<'db> {
#[salsa::interned]
pub struct IntersectionType<'db> {
/// The intersection type includes only values in all of these types.
#[return_ref]
positive: FxOrderSet<Type<'db>>,
/// The intersection type does not include any value in any of these types.
@@ -424,6 +555,7 @@ pub struct IntersectionType<'db> {
/// Negation types aren't expressible in annotations, and are most likely to arise from type
/// narrowing along with intersections (e.g. `if not isinstance(...)`), so we represent them
/// directly in intersections rather than as a separate type.
#[return_ref]
negative: FxOrderSet<Type<'db>>,
}
@@ -535,4 +667,25 @@ mod tests {
let b_file_diagnostics = super::check_types(&db, b_file);
assert_eq!(&*b_file_diagnostics, &[]);
}
#[test]
fn invalid_callable() {
let mut db = setup_db();
db.write_dedented(
"src/a.py",
"
nonsense = 123
x = nonsense()
",
)
.unwrap();
let a_file = system_path_to_file(&db, "/src/a.py").unwrap();
let a_file_diagnostics = super::check_types(&db, a_file);
assert_diagnostic_messages(
&a_file_diagnostics,
&["Object of type 'Literal[123]' is not callable"],
);
}
}

View File

@@ -25,8 +25,12 @@
//! * No type in an intersection can be a supertype of any other type in the intersection (just
//! eliminate the supertype from the intersection).
//! * An intersection containing two non-overlapping types should simplify to [`Type::Never`].
use crate::types::{IntersectionType, Type, UnionType};
use crate::{Db, FxOrderSet};
use ordermap::set::MutableValues;
use super::builtins_symbol_ty_by_name;
pub(crate) struct UnionBuilder<'db> {
elements: FxOrderSet<Type<'db>>,
@@ -45,7 +49,7 @@ impl<'db> UnionBuilder<'db> {
pub(crate) fn add(mut self, ty: Type<'db>) -> Self {
match ty {
Type::Union(union) => {
self.elements.extend(&union.elements(self.db));
self.elements.extend(union.elements(self.db));
}
Type::Never => {}
_ => {
@@ -56,11 +60,36 @@ impl<'db> UnionBuilder<'db> {
self
}
pub(crate) fn build(self) -> Type<'db> {
/// Performs the following normalizations:
/// - Replaces `Literal[True,False]` with `bool`.
/// - TODO For enums `E` with members `X1`,...,`Xn`, replaces
/// `Literal[E.X1,...,E.Xn]` with `E`.
fn simplify(&mut self) {
if let Some(true_index) = self.elements.get_index_of(&Type::BooleanLiteral(true)) {
if self.elements.contains(&Type::BooleanLiteral(false)) {
*self.elements.get_index_mut2(true_index).unwrap() =
builtins_symbol_ty_by_name(self.db, "bool");
self.elements.remove(&Type::BooleanLiteral(false));
}
}
}
pub(crate) fn build(mut self) -> Type<'db> {
match self.elements.len() {
0 => Type::Never,
1 => self.elements[0],
_ => Type::Union(UnionType::new(self.db, self.elements)),
_ => {
self.simplify();
match self.elements.len() {
0 => Type::Never,
1 => self.elements[0],
_ => {
self.elements.shrink_to_fit();
Type::Union(UnionType::new(self.db, self.elements))
}
}
}
}
}
}
@@ -247,25 +276,48 @@ impl<'db> InnerIntersectionBuilder<'db> {
mod tests {
use super::{IntersectionBuilder, IntersectionType, Type, UnionBuilder, UnionType};
use crate::db::tests::TestDb;
fn setup_db() -> TestDb {
TestDb::new()
}
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::types::builtins_symbol_ty_by_name;
use crate::ProgramSettings;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
impl<'db> UnionType<'db> {
fn elements_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
self.elements(db).into_iter().collect()
self.elements(db).into_iter().copied().collect()
}
}
fn setup_db() -> TestDb {
let db = TestDb::new();
let src_root = SystemPathBuf::from("/src");
db.memory_file_system()
.create_directory_all(&src_root)
.unwrap();
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings::new(src_root),
},
)
.expect("Valid search path settings");
db
}
#[test]
fn build_union() {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let t1 = Type::IntLiteral(1);
let Type::Union(union) = UnionBuilder::new(&db).add(t0).add(t1).build() else {
panic!("expected a union");
};
let union = UnionBuilder::new(&db)
.add(t0)
.add(t1)
.build()
.expect_union();
assert_eq!(union.elements_vec(&db), &[t0, t1]);
}
@@ -296,6 +348,34 @@ mod tests {
assert_eq!(ty, t0);
}
#[test]
fn build_union_bool() {
let db = setup_db();
let bool_ty = builtins_symbol_ty_by_name(&db, "bool");
let t0 = Type::BooleanLiteral(true);
let t1 = Type::BooleanLiteral(true);
let t2 = Type::BooleanLiteral(false);
let t3 = Type::IntLiteral(17);
let union = UnionBuilder::new(&db)
.add(t0)
.add(t1)
.add(t3)
.build()
.expect_union();
assert_eq!(union.elements_vec(&db), &[t0, t3]);
let union = UnionBuilder::new(&db)
.add(t0)
.add(t1)
.add(t2)
.add(t3)
.build()
.expect_union();
assert_eq!(union.elements_vec(&db), &[bool_ty, t3]);
}
#[test]
fn build_union_flatten() {
let db = setup_db();
@@ -303,20 +383,22 @@ mod tests {
let t1 = Type::IntLiteral(1);
let t2 = Type::IntLiteral(2);
let u1 = UnionBuilder::new(&db).add(t0).add(t1).build();
let Type::Union(union) = UnionBuilder::new(&db).add(u1).add(t2).build() else {
panic!("expected a union");
};
let union = UnionBuilder::new(&db)
.add(u1)
.add(t2)
.build()
.expect_union();
assert_eq!(union.elements_vec(&db), &[t0, t1, t2]);
}
impl<'db> IntersectionType<'db> {
fn pos_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
self.positive(db).into_iter().collect()
self.positive(db).into_iter().copied().collect()
}
fn neg_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
self.negative(db).into_iter().collect()
self.negative(db).into_iter().copied().collect()
}
}
@@ -325,16 +407,14 @@ mod tests {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let ta = Type::Any;
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
let intersection = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_negative(t0)
.build()
else {
panic!("expected to be an intersection");
};
.expect_intersection();
assert_eq!(inter.pos_vec(&db), &[ta]);
assert_eq!(inter.neg_vec(&db), &[t0]);
assert_eq!(intersection.pos_vec(&db), &[ta]);
assert_eq!(intersection.neg_vec(&db), &[t0]);
}
#[test]
@@ -347,16 +427,14 @@ mod tests {
.add_positive(ta)
.add_negative(t1)
.build();
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
let intersection = IntersectionBuilder::new(&db)
.add_positive(t2)
.add_positive(i0)
.build()
else {
panic!("expected to be an intersection");
};
.expect_intersection();
assert_eq!(inter.pos_vec(&db), &[t2, ta]);
assert_eq!(inter.neg_vec(&db), &[t1]);
assert_eq!(intersection.pos_vec(&db), &[t2, ta]);
assert_eq!(intersection.neg_vec(&db), &[t1]);
}
#[test]
@@ -369,16 +447,14 @@ mod tests {
.add_positive(ta)
.add_negative(t1)
.build();
let Type::Intersection(inter) = IntersectionBuilder::new(&db)
let intersection = IntersectionBuilder::new(&db)
.add_positive(t2)
.add_negative(i0)
.build()
else {
panic!("expected to be an intersection");
};
.expect_intersection();
assert_eq!(inter.pos_vec(&db), &[t2, t1]);
assert_eq!(inter.neg_vec(&db), &[ta]);
assert_eq!(intersection.pos_vec(&db), &[t2, t1]);
assert_eq!(intersection.neg_vec(&db), &[ta]);
}
#[test]
@@ -389,13 +465,11 @@ mod tests {
let ta = Type::Any;
let u0 = UnionBuilder::new(&db).add(t0).add(t1).build();
let Type::Union(union) = IntersectionBuilder::new(&db)
let union = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_positive(u0)
.build()
else {
panic!("expected a union");
};
.expect_union();
let [Type::Intersection(i0), Type::Intersection(i1)] = union.elements_vec(&db)[..] else {
panic!("expected a union of two intersections");
};

View File

@@ -6,12 +6,16 @@ use ruff_python_ast::str::Quote;
use ruff_python_literal::escape::AsciiEscape;
use crate::types::{IntersectionType, Type, UnionType};
use crate::Db;
use crate::{Db, FxOrderMap};
impl<'db> Type<'db> {
pub fn display(&'db self, db: &'db dyn Db) -> DisplayType<'db> {
DisplayType { ty: self, db }
}
fn representation(&'db self, db: &'db dyn Db) -> DisplayRepresentation<'db> {
DisplayRepresentation { db, ty: self }
}
}
#[derive(Copy, Clone)]
@@ -21,6 +25,39 @@ pub struct DisplayType<'db> {
}
impl Display for DisplayType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let representation = self.ty.representation(self.db);
if matches!(
self.ty,
Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::StringLiteral(_)
| Type::BytesLiteral(_)
| Type::Class(_)
| Type::Function(_)
) {
write!(f, "Literal[{representation}]",)
} else {
representation.fmt(f)
}
}
}
impl std::fmt::Debug for DisplayType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
/// Writes the string representation of a type, which is the value displayed either as
/// `Literal[<repr>]` or `Literal[<repr1>, <repr2>]` for literal types or as `<repr>` for
/// non literals
struct DisplayRepresentation<'db> {
ty: &'db Type<'db>,
db: &'db dyn Db,
}
impl std::fmt::Display for DisplayRepresentation<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.ty {
Type::Any => f.write_str("Any"),
@@ -32,39 +69,27 @@ impl Display for DisplayType<'_> {
write!(f, "<module '{:?}'>", file.path(self.db))
}
// TODO functions and classes should display using a fully qualified name
Type::Class(class) => write!(f, "Literal[{}]", class.name(self.db)),
Type::Instance(class) => f.write_str(&class.name(self.db)),
Type::Function(function) => write!(f, "Literal[{}]", function.name(self.db)),
Type::Class(class) => f.write_str(class.name(self.db)),
Type::Instance(class) => f.write_str(class.name(self.db)),
Type::Function(function) => f.write_str(function.name(self.db)),
Type::Union(union) => union.display(self.db).fmt(f),
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
Type::IntLiteral(n) => write!(f, "Literal[{n}]"),
Type::BooleanLiteral(boolean) => {
write!(f, "Literal[{}]", if *boolean { "True" } else { "False" })
Type::IntLiteral(n) => write!(f, "{n}"),
Type::BooleanLiteral(boolean) => f.write_str(if *boolean { "True" } else { "False" }),
Type::StringLiteral(string) => {
write!(f, r#""{}""#, string.value(self.db).replace('"', r#"\""#))
}
Type::StringLiteral(string) => write!(
f,
r#"Literal["{}"]"#,
string.value(self.db).replace('"', r#"\""#)
),
Type::LiteralString => write!(f, "LiteralString"),
Type::LiteralString => f.write_str("LiteralString"),
Type::BytesLiteral(bytes) => {
let escape =
AsciiEscape::with_preferred_quote(bytes.value(self.db).as_ref(), Quote::Double);
f.write_str("Literal[")?;
escape.bytes_repr().write(f)?;
f.write_str("]")
escape.bytes_repr().write(f)
}
}
}
}
impl std::fmt::Debug for DisplayType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl<'db> UnionType<'db> {
fn display(&'db self, db: &'db dyn Db) -> DisplayUnionType<'db> {
DisplayUnionType { db, ty: self }
@@ -78,45 +103,58 @@ struct DisplayUnionType<'db> {
impl Display for DisplayUnionType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let union = self.ty;
let elements = self.ty.elements(self.db);
let (int_literals, other_types): (Vec<Type>, Vec<Type>) = union
.elements(self.db)
.iter()
.copied()
.partition(|ty| matches!(ty, Type::IntLiteral(_)));
// Group literal types by kind.
let mut grouped_literals = FxOrderMap::default();
for element in elements {
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
grouped_literals
.entry(literal_kind)
.or_insert_with(Vec::new)
.push(*element);
}
}
let mut first = true;
if !int_literals.is_empty() {
f.write_str("Literal[")?;
let mut nums: Vec<_> = int_literals
.into_iter()
.filter_map(|ty| {
if let Type::IntLiteral(n) = ty {
Some(n)
} else {
None
}
})
.collect();
nums.sort_unstable();
for num in nums {
// Print all types, but write all literals together (while preserving their position).
for ty in elements {
if let Ok(literal_kind) = LiteralTypeKind::try_from(*ty) {
let Some(mut literals) = grouped_literals.remove(&literal_kind) else {
continue;
};
if !first {
f.write_str(", ")?;
f.write_str(" | ")?;
};
f.write_str("Literal[")?;
if literal_kind == LiteralTypeKind::IntLiteral {
literals.sort_unstable_by_key(|ty| ty.expect_int_literal());
}
write!(f, "{num}")?;
first = false;
for (i, literal_ty) in literals.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
literal_ty.representation(self.db).fmt(f)?;
}
f.write_str("]")?;
} else {
if !first {
f.write_str(" | ")?;
};
ty.display(self.db).fmt(f)?;
}
f.write_str("]")?;
first = false;
}
for ty in other_types {
if !first {
f.write_str(" | ")?;
};
first = false;
write!(f, "{}", ty.display(self.db))?;
}
debug_assert!(grouped_literals.is_empty());
Ok(())
}
@@ -128,6 +166,30 @@ impl std::fmt::Debug for DisplayUnionType<'_> {
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
enum LiteralTypeKind {
Class,
Function,
IntLiteral,
StringLiteral,
BytesLiteral,
}
impl TryFrom<Type<'_>> for LiteralTypeKind {
type Error = ();
fn try_from(value: Type<'_>) -> Result<Self, Self::Error> {
match value {
Type::Class(_) => Ok(Self::Class),
Type::Function(_) => Ok(Self::Function),
Type::IntLiteral(_) => Ok(Self::IntLiteral),
Type::StringLiteral(_) => Ok(Self::StringLiteral),
Type::BytesLiteral(_) => Ok(Self::BytesLiteral),
_ => Err(()),
}
}
}
impl<'db> IntersectionType<'db> {
fn display(&'db self, db: &'db dyn Db) -> DisplayIntersectionType<'db> {
DisplayIntersectionType { db, ty: self }
@@ -167,3 +229,91 @@ impl std::fmt::Debug for DisplayIntersectionType<'_> {
std::fmt::Display::fmt(self, f)
}
}
#[cfg(test)]
mod tests {
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use crate::db::tests::TestDb;
use crate::types::{
global_symbol_ty_by_name, BytesLiteralType, StringLiteralType, Type, UnionBuilder,
};
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
fn setup_db() -> TestDb {
let db = TestDb::new();
let src_root = SystemPathBuf::from("/src");
db.memory_file_system()
.create_directory_all(&src_root)
.unwrap();
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings::new(src_root),
},
)
.expect("Valid search path settings");
db
}
#[test]
fn test_condense_literal_display_by_type() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_dedented(
"src/main.py",
"
def foo(x: int) -> int:
return x + 1
def bar(s: str) -> str:
return s
class A: ...
class B: ...
",
)?;
let mod_file = system_path_to_file(&db, "src/main.py").expect("Expected file to exist.");
let vec: Vec<Type<'_>> = vec![
Type::Unknown,
Type::IntLiteral(-1),
global_symbol_ty_by_name(&db, mod_file, "A"),
Type::StringLiteral(StringLiteralType::new(&db, Box::from("A"))),
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([0]))),
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([7]))),
Type::IntLiteral(0),
Type::IntLiteral(1),
Type::StringLiteral(StringLiteralType::new(&db, Box::from("B"))),
global_symbol_ty_by_name(&db, mod_file, "foo"),
global_symbol_ty_by_name(&db, mod_file, "bar"),
global_symbol_ty_by_name(&db, mod_file, "B"),
Type::BooleanLiteral(true),
Type::None,
];
let builder = vec.iter().fold(UnionBuilder::new(&db), |builder, literal| {
builder.add(*literal)
});
let union = builder.build().expect_union();
let display = format!("{}", union.display(&db));
assert_eq!(
display,
concat!(
"Unknown | ",
"Literal[-1, 0, 1] | ",
"Literal[A, B] | ",
"Literal[\"A\", \"B\"] | ",
"Literal[b\"\\x00\", b\"\\x07\"] | ",
"Literal[foo, bar] | ",
"Literal[True] | ",
"None"
)
);
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
1ace5718deaf3041f8e3d1dc9c9e8a8e830e517f
23d867efb2df6de5600f64656f1aa8a83e06109e

View File

@@ -1,7 +1,7 @@
import sys
import typing_extensions
from typing import Any, ClassVar, Generic, Literal, TypedDict, overload
from typing_extensions import Unpack
from typing_extensions import Self, Unpack
PyCF_ONLY_AST: Literal[1024]
PyCF_TYPE_COMMENTS: Literal[4096]
@@ -34,6 +34,9 @@ class AST:
if sys.version_info >= (3, 13):
_field_types: ClassVar[dict[str, Any]]
if sys.version_info >= (3, 14):
def __replace__(self) -> Self: ...
class mod(AST): ...
class type_ignore(AST): ...
@@ -44,6 +47,9 @@ class TypeIgnore(type_ignore):
tag: str
def __init__(self, lineno: int, tag: str) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, lineno: int = ..., tag: str = ...) -> Self: ...
class FunctionType(mod):
if sys.version_info >= (3, 10):
__match_args__ = ("argtypes", "returns")
@@ -57,6 +63,9 @@ class FunctionType(mod):
else:
def __init__(self, argtypes: list[expr], returns: expr) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, argtypes: list[expr] = ..., returns: expr = ...) -> Self: ...
class Module(mod):
if sys.version_info >= (3, 10):
__match_args__ = ("body", "type_ignores")
@@ -67,6 +76,9 @@ class Module(mod):
else:
def __init__(self, body: list[stmt], type_ignores: list[TypeIgnore]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, body: list[stmt] = ..., type_ignores: list[TypeIgnore] = ...) -> Self: ...
class Interactive(mod):
if sys.version_info >= (3, 10):
__match_args__ = ("body",)
@@ -76,12 +88,18 @@ class Interactive(mod):
else:
def __init__(self, body: list[stmt]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, body: list[stmt] = ...) -> Self: ...
class Expression(mod):
if sys.version_info >= (3, 10):
__match_args__ = ("body",)
body: expr
def __init__(self, body: expr) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, body: expr = ...) -> Self: ...
class stmt(AST):
lineno: int
col_offset: int
@@ -89,6 +107,9 @@ class stmt(AST):
end_col_offset: int | None
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, **kwargs: Unpack[_Attributes]) -> Self: ...
class FunctionDef(stmt):
if sys.version_info >= (3, 12):
__match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params")
@@ -152,6 +173,19 @@ class FunctionDef(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
name: _Identifier = ...,
args: arguments = ...,
body: list[stmt] = ...,
decorator_list: list[expr] = ...,
returns: expr | None = ...,
type_comment: str | None = ...,
type_params: list[type_param] = ...,
) -> Self: ...
class AsyncFunctionDef(stmt):
if sys.version_info >= (3, 12):
__match_args__ = ("name", "args", "body", "decorator_list", "returns", "type_comment", "type_params")
@@ -215,6 +249,19 @@ class AsyncFunctionDef(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
name: _Identifier = ...,
args: arguments = ...,
body: list[stmt],
decorator_list: list[expr],
returns: expr | None,
type_comment: str | None,
type_params: list[type_param],
) -> Self: ...
class ClassDef(stmt):
if sys.version_info >= (3, 12):
__match_args__ = ("name", "bases", "keywords", "body", "decorator_list", "type_params")
@@ -260,12 +307,28 @@ class ClassDef(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
name: _Identifier,
bases: list[expr],
keywords: list[keyword],
body: list[stmt],
decorator_list: list[expr],
type_params: list[type_param],
**kwargs: Unpack[_Attributes],
) -> Self: ...
class Return(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("value",)
value: expr | None
def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Delete(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("targets",)
@@ -275,6 +338,9 @@ class Delete(stmt):
else:
def __init__(self, targets: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, targets: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Assign(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("targets", "value", "type_comment")
@@ -295,6 +361,11 @@ class Assign(stmt):
self, targets: list[expr], value: expr, type_comment: str | None = None, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, targets: list[expr] = ..., value: expr = ..., type_comment: str | None = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class AugAssign(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("target", "op", "value")
@@ -305,6 +376,16 @@ class AugAssign(stmt):
self, target: Name | Attribute | Subscript, op: operator, value: expr, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
target: Name | Attribute | Subscript = ...,
op: operator = ...,
value: expr = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class AnnAssign(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("target", "annotation", "value", "simple")
@@ -332,6 +413,17 @@ class AnnAssign(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
target: Name | Attribute | Subscript = ...,
annotation: expr = ...,
value: expr | None = ...,
simple: int = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class For(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("target", "iter", "body", "orelse", "type_comment")
@@ -361,6 +453,18 @@ class For(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
target: expr = ...,
iter: expr = ...,
body: list[stmt] = ...,
orelse: list[stmt] = ...,
type_comment: str | None = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class AsyncFor(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("target", "iter", "body", "orelse", "type_comment")
@@ -390,6 +494,18 @@ class AsyncFor(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
target: expr = ...,
iter: expr = ...,
body: list[stmt] = ...,
orelse: list[stmt] = ...,
type_comment: str | None = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class While(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("test", "body", "orelse")
@@ -403,6 +519,9 @@ class While(stmt):
else:
def __init__(self, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> Self: ...
class If(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("test", "body", "orelse")
@@ -416,6 +535,11 @@ class If(stmt):
else:
def __init__(self, test: expr, body: list[stmt], orelse: list[stmt], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, test: expr = ..., body: list[stmt] = ..., orelse: list[stmt] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class With(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("items", "body", "type_comment")
@@ -435,6 +559,16 @@ class With(stmt):
self, items: list[withitem], body: list[stmt], type_comment: str | None = None, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
items: list[withitem] = ...,
body: list[stmt] = ...,
type_comment: str | None = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class AsyncWith(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("items", "body", "type_comment")
@@ -454,6 +588,16 @@ class AsyncWith(stmt):
self, items: list[withitem], body: list[stmt], type_comment: str | None = None, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
items: list[withitem] = ...,
body: list[stmt] = ...,
type_comment: str | None = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class Raise(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("exc", "cause")
@@ -461,6 +605,9 @@ class Raise(stmt):
cause: expr | None
def __init__(self, exc: expr | None = None, cause: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, exc: expr | None = ..., cause: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Try(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("body", "handlers", "orelse", "finalbody")
@@ -487,6 +634,17 @@ class Try(stmt):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
body: list[stmt] = ...,
handlers: list[ExceptHandler] = ...,
orelse: list[stmt] = ...,
finalbody: list[stmt] = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
if sys.version_info >= (3, 11):
class TryStar(stmt):
__match_args__ = ("body", "handlers", "orelse", "finalbody")
@@ -513,6 +671,17 @@ if sys.version_info >= (3, 11):
**kwargs: Unpack[_Attributes],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
body: list[stmt] = ...,
handlers: list[ExceptHandler] = ...,
orelse: list[stmt] = ...,
finalbody: list[stmt] = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class Assert(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("test", "msg")
@@ -520,6 +689,9 @@ class Assert(stmt):
msg: expr | None
def __init__(self, test: expr, msg: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, test: expr, msg: expr | None, **kwargs: Unpack[_Attributes]) -> Self: ...
class Import(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("names",)
@@ -529,6 +701,9 @@ class Import(stmt):
else:
def __init__(self, names: list[alias], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, names: list[alias] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class ImportFrom(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("module", "names", "level")
@@ -550,6 +725,11 @@ class ImportFrom(stmt):
self, module: str | None = None, *, names: list[alias], level: int, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, module: str | None = ..., names: list[alias] = ..., level: int = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Global(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("names",)
@@ -559,6 +739,9 @@ class Global(stmt):
else:
def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> Self: ...
class Nonlocal(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("names",)
@@ -568,12 +751,18 @@ class Nonlocal(stmt):
else:
def __init__(self, names: list[_Identifier], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, names: list[_Identifier] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Expr(stmt):
if sys.version_info >= (3, 10):
__match_args__ = ("value",)
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Pass(stmt): ...
class Break(stmt): ...
class Continue(stmt): ...
@@ -585,6 +774,9 @@ class expr(AST):
end_col_offset: int | None
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, **kwargs: Unpack[_Attributes]) -> Self: ...
class BoolOp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("op", "values")
@@ -595,6 +787,9 @@ class BoolOp(expr):
else:
def __init__(self, op: boolop, values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, op: boolop = ..., values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class BinOp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("left", "op", "right")
@@ -603,6 +798,11 @@ class BinOp(expr):
right: expr
def __init__(self, left: expr, op: operator, right: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, left: expr = ..., op: operator = ..., right: expr = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class UnaryOp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("op", "operand")
@@ -610,6 +810,9 @@ class UnaryOp(expr):
operand: expr
def __init__(self, op: unaryop, operand: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, op: unaryop = ..., operand: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Lambda(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("args", "body")
@@ -617,6 +820,9 @@ class Lambda(expr):
body: expr
def __init__(self, args: arguments, body: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, args: arguments = ..., body: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class IfExp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("test", "body", "orelse")
@@ -625,6 +831,11 @@ class IfExp(expr):
orelse: expr
def __init__(self, test: expr, body: expr, orelse: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, test: expr = ..., body: expr = ..., orelse: expr = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Dict(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("keys", "values")
@@ -635,6 +846,11 @@ class Dict(expr):
else:
def __init__(self, keys: list[expr | None], values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, keys: list[expr | None] = ..., values: list[expr] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Set(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elts",)
@@ -644,6 +860,9 @@ class Set(expr):
else:
def __init__(self, elts: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, elts: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class ListComp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elt", "generators")
@@ -654,6 +873,11 @@ class ListComp(expr):
else:
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class SetComp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elt", "generators")
@@ -664,6 +888,11 @@ class SetComp(expr):
else:
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class DictComp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("key", "value", "generators")
@@ -677,6 +906,11 @@ class DictComp(expr):
else:
def __init__(self, key: expr, value: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, key: expr = ..., value: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class GeneratorExp(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elt", "generators")
@@ -687,24 +921,38 @@ class GeneratorExp(expr):
else:
def __init__(self, elt: expr, generators: list[comprehension], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, elt: expr = ..., generators: list[comprehension] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Await(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value",)
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Yield(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value",)
value: expr | None
def __init__(self, value: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class YieldFrom(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value",)
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Compare(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("left", "ops", "comparators")
@@ -718,6 +966,11 @@ class Compare(expr):
else:
def __init__(self, left: expr, ops: list[cmpop], comparators: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, left: expr = ..., ops: list[cmpop] = ..., comparators: list[expr] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Call(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("func", "args", "keywords")
@@ -731,6 +984,11 @@ class Call(expr):
else:
def __init__(self, func: expr, args: list[expr], keywords: list[keyword], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, func: expr = ..., args: list[expr] = ..., keywords: list[keyword] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class FormattedValue(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value", "conversion", "format_spec")
@@ -739,6 +997,11 @@ class FormattedValue(expr):
format_spec: expr | None
def __init__(self, value: expr, conversion: int, format_spec: expr | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, value: expr = ..., conversion: int = ..., format_spec: expr | None = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class JoinedStr(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("values",)
@@ -748,6 +1011,9 @@ class JoinedStr(expr):
else:
def __init__(self, values: list[expr], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, values: list[expr] = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Constant(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value", "kind")
@@ -760,6 +1026,9 @@ class Constant(expr):
def __init__(self, value: Any, kind: str | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: Any = ..., kind: str | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class NamedExpr(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("target", "value")
@@ -767,6 +1036,9 @@ class NamedExpr(expr):
value: expr
def __init__(self, target: Name, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, target: Name = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Attribute(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value", "attr", "ctx")
@@ -775,6 +1047,11 @@ class Attribute(expr):
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
def __init__(self, value: expr, attr: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, value: expr = ..., attr: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
if sys.version_info >= (3, 9):
_Slice: typing_extensions.TypeAlias = expr
_SliceAttributes: typing_extensions.TypeAlias = _Attributes
@@ -794,6 +1071,16 @@ class Slice(_Slice):
self, lower: expr | None = None, upper: expr | None = None, step: expr | None = None, **kwargs: Unpack[_SliceAttributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
lower: expr | None = ...,
upper: expr | None = ...,
step: expr | None = ...,
**kwargs: Unpack[_SliceAttributes],
) -> Self: ...
if sys.version_info < (3, 9):
class ExtSlice(slice):
dims: list[slice]
@@ -811,6 +1098,11 @@ class Subscript(expr):
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
def __init__(self, value: expr, slice: _Slice, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, value: expr = ..., slice: _Slice = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class Starred(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("value", "ctx")
@@ -818,6 +1110,9 @@ class Starred(expr):
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
def __init__(self, value: expr, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Name(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("id", "ctx")
@@ -825,6 +1120,9 @@ class Name(expr):
ctx: expr_context # Not present in Python < 3.13 if not passed to `__init__`
def __init__(self, id: _Identifier, ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, id: _Identifier = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class List(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elts", "ctx")
@@ -835,6 +1133,9 @@ class List(expr):
else:
def __init__(self, elts: list[expr], ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class Tuple(expr):
if sys.version_info >= (3, 10):
__match_args__ = ("elts", "ctx")
@@ -847,6 +1148,9 @@ class Tuple(expr):
else:
def __init__(self, elts: list[expr], ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class expr_context(AST): ...
if sys.version_info < (3, 9):
@@ -910,6 +1214,9 @@ class comprehension(AST):
else:
def __init__(self, target: expr, iter: expr, ifs: list[expr], is_async: int) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, target: expr = ..., iter: expr = ..., ifs: list[expr] = ..., is_async: int = ...) -> Self: ...
class excepthandler(AST):
lineno: int
col_offset: int
@@ -917,6 +1224,11 @@ class excepthandler(AST):
end_col_offset: int | None
def __init__(self, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int | None = ..., end_col_offset: int | None = ...
) -> Self: ...
class ExceptHandler(excepthandler):
if sys.version_info >= (3, 10):
__match_args__ = ("type", "name", "body")
@@ -937,6 +1249,16 @@ class ExceptHandler(excepthandler):
self, type: expr | None = None, name: _Identifier | None = None, *, body: list[stmt], **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
type: expr | None = ...,
name: _Identifier | None = ...,
body: list[stmt] = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class arguments(AST):
if sys.version_info >= (3, 10):
__match_args__ = ("posonlyargs", "args", "vararg", "kwonlyargs", "kw_defaults", "kwarg", "defaults")
@@ -995,6 +1317,19 @@ class arguments(AST):
defaults: list[expr],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
posonlyargs: list[arg] = ...,
args: list[arg] = ...,
vararg: arg | None = ...,
kwonlyargs: list[arg] = ...,
kw_defaults: list[expr | None] = ...,
kwarg: arg | None = ...,
defaults: list[expr] = ...,
) -> Self: ...
class arg(AST):
lineno: int
col_offset: int
@@ -1009,6 +1344,16 @@ class arg(AST):
self, arg: _Identifier, annotation: expr | None = None, type_comment: str | None = None, **kwargs: Unpack[_Attributes]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
arg: _Identifier = ...,
annotation: expr | None = ...,
type_comment: str | None = ...,
**kwargs: Unpack[_Attributes],
) -> Self: ...
class keyword(AST):
lineno: int
col_offset: int
@@ -1023,6 +1368,9 @@ class keyword(AST):
@overload
def __init__(self, arg: _Identifier | None = None, *, value: expr, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, arg: _Identifier | None = ..., value: expr = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class alias(AST):
lineno: int
col_offset: int
@@ -1034,6 +1382,9 @@ class alias(AST):
asname: _Identifier | None
def __init__(self, name: str, asname: _Identifier | None = None, **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, name: str = ..., asname: _Identifier | None = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
class withitem(AST):
if sys.version_info >= (3, 10):
__match_args__ = ("context_expr", "optional_vars")
@@ -1041,6 +1392,9 @@ class withitem(AST):
optional_vars: expr | None
def __init__(self, context_expr: expr, optional_vars: expr | None = None) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, context_expr: expr = ..., optional_vars: expr | None = ...) -> Self: ...
if sys.version_info >= (3, 10):
class Match(stmt):
__match_args__ = ("subject", "cases")
@@ -1051,6 +1405,11 @@ if sys.version_info >= (3, 10):
else:
def __init__(self, subject: expr, cases: list[match_case], **kwargs: Unpack[_Attributes]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, subject: expr = ..., cases: list[match_case] = ..., **kwargs: Unpack[_Attributes]
) -> Self: ...
class pattern(AST):
lineno: int
col_offset: int
@@ -1058,6 +1417,11 @@ if sys.version_info >= (3, 10):
end_col_offset: int
def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, lineno: int = ..., col_offset: int = ..., end_lineno: int = ..., end_col_offset: int = ...
) -> Self: ...
# Without the alias, Pyright complains variables named pattern are recursively defined
_Pattern: typing_extensions.TypeAlias = pattern
@@ -1074,16 +1438,25 @@ if sys.version_info >= (3, 10):
@overload
def __init__(self, pattern: _Pattern, guard: expr | None = None, *, body: list[stmt]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, pattern: _Pattern = ..., guard: expr | None = ..., body: list[stmt] = ...) -> Self: ...
class MatchValue(pattern):
__match_args__ = ("value",)
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: expr = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
class MatchSingleton(pattern):
__match_args__ = ("value",)
value: Literal[True, False] | None
def __init__(self, value: Literal[True, False] | None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, value: Literal[True, False] | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
class MatchSequence(pattern):
__match_args__ = ("patterns",)
patterns: list[pattern]
@@ -1092,11 +1465,17 @@ if sys.version_info >= (3, 10):
else:
def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
class MatchStar(pattern):
__match_args__ = ("name",)
name: _Identifier | None
def __init__(self, name: _Identifier | None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
class MatchMapping(pattern):
__match_args__ = ("keys", "patterns", "rest")
keys: list[expr]
@@ -1119,6 +1498,16 @@ if sys.version_info >= (3, 10):
**kwargs: Unpack[_Attributes[int]],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
keys: list[expr] = ...,
patterns: list[pattern] = ...,
rest: _Identifier | None = ...,
**kwargs: Unpack[_Attributes[int]],
) -> Self: ...
class MatchClass(pattern):
__match_args__ = ("cls", "patterns", "kwd_attrs", "kwd_patterns")
cls: expr
@@ -1144,6 +1533,17 @@ if sys.version_info >= (3, 10):
**kwargs: Unpack[_Attributes[int]],
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
cls: expr = ...,
patterns: list[pattern] = ...,
kwd_attrs: list[_Identifier] = ...,
kwd_patterns: list[pattern] = ...,
**kwargs: Unpack[_Attributes[int]],
) -> Self: ...
class MatchAs(pattern):
__match_args__ = ("pattern", "name")
pattern: _Pattern | None
@@ -1152,6 +1552,11 @@ if sys.version_info >= (3, 10):
self, pattern: _Pattern | None = None, name: _Identifier | None = None, **kwargs: Unpack[_Attributes[int]]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, pattern: _Pattern | None = ..., name: _Identifier | None = ..., **kwargs: Unpack[_Attributes[int]]
) -> Self: ...
class MatchOr(pattern):
__match_args__ = ("patterns",)
patterns: list[pattern]
@@ -1160,6 +1565,9 @@ if sys.version_info >= (3, 10):
else:
def __init__(self, patterns: list[pattern], **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, *, patterns: list[pattern] = ..., **kwargs: Unpack[_Attributes[int]]) -> Self: ...
if sys.version_info >= (3, 12):
class type_param(AST):
lineno: int
@@ -1168,6 +1576,9 @@ if sys.version_info >= (3, 12):
end_col_offset: int
def __init__(self, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(self, **kwargs: Unpack[_Attributes[int]]) -> Self: ...
class TypeVar(type_param):
if sys.version_info >= (3, 13):
__match_args__ = ("name", "bound", "default_value")
@@ -1187,6 +1598,16 @@ if sys.version_info >= (3, 12):
else:
def __init__(self, name: _Identifier, bound: expr | None = None, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
name: _Identifier = ...,
bound: expr | None = ...,
default_value: expr | None = ...,
**kwargs: Unpack[_Attributes[int]],
) -> Self: ...
class ParamSpec(type_param):
if sys.version_info >= (3, 13):
__match_args__ = ("name", "default_value")
@@ -1201,6 +1622,11 @@ if sys.version_info >= (3, 12):
else:
def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]]
) -> Self: ...
class TypeVarTuple(type_param):
if sys.version_info >= (3, 13):
__match_args__ = ("name", "default_value")
@@ -1215,6 +1641,11 @@ if sys.version_info >= (3, 12):
else:
def __init__(self, name: _Identifier, **kwargs: Unpack[_Attributes[int]]) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self, *, name: _Identifier = ..., default_value: expr | None = ..., **kwargs: Unpack[_Attributes[int]]
) -> Self: ...
class TypeAlias(stmt):
__match_args__ = ("name", "type_params", "value")
name: Name
@@ -1233,3 +1664,13 @@ if sys.version_info >= (3, 12):
def __init__(
self, name: Name, type_params: list[type_param], value: expr, **kwargs: Unpack[_Attributes[int]]
) -> None: ...
if sys.version_info >= (3, 14):
def __replace__(
self,
*,
name: Name = ...,
type_params: list[type_param] = ...,
value: expr = ...,
**kwargs: Unpack[_Attributes[int]],
) -> Self: ...

View File

@@ -2,7 +2,7 @@ import sys
from _typeshed import SupportsGetItem
from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence
from typing import Any, AnyStr, Generic, Protocol, SupportsAbs, SupportsIndex, TypeVar, final, overload
from typing_extensions import ParamSpec, TypeAlias, TypeVarTuple, Unpack
from typing_extensions import ParamSpec, TypeAlias, TypeIs, TypeVarTuple, Unpack
_R = TypeVar("_R")
_T = TypeVar("_T")
@@ -145,3 +145,7 @@ if sys.version_info >= (3, 11):
def call(obj: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
def _compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ...
if sys.version_info >= (3, 14):
def is_none(a: object, /) -> TypeIs[None]: ...
def is_not_none(a: _T | None, /) -> TypeIs[_T]: ...

View File

@@ -28,9 +28,9 @@ S_IFDIR: Final = 0o040000
# These are 0 on systems that don't support the specific kind of file.
# Example: Linux doesn't support door files, so S_IFDOOR is 0 on linux.
S_IFDOOR: int
S_IFPORT: int
S_IFWHT: int
S_IFDOOR: Final[int]
S_IFPORT: Final[int]
S_IFWHT: Final[int]
S_ISUID: Final = 0o4000
S_ISGID: Final = 0o2000
@@ -79,9 +79,9 @@ def S_ISWHT(mode: int, /) -> bool: ...
def filemode(mode: int, /) -> str: ...
if sys.platform == "win32":
IO_REPARSE_TAG_SYMLINK: int
IO_REPARSE_TAG_MOUNT_POINT: int
IO_REPARSE_TAG_APPEXECLINK: int
IO_REPARSE_TAG_SYMLINK: Final = 0xA000000C
IO_REPARSE_TAG_MOUNT_POINT: Final = 0xA0000003
IO_REPARSE_TAG_APPEXECLINK: Final = 0x8000001B
if sys.platform == "win32":
FILE_ATTRIBUTE_ARCHIVE: Final = 32

View File

@@ -1,3 +1,4 @@
import signal
import sys
from _typeshed import structseq
from collections.abc import Callable
@@ -16,16 +17,39 @@ class LockType:
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release(self) -> None: ...
def locked(self) -> bool: ...
def acquire_lock(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release_lock(self) -> None: ...
def locked_lock(self) -> bool: ...
def __enter__(self) -> bool: ...
def __exit__(
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> None: ...
if sys.version_info >= (3, 13):
@final
class _ThreadHandle:
ident: int
def join(self, timeout: float | None = None, /) -> None: ...
def is_done(self) -> bool: ...
def _set_done(self) -> None: ...
def start_joinable_thread(
function: Callable[[], object], handle: _ThreadHandle | None = None, daemon: bool = True
) -> _ThreadHandle: ...
lock = LockType
@overload
def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ...
@overload
def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
def interrupt_main() -> None: ...
if sys.version_info >= (3, 10):
def interrupt_main(signum: signal.Signals = ..., /) -> None: ...
else:
def interrupt_main() -> None: ...
def exit() -> NoReturn: ...
def allocate_lock() -> LockType: ...
def get_ident() -> int: ...

View File

@@ -106,8 +106,8 @@ EXCEPTION: Final = 8
READABLE: Final = 2
WRITABLE: Final = 4
TCL_VERSION: str
TK_VERSION: str
TCL_VERSION: Final[str]
TK_VERSION: Final[str]
@final
class TkttType:

View File

@@ -33,7 +33,8 @@ from collections.abc import Awaitable, Callable, Iterable, Iterator, MutableSet,
from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
from types import CellType, CodeType, TracebackType
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping} are imported from collections.abc in builtins.pyi
# mypy crashes if any of {ByteString, Sequence, MutableSequence, Mapping, MutableMapping}
# are imported from collections.abc in builtins.pyi
from typing import ( # noqa: Y022
IO,
Any,
@@ -967,7 +968,9 @@ class tuple(Sequence[_T_co]):
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
# Doesn't exist at runtime, but deleting this breaks mypy. See #2999
# Doesn't exist at runtime, but deleting this breaks mypy and pyright. See:
# https://github.com/python/typeshed/issues/7580
# https://github.com/python/mypy/issues/8240
@final
@type_check_only
class function:
@@ -1084,7 +1087,8 @@ class dict(MutableMapping[_KT, _VT]):
def keys(self) -> dict_keys[_KT, _VT]: ...
def values(self) -> dict_values[_KT, _VT]: ...
def items(self) -> dict_items[_KT, _VT]: ...
# Signature of `dict.fromkeys` should be kept identical to `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
# Signature of `dict.fromkeys` should be kept identical to
# `fromkeys` methods of `OrderedDict`/`ChainMap`/`UserDict` in `collections`
# TODO: the true signature of `dict.fromkeys` is not expressible in the current type system.
# See #3800 & https://github.com/python/typing/issues/548#issuecomment-683336963.
@classmethod

View File

@@ -1,10 +1,11 @@
from collections.abc import Callable
from typing import IO, Any, Final
from typing_extensions import LiteralString
__all__ = ["Cmd"]
PROMPT: Final = "(Cmd) "
IDENTCHARS: str # Too big to be `Literal`
IDENTCHARS: Final[LiteralString] # Too big to be `Literal`
class Cmd:
prompt: str

View File

@@ -345,15 +345,15 @@ class _OrderedDictValuesView(ValuesView[_VT_co], Reversible[_VT_co]):
# but they are not exposed anywhere)
# pyright doesn't have a specific error code for subclassing error!
@final
class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] # pyright: ignore
class _odict_keys(dict_keys[_KT_co, _VT_co], Reversible[_KT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __reversed__(self) -> Iterator[_KT_co]: ...
@final
class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] # pyright: ignore
class _odict_items(dict_items[_KT_co, _VT_co], Reversible[tuple[_KT_co, _VT_co]]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ...
@final
class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] # pyright: ignore
class _odict_values(dict_values[_KT_co, _VT_co], Reversible[_VT_co], Generic[_KT_co, _VT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __reversed__(self) -> Iterator[_VT_co]: ...
class OrderedDict(dict[_KT, _VT], Reversible[_KT], Generic[_KT, _VT]):
@@ -475,7 +475,8 @@ class ChainMap(MutableMapping[_KT, _VT]):
def pop(self, key: _KT, default: _T) -> _VT | _T: ...
def copy(self) -> Self: ...
__copy__ = copy
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime, so the signature should be kept in line with `dict.fromkeys`.
# All arguments to `fromkeys` are passed to `dict.fromkeys` at runtime,
# so the signature should be kept in line with `dict.fromkeys`.
@classmethod
@overload
def fromkeys(cls, iterable: Iterable[_T]) -> ChainMap[_T, Any | None]: ...

View File

@@ -1,12 +1,13 @@
import sys
from typing import Final
if sys.platform != "win32":
class _Method: ...
METHOD_CRYPT: _Method
METHOD_MD5: _Method
METHOD_SHA256: _Method
METHOD_SHA512: _Method
METHOD_BLOWFISH: _Method
METHOD_CRYPT: Final[_Method]
METHOD_MD5: Final[_Method]
METHOD_SHA256: Final[_Method]
METHOD_SHA512: Final[_Method]
METHOD_BLOWFISH: Final[_Method]
methods: list[_Method]
def mksalt(method: _Method | None = None, *, rounds: int | None = None) -> str: ...
def crypt(word: str, salt: str | _Method | None = None) -> str: ...

View File

@@ -185,3 +185,8 @@ if sys.version_info >= (3, 12):
c_time_t: type[c_int32 | c_int64] # alias for one or the other at runtime
class py_object(_CanCastTo, _SimpleCData[_T]): ...
if sys.version_info >= (3, 14):
class c_float_complex(_SimpleCData[complex]): ...
class c_double_complex(_SimpleCData[complex]): ...
class c_longdouble_complex(_SimpleCData[complex]): ...

View File

@@ -229,18 +229,17 @@ if sys.version_info >= (3, 9):
else:
class _InitVarMeta(type):
# Not used, instead `InitVar.__class_getitem__` is called.
# pyright ignore is needed because pyright (not unreasonably) thinks this
# is an invalid use of InitVar.
def __getitem__(self, params: Any) -> InitVar[Any]: ... # pyright: ignore
# pyright (not unreasonably) thinks this is an invalid use of InitVar.
def __getitem__(self, params: Any) -> InitVar[Any]: ... # pyright: ignore[reportInvalidTypeForm]
class InitVar(Generic[_T], metaclass=_InitVarMeta):
type: Type[_T]
def __init__(self, type: Type[_T]) -> None: ...
if sys.version_info >= (3, 9):
@overload
def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore
def __class_getitem__(cls, type: Type[_T]) -> InitVar[_T]: ... # pyright: ignore[reportInvalidTypeForm]
@overload
def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore
def __class_getitem__(cls, type: Any) -> InitVar[Any]: ... # pyright: ignore[reportInvalidTypeForm]
if sys.version_info >= (3, 12):
def make_dataclass(

View File

@@ -265,12 +265,12 @@ class datetime(date):
def fromtimestamp(cls, timestamp: float, /, tz: _TzInfo | None = ...) -> Self: ...
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.UTC)")
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .fromtimestamp(datetime.timezone.utc)")
def utcfromtimestamp(cls, t: float, /) -> Self: ...
@classmethod
def now(cls, tz: _TzInfo | None = None) -> Self: ...
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.timezone.utc)")
def utcnow(cls) -> Self: ...
@classmethod
def combine(cls, date: _Date, time: _Time, tzinfo: _TzInfo | None = ...) -> Self: ...

View File

@@ -30,6 +30,7 @@ _CommandT = TypeVar("_CommandT", bound=Command)
_Ts = TypeVarTuple("_Ts")
class Command:
dry_run: Literal[0, 1] # Exposed from __getattr_. Same as Distribution.dry_run
distribution: Distribution
# Any to work around variance issues
sub_commands: ClassVar[list[tuple[str, Callable[[Any], bool] | None]]]

View File

@@ -1,4 +1,4 @@
from typing import Any, ClassVar, Literal
from typing import Any, ClassVar, Final, Literal
from typing_extensions import TypeAlias
from ..cmd import Command
@@ -22,7 +22,7 @@ class SilentReporter(_Reporter):
) -> None: ...
def system_message(self, level, message, *children, **kwargs): ...
HAS_DOCUTILS: bool
HAS_DOCUTILS: Final[bool]
class check(Command):
description: str

View File

@@ -1,12 +1,12 @@
from _typeshed import StrOrBytesPath
from collections.abc import Sequence
from re import Pattern
from typing import Any, ClassVar, Literal
from typing import Any, ClassVar, Final, Literal
from ..ccompiler import CCompiler
from ..cmd import Command
LANG_EXT: dict[str, str]
LANG_EXT: Final[dict[str, str]]
class config(Command):
description: str

View File

@@ -1,11 +1,16 @@
import sys
from collections.abc import Callable
from typing import Any, ClassVar
from typing import Any, ClassVar, Final, Literal
from ..cmd import Command
HAS_USER_SITE: bool
SCHEME_KEYS: tuple[str, ...]
INSTALL_SCHEMES: dict[str, dict[Any, Any]]
HAS_USER_SITE: Final[bool]
SCHEME_KEYS: Final[tuple[Literal["purelib"], Literal["platlib"], Literal["headers"], Literal["scripts"], Literal["data"]]]
INSTALL_SCHEMES: Final[dict[str, dict[str, str]]]
if sys.version_info < (3, 10):
WINDOWS_SCHEME: Final[dict[str, str]]
class install(Command):
description: str

View File

@@ -1,8 +1,8 @@
from typing import Any, ClassVar
from typing import Any, ClassVar, Final
from ..cmd import Command
PYTHON_SOURCE_EXTENSION: str
PYTHON_SOURCE_EXTENSION: Final = ".py"
class install_lib(Command):
description: str

View File

@@ -3,9 +3,9 @@ from collections.abc import Mapping
from distutils.cmd import Command as Command
from distutils.dist import Distribution as Distribution
from distutils.extension import Extension as Extension
from typing import Any, Literal
from typing import Any, Final, Literal
USAGE: str
USAGE: Final[str]
def gen_usage(script_name: StrOrBytesPath) -> str: ...

View File

@@ -1,20 +1,20 @@
from distutils.unixccompiler import UnixCCompiler
from distutils.version import LooseVersion
from re import Pattern
from typing import Literal
from typing import Final, Literal
def get_msvcr() -> list[str] | None: ...
class CygwinCCompiler(UnixCCompiler): ...
class Mingw32CCompiler(CygwinCCompiler): ...
CONFIG_H_OK: str
CONFIG_H_NOTOK: str
CONFIG_H_UNCERTAIN: str
CONFIG_H_OK: Final = "ok"
CONFIG_H_NOTOK: Final = "not ok"
CONFIG_H_UNCERTAIN: Final = "uncertain"
def check_config_h() -> tuple[Literal["ok", "not ok", "uncertain"], str]: ...
RE_VERSION: Pattern[bytes]
RE_VERSION: Final[Pattern[bytes]]
def get_versions() -> tuple[LooseVersion | None, ...]: ...
def is_cygwingcc() -> bool: ...

View File

@@ -1 +1,3 @@
DEBUG: bool | None
from typing import Final
DEBUG: Final[str | None]

View File

@@ -88,9 +88,9 @@ class Distribution:
display_options: ClassVar[_OptionsList]
display_option_names: ClassVar[list[str]]
negative_opt: ClassVar[dict[str, str]]
verbose: int
dry_run: int
help: int
verbose: Literal[0, 1]
dry_run: Literal[0, 1]
help: Literal[0, 1]
command_packages: list[str] | None
script_name: str | None
script_args: list[str] | None

View File

@@ -1,15 +1,15 @@
from collections.abc import Iterable, Mapping
from re import Pattern
from typing import Any, overload
from typing import Any, Final, overload
from typing_extensions import TypeAlias
_Option: TypeAlias = tuple[str, str | None, str]
_GR: TypeAlias = tuple[list[str], OptionDummy]
longopt_pat: str
longopt_re: Pattern[str]
neg_alias_re: Pattern[str]
longopt_xlate: dict[int, int]
longopt_pat: Final = r"[a-zA-Z](?:[a-zA-Z0-9-]*)"
longopt_re: Final[Pattern[str]]
neg_alias_re: Final[Pattern[str]]
longopt_xlate: Final[dict[int, int]]
class FancyGetopt:
def __init__(self, option_table: list[_Option] | None = None) -> None: ...
@@ -25,7 +25,7 @@ def fancy_getopt(
options: list[_Option], negative_opt: Mapping[_Option, _Option], object: Any, args: list[str] | None
) -> list[str] | _GR: ...
WS_TRANS: dict[int, str]
WS_TRANS: Final[dict[int, str]]
def wrap_text(text: str, width: int) -> list[str]: ...
def translate_longopt(opt: str) -> str: ...

View File

@@ -1,10 +1,10 @@
from typing import Any
from typing import Any, Final
DEBUG: int
INFO: int
WARN: int
ERROR: int
FATAL: int
DEBUG: Final = 1
INFO: Final = 2
WARN: Final = 3
ERROR: Final = 4
FATAL: Final = 5
class Log:
def __init__(self, threshold: int = 3) -> None: ...

View File

@@ -1,15 +1,15 @@
import sys
from collections.abc import Mapping
from distutils.ccompiler import CCompiler
from typing import Literal, overload
from typing import Final, Literal, overload
from typing_extensions import deprecated
PREFIX: str
EXEC_PREFIX: str
BASE_PREFIX: str
BASE_EXEC_PREFIX: str
project_base: str
python_build: bool
PREFIX: Final[str]
EXEC_PREFIX: Final[str]
BASE_PREFIX: Final[str]
BASE_EXEC_PREFIX: Final[str]
project_base: Final[str]
python_build: Final[bool]
def expand_makefile_vars(s: str, vars: Mapping[str, str]) -> str: ...
@overload

View File

@@ -50,7 +50,8 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
def get_payload(self, i: None = None, *, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
@overload # not multipart, IDEM but w/o kwarg
def get_payload(self, i: None, decode: Literal[True]) -> _EncodedPayloadType | Any: ...
# If `charset=None` and payload supports both `encode` AND `decode`, then an invalid payload could be passed, but this is unlikely
# If `charset=None` and payload supports both `encode` AND `decode`,
# then an invalid payload could be passed, but this is unlikely
# Not[_SupportsEncodeToPayload]
@overload
def set_payload(
@@ -146,7 +147,11 @@ class Message(Generic[_HeaderT, _HeaderParamT]):
class MIMEPart(Message[_HeaderRegistryT, _HeaderRegistryParamT]):
def __init__(self, policy: Policy | None = None) -> None: ...
def get_body(self, preferencelist: Sequence[str] = ("related", "html", "plain")) -> MIMEPart[_HeaderRegistryT] | None: ...
def iter_attachments(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
def attach(self, payload: Self) -> None: ... # type: ignore[override]
# The attachments are created via type(self) in the attach method. It's theoretically
# possible to sneak other attachment types into a MIMEPart instance, but could cause
# cause unforseen consequences.
def iter_attachments(self) -> Iterator[Self]: ...
def iter_parts(self) -> Iterator[MIMEPart[_HeaderRegistryT]]: ...
def get_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> Any: ...
def set_content(self, *args: Any, content_manager: ContentManager | None = None, **kw: Any) -> None: ...

View File

@@ -86,7 +86,7 @@ class FTP:
def makeport(self) -> socket: ...
def makepasv(self) -> tuple[str, int]: ...
def login(self, user: str = "", passwd: str = "", acct: str = "") -> str: ...
# In practice, `rest` rest can actually be anything whose str() is an integer sequence, so to make it simple we allow integers.
# In practice, `rest` can actually be anything whose str() is an integer sequence, so to make it simple we allow integers
def ntransfercmd(self, cmd: str, rest: int | str | None = None) -> tuple[socket, int | None]: ...
def transfercmd(self, cmd: str, rest: int | str | None = None) -> socket: ...
def retrbinary(

View File

@@ -1,12 +1,12 @@
from collections.abc import Callable, Iterator
from re import Pattern
from typing import Any
from typing import Any, Final
ESCAPE: Pattern[str]
ESCAPE_ASCII: Pattern[str]
HAS_UTF8: Pattern[bytes]
ESCAPE_DCT: dict[str, str]
INFINITY: float
ESCAPE: Final[Pattern[str]]
ESCAPE_ASCII: Final[Pattern[str]]
HAS_UTF8: Final[Pattern[bytes]]
ESCAPE_DCT: Final[dict[str, str]]
INFINITY: Final[float]
def py_encode_basestring(s: str) -> str: ... # undocumented
def py_encode_basestring_ascii(s: str) -> str: ... # undocumented

View File

@@ -61,6 +61,9 @@ __all__ = [
if sys.version_info >= (3, 11):
__all__ += ["call"]
if sys.version_info >= (3, 14):
__all__ += ["is_none", "is_not_none"]
__lt__ = lt
__le__ = le
__eq__ = eq

View File

@@ -365,7 +365,9 @@ class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, flo
if sys.version_info >= (3, 12) and sys.platform == "win32":
@property
@deprecated(
"Use st_birthtime instead to retrieve the file creation time. In the future, this property will contain the last metadata change time."
"""\
Use st_birthtime instead to retrieve the file creation time. \
In the future, this property will contain the last metadata change time."""
)
def st_ctime(self) -> float: ...
else:

View File

@@ -67,5 +67,6 @@ class POP3_SSL(POP3):
timeout: float = ...,
context: ssl.SSLContext | None = None,
) -> None: ...
# "context" is actually the last argument, but that breaks LSP and it doesn't really matter because all the arguments are ignored
# "context" is actually the last argument,
# but that breaks LSP and it doesn't really matter because all the arguments are ignored
def stls(self, context: Any = None, keyfile: Any = None, certfile: Any = None) -> NoReturn: ...

View File

@@ -15,9 +15,9 @@ class ExpatError(Exception):
offset: int
error = ExpatError
XML_PARAM_ENTITY_PARSING_NEVER: int
XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: int
XML_PARAM_ENTITY_PARSING_ALWAYS: int
XML_PARAM_ENTITY_PARSING_NEVER: Final = 0
XML_PARAM_ENTITY_PARSING_UNLESS_STANDALONE: Final = 1
XML_PARAM_ENTITY_PARSING_ALWAYS: Final = 2
_Model: TypeAlias = tuple[int, int, str | None, tuple[Any, ...]]

View File

@@ -1,49 +1,51 @@
import sys
from typing import Final
from typing_extensions import LiteralString
codes: dict[str, int]
messages: dict[int, str]
XML_ERROR_ABORTED: str
XML_ERROR_ASYNC_ENTITY: str
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF: str
XML_ERROR_BAD_CHAR_REF: str
XML_ERROR_BINARY_ENTITY_REF: str
XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING: str
XML_ERROR_DUPLICATE_ATTRIBUTE: str
XML_ERROR_ENTITY_DECLARED_IN_PE: str
XML_ERROR_EXTERNAL_ENTITY_HANDLING: str
XML_ERROR_FEATURE_REQUIRES_XML_DTD: str
XML_ERROR_FINISHED: str
XML_ERROR_INCOMPLETE_PE: str
XML_ERROR_INCORRECT_ENCODING: str
XML_ERROR_INVALID_TOKEN: str
XML_ERROR_JUNK_AFTER_DOC_ELEMENT: str
XML_ERROR_MISPLACED_XML_PI: str
XML_ERROR_NOT_STANDALONE: str
XML_ERROR_NOT_SUSPENDED: str
XML_ERROR_NO_ELEMENTS: str
XML_ERROR_NO_MEMORY: str
XML_ERROR_PARAM_ENTITY_REF: str
XML_ERROR_PARTIAL_CHAR: str
XML_ERROR_PUBLICID: str
XML_ERROR_RECURSIVE_ENTITY_REF: str
XML_ERROR_SUSPENDED: str
XML_ERROR_SUSPEND_PE: str
XML_ERROR_SYNTAX: str
XML_ERROR_TAG_MISMATCH: str
XML_ERROR_TEXT_DECL: str
XML_ERROR_UNBOUND_PREFIX: str
XML_ERROR_UNCLOSED_CDATA_SECTION: str
XML_ERROR_UNCLOSED_TOKEN: str
XML_ERROR_UNDECLARING_PREFIX: str
XML_ERROR_UNDEFINED_ENTITY: str
XML_ERROR_UNEXPECTED_STATE: str
XML_ERROR_UNKNOWN_ENCODING: str
XML_ERROR_XML_DECL: str
XML_ERROR_ABORTED: Final[LiteralString]
XML_ERROR_ASYNC_ENTITY: Final[LiteralString]
XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF: Final[LiteralString]
XML_ERROR_BAD_CHAR_REF: Final[LiteralString]
XML_ERROR_BINARY_ENTITY_REF: Final[LiteralString]
XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING: Final[LiteralString]
XML_ERROR_DUPLICATE_ATTRIBUTE: Final[LiteralString]
XML_ERROR_ENTITY_DECLARED_IN_PE: Final[LiteralString]
XML_ERROR_EXTERNAL_ENTITY_HANDLING: Final[LiteralString]
XML_ERROR_FEATURE_REQUIRES_XML_DTD: Final[LiteralString]
XML_ERROR_FINISHED: Final[LiteralString]
XML_ERROR_INCOMPLETE_PE: Final[LiteralString]
XML_ERROR_INCORRECT_ENCODING: Final[LiteralString]
XML_ERROR_INVALID_TOKEN: Final[LiteralString]
XML_ERROR_JUNK_AFTER_DOC_ELEMENT: Final[LiteralString]
XML_ERROR_MISPLACED_XML_PI: Final[LiteralString]
XML_ERROR_NOT_STANDALONE: Final[LiteralString]
XML_ERROR_NOT_SUSPENDED: Final[LiteralString]
XML_ERROR_NO_ELEMENTS: Final[LiteralString]
XML_ERROR_NO_MEMORY: Final[LiteralString]
XML_ERROR_PARAM_ENTITY_REF: Final[LiteralString]
XML_ERROR_PARTIAL_CHAR: Final[LiteralString]
XML_ERROR_PUBLICID: Final[LiteralString]
XML_ERROR_RECURSIVE_ENTITY_REF: Final[LiteralString]
XML_ERROR_SUSPENDED: Final[LiteralString]
XML_ERROR_SUSPEND_PE: Final[LiteralString]
XML_ERROR_SYNTAX: Final[LiteralString]
XML_ERROR_TAG_MISMATCH: Final[LiteralString]
XML_ERROR_TEXT_DECL: Final[LiteralString]
XML_ERROR_UNBOUND_PREFIX: Final[LiteralString]
XML_ERROR_UNCLOSED_CDATA_SECTION: Final[LiteralString]
XML_ERROR_UNCLOSED_TOKEN: Final[LiteralString]
XML_ERROR_UNDECLARING_PREFIX: Final[LiteralString]
XML_ERROR_UNDEFINED_ENTITY: Final[LiteralString]
XML_ERROR_UNEXPECTED_STATE: Final[LiteralString]
XML_ERROR_UNKNOWN_ENCODING: Final[LiteralString]
XML_ERROR_XML_DECL: Final[LiteralString]
if sys.version_info >= (3, 11):
XML_ERROR_RESERVED_PREFIX_XML: str
XML_ERROR_RESERVED_PREFIX_XMLNS: str
XML_ERROR_RESERVED_NAMESPACE_URI: str
XML_ERROR_INVALID_ARGUMENT: str
XML_ERROR_NO_BUFFER: str
XML_ERROR_AMPLIFICATION_LIMIT_BREACH: str
XML_ERROR_RESERVED_PREFIX_XML: Final[LiteralString]
XML_ERROR_RESERVED_PREFIX_XMLNS: Final[LiteralString]
XML_ERROR_RESERVED_NAMESPACE_URI: Final[LiteralString]
XML_ERROR_INVALID_ARGUMENT: Final[LiteralString]
XML_ERROR_NO_BUFFER: Final[LiteralString]
XML_ERROR_AMPLIFICATION_LIMIT_BREACH: Final[LiteralString]

View File

@@ -1,11 +1,13 @@
XML_CTYPE_ANY: int
XML_CTYPE_CHOICE: int
XML_CTYPE_EMPTY: int
XML_CTYPE_MIXED: int
XML_CTYPE_NAME: int
XML_CTYPE_SEQ: int
from typing import Final
XML_CQUANT_NONE: int
XML_CQUANT_OPT: int
XML_CQUANT_PLUS: int
XML_CQUANT_REP: int
XML_CTYPE_ANY: Final = 2
XML_CTYPE_EMPTY: Final = 1
XML_CTYPE_MIXED: Final = 3
XML_CTYPE_NAME: Final = 4
XML_CTYPE_CHOICE: Final = 5
XML_CTYPE_SEQ: Final = 6
XML_CQUANT_NONE: Final = 0
XML_CQUANT_OPT: Final = 1
XML_CQUANT_REP: Final = 2
XML_CQUANT_PLUS: Final = 3

View File

@@ -3,8 +3,9 @@ import types
from _socket import _Address, _RetAddress
from _typeshed import ReadableBuffer
from collections.abc import Callable
from io import BufferedIOBase
from socket import socket as _socket
from typing import Any, BinaryIO, ClassVar
from typing import Any, ClassVar
from typing_extensions import Self, TypeAlias
__all__ = [
@@ -158,11 +159,11 @@ class StreamRequestHandler(BaseRequestHandler):
timeout: ClassVar[float | None] # undocumented
disable_nagle_algorithm: ClassVar[bool] # undocumented
connection: Any # undocumented
rfile: BinaryIO
wfile: BinaryIO
rfile: BufferedIOBase
wfile: BufferedIOBase
class DatagramRequestHandler(BaseRequestHandler):
packet: _socket # undocumented
packet: bytes # undocumented
socket: _socket # undocumented
rfile: BinaryIO
wfile: BinaryIO
rfile: BufferedIOBase
wfile: BufferedIOBase

View File

@@ -1,12 +1,12 @@
from typing import Final
# These are not actually bools. See #4669
NO: bool
YES: bool
TRUE: bool
FALSE: bool
ON: bool
OFF: bool
NO: Final[bool]
YES: Final[bool]
TRUE: Final[bool]
FALSE: Final[bool]
ON: Final[bool]
OFF: Final[bool]
N: Final = "n"
S: Final = "s"
W: Final = "w"

View File

@@ -2,12 +2,12 @@ import sys
from _typeshed import Incomplete
from collections.abc import Mapping
from tkinter import Widget
from typing import Any
from typing import Any, Final
if sys.version_info >= (3, 9):
__all__ = ["Dialog"]
DIALOG_ICON: str
DIALOG_ICON: Final = "questhead"
class Dialog(Widget):
widgetName: str

View File

@@ -1,6 +1,6 @@
import sys
from tkinter.commondialog import Dialog
from typing import ClassVar
from typing import ClassVar, Final
if sys.version_info >= (3, 9):
__all__ = [
@@ -14,22 +14,22 @@ if sys.version_info >= (3, 9):
"askretrycancel",
]
ERROR: str
INFO: str
QUESTION: str
WARNING: str
ABORTRETRYIGNORE: str
OK: str
OKCANCEL: str
RETRYCANCEL: str
YESNO: str
YESNOCANCEL: str
ABORT: str
RETRY: str
IGNORE: str
CANCEL: str
YES: str
NO: str
ERROR: Final = "error"
INFO: Final = "info"
QUESTION: Final = "question"
WARNING: Final = "warning"
ABORTRETRYIGNORE: Final = "abortretryignore"
OK: Final = "ok"
OKCANCEL: Final = "okcancel"
RETRYCANCEL: Final = "retrycancel"
YESNO: Final = "yesno"
YESNOCANCEL: Final = "yesnocancel"
ABORT: Final = "abort"
RETRY: Final = "retry"
IGNORE: Final = "ignore"
CANCEL: Final = "cancel"
YES: Final = "yes"
NO: Final = "no"
class Message(Dialog):
command: ClassVar[str]

View File

@@ -556,7 +556,9 @@ class Notebook(Widget):
sticky: str = ..., # consists of letters 'n', 's', 'w', 'e', no repeats, may be empty
padding: _Padding = ...,
text: str = ...,
image=..., # Sequence of an image name, followed by zero or more (sequences of one or more state names followed by an image name)
# `image` is a sequence of an image name, followed by zero or more
# (sequences of one or more state names followed by an image name)
image=...,
compound: tkinter._Compound = ...,
underline: int = ...,
) -> None: ...

View File

@@ -2,7 +2,7 @@
# ruff: noqa: F811
# TODO: The collections import is required, otherwise mypy crashes.
# https://github.com/python/mypy/issues/16744
import collections # noqa: F401 # pyright: ignore
import collections # noqa: F401 # pyright: ignore[reportUnusedImport]
import sys
import typing_extensions
from _collections_abc import dict_items, dict_keys, dict_values
@@ -800,18 +800,12 @@ class IO(Iterator[AnyStr]):
def writable(self) -> bool: ...
@abstractmethod
@overload
def write(self: IO[str], s: str, /) -> int: ...
@abstractmethod
@overload
def write(self: IO[bytes], s: ReadableBuffer, /) -> int: ...
@abstractmethod
@overload
def write(self, s: AnyStr, /) -> int: ...
@abstractmethod
@overload
def writelines(self: IO[str], lines: Iterable[str], /) -> None: ...
@abstractmethod
@overload
def writelines(self: IO[bytes], lines: Iterable[ReadableBuffer], /) -> None: ...
@abstractmethod
@overload

View File

@@ -2,36 +2,52 @@ import sys
import unittest.case
import unittest.result
import unittest.suite
from _typeshed import Incomplete
from _typeshed import SupportsFlush, SupportsWrite
from collections.abc import Callable, Iterable
from typing import TextIO
from typing_extensions import TypeAlias
from typing import Any, Generic, Protocol, TypeVar
from typing_extensions import Never, TypeAlias
_ResultClassType: TypeAlias = Callable[[TextIO, bool, int], unittest.result.TestResult]
_ResultClassType: TypeAlias = Callable[[_TextTestStream, bool, int], TextTestResult]
class TextTestResult(unittest.result.TestResult):
class _SupportsWriteAndFlush(SupportsWrite[str], SupportsFlush, Protocol): ...
# All methods used by unittest.runner.TextTestResult's stream
class _TextTestStream(_SupportsWriteAndFlush, Protocol):
def writeln(self, arg: str | None = None) -> str: ...
# _WritelnDecorator should have all the same attrs as its stream param.
# But that's not feasible to do Generically
# We can expand the attributes if requested
class _WritelnDecorator(_TextTestStream):
def __init__(self, stream: _TextTestStream) -> None: ...
def __getattr__(self, attr: str) -> Any: ... # Any attribute from the stream type passed to __init__
# These attributes are prevented by __getattr__
stream: Never
__getstate__: Never
_StreamT = TypeVar("_StreamT", bound=_TextTestStream, default=_WritelnDecorator)
class TextTestResult(unittest.result.TestResult, Generic[_StreamT]):
descriptions: bool # undocumented
dots: bool # undocumented
separator1: str
separator2: str
showAll: bool # undocumented
stream: TextIO # undocumented
stream: _StreamT # undocumented
if sys.version_info >= (3, 12):
durations: unittest.result._DurationsType | None
def __init__(
self, stream: TextIO, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
self, stream: _StreamT, descriptions: bool, verbosity: int, *, durations: unittest.result._DurationsType | None = None
) -> None: ...
else:
def __init__(self, stream: TextIO, descriptions: bool, verbosity: int) -> None: ...
def __init__(self, stream: _StreamT, descriptions: bool, verbosity: int) -> None: ...
def getDescription(self, test: unittest.case.TestCase) -> str: ...
def printErrorList(self, flavour: str, errors: Iterable[tuple[unittest.case.TestCase, str]]) -> None: ...
class TextTestRunner:
resultclass: _ResultClassType
# TODO: add `_WritelnDecorator` type
# stream: _WritelnDecorator
stream: Incomplete
stream: _WritelnDecorator
descriptions: bool
verbosity: int
failfast: bool
@@ -43,7 +59,7 @@ class TextTestRunner:
durations: unittest.result._DurationsType | None
def __init__(
self,
stream: TextIO | None = None,
stream: _SupportsWriteAndFlush | None = None,
descriptions: bool = True,
verbosity: int = 1,
failfast: bool = False,
@@ -57,7 +73,7 @@ class TextTestRunner:
else:
def __init__(
self,
stream: TextIO | None = None,
stream: _SupportsWriteAndFlush | None = None,
descriptions: bool = True,
verbosity: int = 1,
failfast: bool = False,
@@ -68,5 +84,5 @@ class TextTestRunner:
tb_locals: bool = False,
) -> None: ...
def _makeResult(self) -> unittest.result.TestResult: ...
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> unittest.result.TestResult: ...
def _makeResult(self) -> TextTestResult: ...
def run(self, test: unittest.suite.TestSuite | unittest.case.TestCase) -> TextTestResult: ...

View File

@@ -304,6 +304,7 @@ else:
class Path:
root: CompleteDirs
at: str
def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ...
@property
def name(self) -> str: ...

View File

@@ -31,6 +31,7 @@ if sys.version_info >= (3, 12):
class Path:
root: CompleteDirs
at: str
def __init__(self, root: ZipFile | StrPath | IO[bytes], at: str = "") -> None: ...
@property
def name(self) -> str: ...

View File

@@ -29,6 +29,7 @@ tracing = { workspace = true }
[dev-dependencies]
ruff_db = { workspace = true, features = ["testing"] }
tempfile = { workspace = true }
[lints]
workspace = true

View File

@@ -164,7 +164,7 @@ fn lint_bad_override(context: &SemanticLintContext, class: &ast::StmtClassDef) {
if ty.has_decorator(db, override_ty) {
let method_name = ty.name(db);
if class_ty
.inherited_class_member(db, &method_name)
.inherited_class_member(db, method_name)
.is_unbound()
{
// TODO should have a qualname() method to support nested classes

View File

@@ -297,7 +297,6 @@ impl Workspace {
}
}
#[salsa::tracked]
impl Package {
pub fn root(self, db: &dyn Db) -> &SystemPath {
self.root_buf(db)

View File

@@ -21,19 +21,27 @@ fn setup_db(workspace_root: &SystemPath) -> anyhow::Result<RootDatabase> {
#[test]
#[allow(clippy::print_stdout)]
fn corpus_no_panic() -> anyhow::Result<()> {
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus");
let system_corpus =
SystemPathBuf::from_path_buf(corpus.clone()).expect("corpus path to be UTF8");
let db = setup_db(&system_corpus)?;
let root = SystemPathBuf::from_path_buf(tempfile::TempDir::new()?.into_path()).unwrap();
let db = setup_db(&root)?;
for path in fs::read_dir(&corpus).expect("corpus to be a directory") {
let path = path.expect("path to not be an error").path();
println!("checking {path:?}");
let path = SystemPathBuf::from_path_buf(path.clone()).expect("path to be UTF-8");
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/corpus");
for path in fs::read_dir(&corpus)? {
let source = path?.path();
println!("checking {source:?}");
let source_fn = source.file_name().unwrap().to_str().unwrap();
let py_dest = root.join(source_fn);
fs::copy(&source, py_dest.as_std_path())?;
// this test is only asserting that we can pull every expression type without a panic
// (and some non-expressions that clearly define a single type)
let file = system_path_to_file(&db, path).expect("file to exist");
let file = system_path_to_file(&db, py_dest).unwrap();
pull_types(&db, file);
// try the file as a stub also
println!("re-checking as .pyi");
let pyi_dest = root.join(format!("{source_fn}i"));
std::fs::copy(source, pyi_dest.as_std_path())?;
let file = system_path_to_file(&db, pyi_dest).unwrap();
pull_types(&db, file);
}
Ok(())

View File

@@ -810,7 +810,11 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
.map(|rule| format!("`{}`", rule.noqa_code()))
.collect();
rule_names.sort();
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
if let [rule] = rule_names.as_slice() {
warn_user_once!("The following rule may cause conflicts when used with the formatter: {rule}. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.");
} else {
warn_user_once!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.", rule_names.join(", "));
}
}
// Next, validate settings-specific incompatibilities.

View File

@@ -151,15 +151,15 @@ impl Printer {
let fix_prefix = format!("[{}]", "*".cyan());
if self.unsafe_fixes.is_hint() {
if fixables.applicable > 0 && fixables.unapplicable_unsafe > 0 {
let es = if fixables.unapplicable_unsafe == 1 {
if fixables.applicable > 0 && fixables.inapplicable_unsafe > 0 {
let es = if fixables.inapplicable_unsafe == 1 {
""
} else {
"es"
};
writeln!(writer,
"{fix_prefix} {} fixable with the `--fix` option ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
fixables.applicable, fixables.unapplicable_unsafe
fixables.applicable, fixables.inapplicable_unsafe
)?;
} else if fixables.applicable > 0 {
// Only applicable fixes
@@ -169,15 +169,15 @@ impl Printer {
fixables.applicable,
)?;
} else {
// Only unapplicable fixes
let es = if fixables.unapplicable_unsafe == 1 {
// Only inapplicable fixes
let es = if fixables.inapplicable_unsafe == 1 {
""
} else {
"es"
};
writeln!(writer,
"No fixes available ({} hidden fix{es} can be enabled with the `--unsafe-fixes` option).",
fixables.unapplicable_unsafe
fixables.inapplicable_unsafe
)?;
}
} else {
@@ -194,7 +194,7 @@ impl Printer {
// Check if there are unapplied fixes
let unapplied = {
if let Some(fixables) = fixables {
fixables.unapplicable_unsafe
fixables.inapplicable_unsafe
} else {
0
}
@@ -545,33 +545,33 @@ fn print_fix_summary(writer: &mut dyn Write, fixed: &FixMap) -> Result<()> {
#[derive(Debug)]
struct FixableStatistics {
applicable: u32,
unapplicable_unsafe: u32,
inapplicable_unsafe: u32,
}
impl FixableStatistics {
fn try_from(diagnostics: &Diagnostics, unsafe_fixes: UnsafeFixes) -> Option<Self> {
let mut applicable = 0;
let mut unapplicable_unsafe = 0;
let mut inapplicable_unsafe = 0;
for message in &diagnostics.messages {
if let Some(fix) = message.fix() {
if fix.applies(unsafe_fixes.required_applicability()) {
applicable += 1;
} else {
// Do not include unapplicable fixes at other levels that do not provide an opt-in
// Do not include inapplicable fixes at other levels that do not provide an opt-in
if fix.applicability().is_unsafe() {
unapplicable_unsafe += 1;
inapplicable_unsafe += 1;
}
}
}
}
if applicable == 0 && unapplicable_unsafe == 0 {
if applicable == 0 && inapplicable_unsafe == 0 {
None
} else {
Some(Self {
applicable,
unapplicable_unsafe,
inapplicable_unsafe,
})
}
}

View File

@@ -785,7 +785,7 @@ if condition:
print('Should change quotes')
----- stderr -----
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
warning: The following rule may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling this rule, either by removing it from the `select` or `extend-select` configuration, or adding it to the `ignore` configuration.
"###);
Ok(())
}

View File

@@ -21,7 +21,7 @@ struct Case {
const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/8e8a4baf652f6e1cee7acde9d78c4b6154539748/Lib/tomllib";
// The "unresolved import" is because we don't understand `*` imports yet.
// The failed import from 'collections.abc' is due to lack of support for 'import *'.
static EXPECTED_DIAGNOSTICS: &[&str] = &[
"/src/tomllib/_parser.py:7:29: Module 'collections.abc' has no member 'Iterable'",
"Line 69 is too long (89 characters)",
@@ -32,10 +32,7 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
"Use double quotes for strings",
"Use double quotes for strings",
"Use double quotes for strings",
"/src/tomllib/_parser.py:330:32: Name 'header' used when not defined.",
"/src/tomllib/_parser.py:330:41: Name 'key' used when not defined.",
"/src/tomllib/_parser.py:628:75: Name 'e' used when not defined.",
"/src/tomllib/_parser.py:686:23: Name 'parse_float' used when not defined.",
];
fn get_test_file(name: &str) -> TestFile {

View File

@@ -3,3 +3,9 @@ import float
from some import other as int
from some import input, exec
from directory import new as dir
# See: https://github.com/astral-sh/ruff/issues/13037
import sys
if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup, ExceptionGroup

View File

@@ -44,3 +44,11 @@ class PEP695BadDunderNew[T]:
class PEP695GoodDunderNew[T]:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
class CustomClassMethod:
# Should be recognised as a classmethod decorator
# due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators`
# in the settings for this test:
@foo_classmethod
def foo[S](cls: type[S]) -> S: ... # PYI019

View File

@@ -44,3 +44,11 @@ class PEP695BadDunderNew[T]:
class PEP695GoodDunderNew[T]:
def __new__(cls, *args: Any, **kwargs: Any) -> Self: ...
class CustomClassMethod:
# Should be recognised as a classmethod decorator
# due to `foo_classmethod being listed in `pep8_naming.classmethod-decorators`
# in the settings for this test:
@foo_classmethod
def foo[S](cls: type[S]) -> S: ... # PYI019

View File

@@ -3,8 +3,8 @@ import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Y032
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
def __eq__(self, other: Any) -> bool: ... # PYI032
def __ne__(self, other: typing.Any) -> typing.Any: ... # PYI032
class Good:
@@ -22,3 +22,7 @@ class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...
class BadStringized:
def __eq__(self, other: "Any") -> bool: ... # PYI032
def __ne__(self, other: "Any") -> bool: ... # PYI032

View File

@@ -3,8 +3,8 @@ import typing
class Bad:
def __eq__(self, other: Any) -> bool: ... # Y032
def __ne__(self, other: typing.Any) -> typing.Any: ... # Y032
def __eq__(self, other: Any) -> bool: ... # PYI032
def __ne__(self, other: typing.Any) -> typing.Any: ... # PYI032
class Good:
@@ -22,3 +22,6 @@ class Unannotated:
def __eq__(self) -> Any: ...
def __ne__(self) -> bool: ...
class BadStringized:
def __eq__(self, other: "Any") -> bool: ... # PYI032
def __ne__(self, other: "Any") -> bool: ... # PYI032

View File

@@ -317,3 +317,7 @@ def __ne__(self, other: Any) -> bool:
def __imul__(self, other: Any) -> list[str]:
...
class UsesStringizedAnnotations:
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self":
return self

View File

@@ -212,3 +212,6 @@ def __str__(self) -> str: ...
def __eq__(self, other: Any) -> bool: ...
def __ne__(self, other: Any) -> bool: ...
def __imul__(self, other: Any) -> list[str]: ...
class UsesStringizedAnnotations:
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self": ...

View File

@@ -30,3 +30,11 @@ def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
def foo_never(arg: Never):
...
def stringized(arg: "NoReturn"):
...
def stringized_good(arg: "Never"):
...

View File

@@ -21,3 +21,5 @@ def foo_int_kwargs_no_return(*args: NoReturn, **kwargs: int): ... # Error: PYI0
def foo_args_never(*args: Never): ...
def foo_kwargs_never(**kwargs: Never): ...
def foo_args_kwargs_never(*args: Never, **kwargs: Never): ...
def stringized(arg: "NoReturn"): ...
def stringized_good(arg: "Never"): ...

View File

@@ -187,3 +187,50 @@ def foo(s: str) -> str | None:
A string.
"""
return None
# DOC201
def bar() -> int | None:
"""Bar-y method"""
return
from collections.abc import Iterator, Generator
# This is okay -- returning `None` is implied by `Iterator[str]`;
# no need to document it
def generator_function() -> Iterator[str]:
"""Generate some strings"""
yield from "abc"
return
# This is okay -- returning `None` is stated by `Generator[str, None, None]`;
# no need to document it
def generator_function_2() -> Generator[str, None, None]:
"""Generate some strings"""
yield from "abc"
return
# DOC201 -- returns None but `Generator[str, None, int | None]`
# indicates it could sometimes return `int`
def generator_function_3() -> Generator[str, None, int | None]:
"""Generate some strings"""
yield from "abc"
return
# DOC201 -- no type annotation and a non-None return
# indicates it could sometimes return `int`
def generator_function_4():
"""Generate some strings"""
yield from "abc"
return 42
# DOC201 -- no `yield` expressions, so not a generator function
def not_a_generator() -> Iterator[int]:
""""No returns documented here, oh no"""
return (x for x in range(42))

View File

@@ -73,3 +73,15 @@ class A(metaclass=abc.abcmeta):
dict: The values
"""
return
# DOC202 -- never explicitly returns anything, just short-circuits
def foo(s: str, condition: bool):
"""Fooey things.
Returns:
None
"""
if not condition:
return
print(s)

View File

@@ -87,3 +87,22 @@ def f(num: int):
num (int): A number
"""
yield 1
import collections.abc
# DOC402
def foo() -> collections.abc.Generator[int | None, None, None]:
"""
Do something
"""
yield
# DOC402
def bar() -> collections.abc.Iterator[int | None]:
"""
Do something
"""
yield

View File

@@ -60,3 +60,81 @@ class Bar:
A number
"""
yield 'test'
import typing
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
"""
yield None
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
"""
yield
# DOC402
def foo() -> typing.Generator[int | None, None, None]:
"""
Do something
"""
yield None
yield 1
# DOC402
def foo() -> typing.Generator[int, None, None]:
"""
Do something
"""
yield None
# OK
def foo():
"""
Do something
"""
yield None
# OK
def foo():
"""
Do something
"""
yield
# DOC402
def foo():
"""
Do something
"""
yield None
yield 1
# DOC402
def foo():
"""
Do something
"""
yield 1
yield
# DOC402
def bar() -> typing.Iterator[int | None]:
"""
Do something
"""
yield

View File

@@ -48,3 +48,50 @@ class Bar:
num (int): A number
"""
print('test')
import typing
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
Yields:
When X.
"""
yield
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
Yields:
When X.
"""
yield None
# OK
def foo():
"""
Do something
Yields:
When X.
"""
yield
# OK
def foo():
"""
Do something
Yields:
When X.
"""
yield None

View File

@@ -60,3 +60,54 @@ class Bar:
A number
"""
print('test')
import typing
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
Yields
------
When X.
"""
yield None
# OK
def foo() -> typing.Generator[None, None, None]:
"""
Do something
Yields
------
When X.
"""
yield
# OK
def foo():
"""
Do something
Yields
------
When X.
"""
yield None
# OK
def foo():
"""
Do something
Yields
------
When X.
"""
yield

View File

@@ -605,3 +605,17 @@ def test_lowercase_sub_section_header_different_kind(returns: int):
some value
"""
# We used to incorrectly infer this as a numpy-style docstring,
# which caused us to emit D406 and D407 on it;
# see https://github.com/astral-sh/ruff/issues/13139
def another_valid_google_style_docstring(a: str) -> str:
"""Foo bar.
Examples:
Some explanation here.
>>> bla bla bla
"""
return a

View File

@@ -83,3 +83,23 @@ class B(A):
def baz(self):
if super().foo():
...
# See: https://github.com/astral-sh/ruff/issues/12568
from attrs import define, field
@define
class Foo:
x: int = field()
y: int
@x.validator
def validate_x(self, attribute, value):
if value <= 0:
raise ValueError("x must be a positive integer")
@y.validator
def validate_y(self, attribute, value):
if value <= 0:
raise ValueError("y must be a positive integer")

View File

@@ -38,3 +38,16 @@ def value_intentionally_unused():
print(FRUITS[fruit_name]) # OK
blah = FRUITS[fruit_name] # OK
assert FRUITS[fruit_name] == "pear" # OK
def rewrite_client_arrays(value_arrays: dict[str, list[int]]) -> dict[str, list[int]]:
"""Function from https://github.com/zulip/zulip/blob/3da91e951cd03cfa0b9c67378224e348353f36a6/analytics/views/stats.py#L617C1-L626C25"""
mapped_arrays: dict[str, list[int]] = {}
for label, array in value_arrays.items():
mapped_label = client_label_map(label)
if mapped_label in mapped_arrays:
for i in range(len(array)):
mapped_arrays[mapped_label][i] += value_arrays[label][i] # PLR1733
else:
mapped_arrays[mapped_label] = [value_arrays[label][i] for i in range(len(array))] # PLR1733
return mapped_arrays

View File

@@ -76,3 +76,10 @@ def start():
# PLR1736
for index, list_item in enumerate(some_list):
print(some_list[index])
def nested_index_lookup():
data = {"a": 1, "b": 2}
column_names = ["a", "b"]
for index, column_name in enumerate(column_names):
_ = data[column_names[index]] # PLR1736

View File

@@ -119,3 +119,10 @@ def func():
def func():
with suppress(AttributeError):
raise AttributeError("This is an exception") # OK
import builtins
builtins.TypeError("still an exception even though it's an Attribute")
PythonFinalizationError("Added in Python 3.13")

View File

@@ -78,3 +78,10 @@ open("foo", "r")
open("foo", "rt")
open("f", "r", encoding="UTF-8")
open("f", "wt")
import aiofiles
aiofiles.open("foo", "U")
aiofiles.open("foo", "r")
aiofiles.open("foo", mode="r")

View File

@@ -225,3 +225,33 @@ if sys.version_info > (3,0):
"this is\
allowed too"
if sys.version_info[0] == 3:
print("py3")
if sys.version_info[0] <= 3:
print("py3")
if sys.version_info[0] < 3:
print("py3")
if sys.version_info[0] >= 3:
print("py3")
if sys.version_info[0] > 3:
print("py3")
if sys.version_info[0] == 2:
print("py3")
if sys.version_info[0] <= 2:
print("py3")
if sys.version_info[0] < 2:
print("py3")
if sys.version_info[0] >= 2:
print("py3")
if sys.version_info[0] > 2:
print("py3")

View File

@@ -18,6 +18,7 @@ print("", *args)
print("", *args, sep="")
print("", **kwargs)
print(sep="\t")
print(sep=print(1))
# OK.

View File

@@ -0,0 +1,67 @@
from dataclasses import InitVar, dataclass
# OK
@dataclass
class Foo:
bar: InitVar[int] = 0
baz: InitVar[int] = 1
def __post_init__(self, bar, baz) -> None: ...
# RUF033
@dataclass
class Foo:
bar: InitVar[int] = 0
baz: InitVar[int] = 1
def __post_init__(self, bar = 11, baz = 11) -> None: ...
# RUF033
@dataclass
class Foo:
def __post_init__(self, bar = 11, baz = 11) -> None: ...
# OK
@dataclass
class Foo:
def __something_else__(self, bar = 11, baz = 11) -> None: ...
# OK
def __post_init__(foo: bool = True) -> None: ...
# OK
class Foo:
def __post_init__(self, x="hmm") -> None: ...
# RUF033
@dataclass
class Foo:
def __post_init__(self, bar: int = 11, baz: Something[Whatever | None] = 11) -> None: ...
# RUF033
@dataclass
class Foo:
"""A very helpful docstring.
Docstrings are very important and totally not a waste of time.
"""
ping = "pong"
def __post_init__(self, bar: int = 11, baz: int = 12) -> None: ...
# RUF033
@dataclass
class Foo:
bar = "should've used attrs"
def __post_init__(self, bar: str = "ahhh", baz: str = "hmm") -> None: ...

View File

@@ -380,6 +380,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::WhitespaceAfterDecorator) {
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
}
if checker.enabled(Rule::PostInitDefault) {
ruff::rules::post_init_default(checker, function_def);
}
}
Stmt::Return(_) => {
if checker.enabled(Rule::ReturnOutsideFunction) {

View File

@@ -26,10 +26,12 @@
//! represents the lint-rule analysis phase. In the future, these steps may be separated into
//! distinct passes over the AST.
use std::cell::RefCell;
use std::path::Path;
use itertools::Itertools;
use log::debug;
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Diagnostic, IsolationLevel};
use ruff_notebook::{CellOffsets, NotebookIndex};
@@ -40,13 +42,13 @@ use ruff_python_ast::str::Quote;
use ruff_python_ast::visitor::{walk_except_handler, walk_pattern, Visitor};
use ruff_python_ast::{
self as ast, AnyParameterRef, Comprehension, ElifElseClause, ExceptHandler, Expr, ExprContext,
FStringElement, Keyword, MatchCase, ModExpression, ModModule, Parameter, Parameters, Pattern,
Stmt, Suite, UnaryOp,
FStringElement, Keyword, MatchCase, ModModule, Parameter, Parameters, Pattern, Stmt, Suite,
UnaryOp,
};
use ruff_python_ast::{helpers, str, visitor, PySourceType};
use ruff_python_codegen::{Generator, Stylist};
use ruff_python_index::Indexer;
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind};
use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind, ParsedAnnotation};
use ruff_python_parser::{Parsed, Tokens};
use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags};
use ruff_python_semantic::analyze::{imports, typing};
@@ -55,7 +57,7 @@ use ruff_python_semantic::{
ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel, SemanticModelFlags,
StarImport, SubmoduleImport,
};
use ruff_python_stdlib::builtins::{IPYTHON_BUILTINS, MAGIC_GLOBALS, PYTHON_BUILTINS};
use ruff_python_stdlib::builtins::{python_builtins, MAGIC_GLOBALS};
use ruff_python_trivia::CommentRanges;
use ruff_source_file::{Locator, OneIndexed, SourceRow};
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -177,8 +179,10 @@ impl ExpectedDocstringKind {
pub(crate) struct Checker<'a> {
/// The [`Parsed`] output for the source code.
parsed: &'a Parsed<ModModule>,
/// An internal cache for parsed string annotations
parsed_annotations_cache: ParsedAnnotationsCache<'a>,
/// The [`Parsed`] output for the type annotation the checker is currently in.
parsed_type_annotation: Option<&'a Parsed<ModExpression>>,
parsed_type_annotation: Option<&'a ParsedAnnotation>,
/// The [`Path`] to the file under analysis.
path: &'a Path,
/// The [`Path`] to the package containing the current file.
@@ -229,6 +233,7 @@ impl<'a> Checker<'a> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
parsed: &'a Parsed<ModModule>,
parsed_annotations_arena: &'a typed_arena::Arena<ParsedAnnotation>,
settings: &'a LinterSettings,
noqa_line_for: &'a NoqaMapping,
noqa: flags::Noqa,
@@ -245,6 +250,7 @@ impl<'a> Checker<'a> {
Checker {
parsed,
parsed_type_annotation: None,
parsed_annotations_cache: ParsedAnnotationsCache::new(parsed_annotations_arena),
settings,
noqa_line_for,
noqa,
@@ -333,8 +339,8 @@ impl<'a> Checker<'a> {
/// Returns the [`Tokens`] for the parsed type annotation if the checker is in a typing context
/// or the parsed source code.
pub(crate) fn tokens(&self) -> &'a Tokens {
if let Some(parsed_type_annotation) = self.parsed_type_annotation {
parsed_type_annotation.tokens()
if let Some(type_annotation) = self.parsed_type_annotation {
type_annotation.parsed().tokens()
} else {
self.parsed.tokens()
}
@@ -405,6 +411,39 @@ impl<'a> Checker<'a> {
.map(|node_id| IsolationLevel::Group(node_id.into()))
.unwrap_or_default()
}
/// Parse a stringified type annotation as an AST expression,
/// e.g. `"List[str]"` in `x: "List[str]"`
///
/// This method is a wrapper around [`ruff_python_parser::typing::parse_type_annotation`]
/// that adds caching.
pub(crate) fn parse_type_annotation(
&self,
annotation: &ast::ExprStringLiteral,
) -> Option<&'a ParsedAnnotation> {
self.parsed_annotations_cache
.lookup_or_parse(annotation, self.locator.contents())
}
/// Apply a test to an annotation expression,
/// abstracting over the fact that the annotation expression might be "stringized".
///
/// A stringized annotation is one enclosed in string quotes:
/// `foo: "typing.Any"` means the same thing to a type checker as `foo: typing.Any`.
pub(crate) fn match_maybe_stringized_annotation(
&self,
expr: &ast::Expr,
match_fn: impl FnOnce(&ast::Expr) -> bool,
) -> bool {
if let ast::Expr::StringLiteral(string_annotation) = expr {
let Some(parsed_annotation) = self.parse_type_annotation(string_annotation) else {
return false;
};
match_fn(parsed_annotation.expression())
} else {
match_fn(expr)
}
}
}
impl<'a> Visitor<'a> for Checker<'a> {
@@ -1912,16 +1951,13 @@ impl<'a> Checker<'a> {
}
fn bind_builtins(&mut self) {
for builtin in PYTHON_BUILTINS
let standard_builtins = python_builtins(
self.settings.target_version.minor(),
self.source_type.is_ipynb(),
);
for builtin in standard_builtins
.iter()
.chain(MAGIC_GLOBALS.iter())
.chain(
self.source_type
.is_ipynb()
.then_some(IPYTHON_BUILTINS)
.into_iter()
.flatten(),
)
.copied()
.chain(self.settings.builtins.iter().map(String::as_str))
{
@@ -2168,18 +2204,13 @@ impl<'a> Checker<'a> {
///
/// class Bar: pass
/// ```
fn visit_deferred_string_type_definitions(
&mut self,
allocator: &'a typed_arena::Arena<Parsed<ModExpression>>,
) {
fn visit_deferred_string_type_definitions(&mut self) {
let snapshot = self.semantic.snapshot();
while !self.visit.string_type_definitions.is_empty() {
let type_definitions = std::mem::take(&mut self.visit.string_type_definitions);
for (string_expr, snapshot) in type_definitions {
if let Ok((parsed_annotation, kind)) =
parse_type_annotation(string_expr, self.locator.contents())
{
let parsed_annotation = allocator.alloc(parsed_annotation);
let annotation_parse_result = self.parse_type_annotation(string_expr);
if let Some(parsed_annotation) = annotation_parse_result {
self.parsed_type_annotation = Some(parsed_annotation);
let annotation = string_expr.value.to_str();
@@ -2198,7 +2229,7 @@ impl<'a> Checker<'a> {
}
}
let type_definition_flag = match kind {
let type_definition_flag = match parsed_annotation.kind() {
AnnotationKind::Simple => SemanticModelFlags::SIMPLE_STRING_TYPE_DEFINITION,
AnnotationKind::Complex => {
SemanticModelFlags::COMPLEX_STRING_TYPE_DEFINITION
@@ -2207,7 +2238,7 @@ impl<'a> Checker<'a> {
self.semantic.flags |=
SemanticModelFlags::TYPE_DEFINITION | type_definition_flag;
self.visit_expr(parsed_annotation.expr());
self.visit_expr(parsed_annotation.expression());
self.parsed_type_annotation = None;
} else {
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
@@ -2220,6 +2251,35 @@ impl<'a> Checker<'a> {
}
}
}
// If we're parsing string annotations inside string annotations
// (which is the only reason we might enter a second iteration of this loop),
// the cache is no longer valid. We must invalidate it to avoid an infinite loop.
//
// For example, consider the following annotation:
// ```python
// x: "list['str']"
// ```
//
// The first time we visit the AST, we see `"list['str']"`
// and identify it as a stringified annotation.
// We store it in `self.visit.string_type_definitions` to be analyzed later.
//
// After the entire tree has been visited, we look through
// `self.visit.string_type_definitions` and find `"list['str']"`.
// We parse it, and it becomes `list['str']`.
// After parsing it, we call `self.visit_expr()` on the `list['str']` node,
// and that `visit_expr` call is going to find `'str'` inside that node and
// identify it as a string type definition, appending it to
// `self.visit.string_type_definitions`, ensuring that there will be one
// more iteration of this loop.
//
// Unfortunately, the `TextRange` of `'str'`
// here will be *relative to the parsed `list['str']` node* rather than
// *relative to the original module*, meaning the cache
// (which uses `TextSize` as the key) becomes invalid on the second
// iteration of this loop.
self.parsed_annotations_cache.clear();
}
self.semantic.restore(snapshot);
}
@@ -2283,14 +2343,14 @@ impl<'a> Checker<'a> {
/// After initial traversal of the source tree has been completed,
/// recursively visit all AST nodes that were deferred on the first pass.
/// This includes lambdas, functions, type parameters, and type annotations.
fn visit_deferred(&mut self, allocator: &'a typed_arena::Arena<Parsed<ModExpression>>) {
fn visit_deferred(&mut self) {
while !self.visit.is_empty() {
self.visit_deferred_class_bases();
self.visit_deferred_functions();
self.visit_deferred_type_param_definitions();
self.visit_deferred_lambdas();
self.visit_deferred_future_type_definitions();
self.visit_deferred_string_type_definitions(allocator);
self.visit_deferred_string_type_definitions();
}
}
@@ -2358,6 +2418,42 @@ impl<'a> Checker<'a> {
}
}
struct ParsedAnnotationsCache<'a> {
arena: &'a typed_arena::Arena<ParsedAnnotation>,
by_offset: RefCell<FxHashMap<TextSize, Option<&'a ParsedAnnotation>>>,
}
impl<'a> ParsedAnnotationsCache<'a> {
fn new(arena: &'a typed_arena::Arena<ParsedAnnotation>) -> Self {
Self {
arena,
by_offset: RefCell::default(),
}
}
fn lookup_or_parse(
&self,
annotation: &ast::ExprStringLiteral,
source: &str,
) -> Option<&'a ParsedAnnotation> {
*self
.by_offset
.borrow_mut()
.entry(annotation.start())
.or_insert_with(|| {
if let Ok(annotation) = parse_type_annotation(annotation, source) {
Some(self.arena.alloc(annotation))
} else {
None
}
})
}
fn clear(&self) {
self.by_offset.borrow_mut().clear();
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn check_ast(
parsed: &Parsed<ModModule>,
@@ -2393,8 +2489,10 @@ pub(crate) fn check_ast(
python_ast: parsed.suite(),
};
let allocator = typed_arena::Arena::new();
let mut checker = Checker::new(
parsed,
&allocator,
settings,
noqa_line_for,
noqa,
@@ -2417,8 +2515,7 @@ pub(crate) fn check_ast(
// Visit any deferred syntax nodes. Take care to visit in order, such that we avoid adding
// new deferred nodes after visiting nodes of that kind. For example, visiting a deferred
// function can add a deferred lambda, but the opposite is not true.
let allocator = typed_arena::Arena::new();
checker.visit_deferred(&allocator);
checker.visit_deferred();
checker.visit_exports();
// Check docstrings, bindings, and unresolved references.

View File

@@ -960,6 +960,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "030") => (RuleGroup::Preview, rules::ruff::rules::AssertWithPrintMessage),
(Ruff, "031") => (RuleGroup::Preview, rules::ruff::rules::IncorrectlyParenthesizedTupleInSubscript),
(Ruff, "032") => (RuleGroup::Preview, rules::ruff::rules::DecimalFromFloatLiteral),
(Ruff, "033") => (RuleGroup::Preview, rules::ruff::rules::PostInitDefault),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),

View File

@@ -2,7 +2,7 @@ use crate::docstrings::google::GOOGLE_SECTIONS;
use crate::docstrings::numpy::NUMPY_SECTIONS;
use crate::docstrings::sections::SectionKind;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, is_macro::Is)]
pub(crate) enum SectionStyle {
Numpy,
Google,

View File

@@ -1,7 +1,7 @@
use std::io::Write;
use std::path::Path;
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString};
use ruff_source_file::SourceLocation;
@@ -25,7 +25,7 @@ impl Emitter for JunitEmitter {
let mut test_suite = TestSuite::new("ruff");
test_suite
.extra
.insert("package".to_string(), "org.ruff".to_string());
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
let mut case = TestCase::new("No errors found", TestCaseStatus::success());
case.set_classname("ruff");
test_suite.add_test_case(case);
@@ -35,7 +35,7 @@ impl Emitter for JunitEmitter {
let mut test_suite = TestSuite::new(filename);
test_suite
.extra
.insert("package".to_string(), "org.ruff".to_string());
.insert(XmlString::new("package"), XmlString::new("org.ruff"));
for message in messages {
let MessageWithLocation {
@@ -70,10 +70,14 @@ impl Emitter for JunitEmitter {
let file_stem = file_path.file_stem().unwrap().to_str().unwrap();
let classname = file_path.parent().unwrap().join(file_stem);
case.set_classname(classname.to_str().unwrap());
case.extra
.insert("line".to_string(), location.row.to_string());
case.extra
.insert("column".to_string(), location.column.to_string());
case.extra.insert(
XmlString::new("line"),
XmlString::new(location.row.to_string()),
);
case.extra.insert(
XmlString::new("column"),
XmlString::new(location.column.to_string()),
);
test_suite.add_test_case(case);
}

View File

@@ -121,7 +121,7 @@ impl SarifResult {
Ok(Self {
rule: message.rule(),
level: "error".to_string(),
message: message.name().to_string(),
message: message.body().to_string(),
uri: url::Url::from_file_path(&path)
.map_err(|()| anyhow::anyhow!("Failed to convert path to URL: {}", path.display()))?
.to_string(),
@@ -141,7 +141,7 @@ impl SarifResult {
Ok(Self {
rule: message.rule(),
level: "error".to_string(),
message: message.name().to_string(),
message: message.body().to_string(),
uri: path.display().to_string(),
start_line: start_location.row,
start_column: start_location.column,
@@ -215,6 +215,10 @@ mod tests {
.unwrap();
let results = sarif["runs"][0]["results"].as_array().unwrap();
assert_eq!(results.len(), 3);
assert_eq!(
results[0]["message"]["text"].as_str().unwrap(),
"`os` imported but unused"
);
assert!(rules.len() > 3);
}
}

View File

@@ -150,7 +150,7 @@ impl Display for RuleCodeAndBody<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.show_fix_status {
if let Some(fix) = self.message.fix() {
// Do not display an indicator for unapplicable fixes
// Do not display an indicator for inapplicable fixes
if fix.applies(self.unsafe_fixes.required_applicability()) {
if let Some(rule) = self.message.rule() {
write!(f, "{} ", rule.noqa_code().to_string().red().bold())?;

View File

@@ -4,7 +4,6 @@ use ruff_python_ast::helpers::ReturnStatementVisitor;
use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault, Stmt};
use ruff_python_parser::typing::parse_type_annotation;
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::Definition;
use ruff_python_stdlib::typing::simple_magic_return_type;
@@ -514,13 +513,10 @@ fn check_dynamically_typed<F>(
{
if let Expr::StringLiteral(string_expr) = annotation {
// Quoted annotations
if let Ok((parsed_annotation, _)) =
parse_type_annotation(string_expr, checker.locator().contents())
{
if let Some(parsed_annotation) = checker.parse_type_annotation(string_expr) {
if type_hint_resolves_to_any(
parsed_annotation.expr(),
checker.semantic(),
checker.locator(),
parsed_annotation.expression(),
checker,
checker.settings.target_version.minor(),
) {
diagnostics.push(Diagnostic::new(
@@ -530,12 +526,7 @@ fn check_dynamically_typed<F>(
}
}
} else {
if type_hint_resolves_to_any(
annotation,
checker.semantic(),
checker.locator(),
checker.settings.target_version.minor(),
) {
if type_hint_resolves_to_any(annotation, checker, checker.settings.target_version.minor()) {
diagnostics.push(Diagnostic::new(
AnyType { name: func() },
annotation.range(),

View File

@@ -9,13 +9,33 @@ use crate::rules::flake8_async::helpers::AsyncModule;
use crate::settings::types::PythonVersion;
/// ## What it does
/// Checks for `async` functions with a `timeout` argument.
/// Checks for `async` function definitions with `timeout` parameters.
///
/// ## Why is this bad?
/// Rather than implementing asynchronous timeout behavior manually, prefer
/// built-in timeout functionality, such as `asyncio.timeout`, `trio.fail_after`,
/// or `anyio.move_on_after`, among others.
///
/// This rule is highly opinionated to enforce a design pattern
/// called ["structured concurrency"] that allows for
/// `async` functions to be oblivious to timeouts,
/// instead letting callers to handle the logic with a context manager.
///
/// ## Details
///
/// This rule attempts to detect which async framework your code is using
/// by analysing the imports in the file it's checking. If it sees an
/// `anyio` import in your code, it will assume `anyio` is your framework
/// of choice; if it sees a `trio` import, it will assume `trio`; if it
/// sees neither, it will assume `asyncio`. `asyncio.timeout` was added
/// in Python 3.11, so if `asyncio` is detected as the framework being used,
/// this rule will be ignored when your configured [`target-version`] is set
/// to less than Python 3.11.
///
/// For functions that wrap `asyncio.timeout`, `trio.fail_after` or
/// `anyio.move_on_after`, false positives from this rule can be avoided
/// by using a different parameter name.
///
/// ## Example
///
/// ```python
@@ -41,6 +61,8 @@ use crate::settings::types::PythonVersion;
/// - [`asyncio` timeouts](https://docs.python.org/3/library/asyncio-task.html#timeouts)
/// - [`anyio` timeouts](https://anyio.readthedocs.io/en/stable/cancellation.html)
/// - [`trio` timeouts](https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts)
///
/// ["structured concurrency"]: https://vorpus.org/blog/some-thoughts-on-asynchronous-api-design-in-a-post-asyncawait-world/#timeouts-and-cancellation
#[violation]
pub struct AsyncFunctionWithTimeout {
module: AsyncModule,

View File

@@ -1,12 +1,14 @@
use crate::settings::types::PythonVersion;
use ruff_python_ast::PySourceType;
use ruff_python_stdlib::builtins::{is_ipython_builtin, is_python_builtin};
use ruff_python_stdlib::builtins::is_python_builtin;
pub(super) fn shadows_builtin(
name: &str,
ignorelist: &[String],
source_type: PySourceType,
ignorelist: &[String],
python_version: PythonVersion,
) -> bool {
if is_python_builtin(name) || source_type.is_ipynb() && is_ipython_builtin(name) {
if is_python_builtin(name, python_version.minor(), source_type.is_ipynb()) {
ignorelist.iter().all(|ignore| ignore != name)
} else {
false

View File

@@ -12,6 +12,7 @@ mod tests {
use crate::assert_messages;
use crate::registry::Rule;
use crate::settings::types::PythonVersion;
use crate::settings::LinterSettings;
use crate::test::test_path;
@@ -112,4 +113,18 @@ mod tests {
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::BuiltinImportShadowing, Path::new("A004.py"))]
fn rules_py312(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}_py38", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("flake8_builtins").join(path).as_path(),
&LinterSettings {
target_version: PythonVersion::Py38,
..LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

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