Compare commits

..

65 Commits

Author SHA1 Message Date
Dhruv Manilawala
b0e26e6fc8 Bump version to 0.8.2 (#14789) 2024-12-05 18:06:35 +05:30
Dhruv Manilawala
e9941cd714 [red-knot] Move standalone expr inference to for non-name target (#14788)
## Summary

Ref: https://github.com/astral-sh/ruff/pull/14754#discussion_r1871040646

## Test Plan

Remove the TODO comment and update the mdtest.
2024-12-05 18:06:20 +05:30
Dhruv Manilawala
43bf1a8907 Add tests for "keyword as identifier" syntax errors (#14754)
## Summary

This is related to #13778, more specifically
https://github.com/astral-sh/ruff/issues/13778#issuecomment-2513556004.

This PR adds various test cases where a keyword is being where an
identifier is expected. The tests are to make sure that red knot doesn't
panic, raises the syntax error and the identifier is added to the symbol
table. The final part allows editor related features like renaming the
symbol.
2024-12-05 17:32:48 +05:30
InSync
fda8b1f884 [ruff] Unnecessary cast to int (RUF046) (#14697)
## Summary

Resolves #11412.

## Test Plan

`cargo nextest run` and `cargo insta test`.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2024-12-05 10:30:06 +01:00
David Peter
2d3f557875 [red-knot] Fallback for typing._NoDefaultType (#14783)
## Summary

`typing_extensions` has a `>=3.13` re-export for the `typing.NoDefault`
singleton, but not for `typing._NoDefaultType`. This causes problems as
soon as we understand `sys.version_info` branches, so we explicity
switch to `typing._NoDefaultType` for Python 3.13 and later.

This is a part of #14759 that I thought might make sense to break out
and merge in isolation.

## Test Plan

New test that will become more meaningful with #12700

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2024-12-05 09:17:55 +01:00
David Peter
bd27bfab5d [red-knot] Unify setup_db() functions, add TestDb builder (#14777)
## Summary

- Instead of seven (more or less similar) `setup_db` functions, use just
one in a single central place.
- For every test that needs customization beyond that, offer a
`TestDbBuilder` that can control the Python target version, custom
typeshed, and pre-existing files.

The main motivation for this is that we're soon going to need
customization of the Python version, and I didn't feel like adding this
to each of the existing `setup_db` functions.
2024-12-04 21:36:54 +01:00
InSync
155d34bbb9 [red-knot] Infer precise types for len() calls (#14599)
## Summary

Resolves #14598.

## Test Plan

Markdown tests.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-04 11:16:53 -08:00
Well404
04c887c8fc Fix references for async-busy-wait (#14775) 2024-12-04 18:05:49 +01:00
David Peter
af43bd4b0f [red-knot] Gradual forms do not participate in equivalence/subtyping (#14758)
## Summary

This changeset contains various improvements concerning non-fully-static
types and their relationships:

- Make sure that non-fully-static types do not participate in
equivalence or subtyping.
- Clarify what `Type::is_equivalent_to` actually implements.
- Introduce `Type::is_fully_static`
- New tests making sure that multiple `Any`/`Unknown`s inside unions and
intersections are collapsed.

closes #14524

## Test Plan

- Added new unit tests for union and intersection builder
- Added new unit tests for `Type::is_equivalent_to`
- Added new unit tests for `Type::is_subtype_of`
- Added new property test making sure that non-fully-static types do not
participate in subtyping
2024-12-04 17:11:25 +01:00
Harutaka Kawamura
614917769e Remove @ in pytest.mark.parametrize rule messages (#14770) 2024-12-04 16:01:10 +01:00
Douglas Creager
8b23086eac [red-knot] Add typing.Any as a spelling for the Any type (#14742)
We already had a representation for the Any type, which we would use
e.g. for expressions without type annotations. We now recognize
`typing.Any` as a way to refer to this type explicitly. Like other
special forms, this is tracked correctly through aliasing, and isn't
confused with local definitions that happen to have the same name.

Closes #14544
2024-12-04 09:56:36 -05:00
David Peter
948549fcdc [red-knot] Test: Hashable/Sized => A/B (#14769)
## Summary

Minor change that uses two plain classes `A` and `B` instead of
`typing.Sized` and `typing.Hashable`.

The motivation is twofold: I remember that I was confused when I first
saw this test. Was there anything specific to `Sized` and `Hashable`
that was relevant here? (there is, these classes are not overlapping;
and you can build a proper intersection from them; but that's true for
almost all non-builtin classes).

I now ran into another problem while working on #14758: `Sized` and
`Hashable` are protocols that we don't fully understand yet. This
causing some trouble when trying to infer whether these are fully-static
types or not.
2024-12-04 15:00:27 +01:00
David Salvisberg
e67f7f243d [flake8-type-checking] Expands TC006 docs to better explain itself (#14749)
Closes: #14676

I think the consensus generally was to keep the rule as-is, but expand
the docs.

## Summary

Expands the docs for TC006 with an explanation for why the type
expression is always quoted, including mention of another potential
benefit to this style.
2024-12-04 13:16:31 +00:00
Dylan
c617b2a48a [pycodestyle] Handle f-strings properly for invalid-escape-sequence (W605) (#14748)
When fixing an invalid escape sequence in an f-string, each f-string
element is analyzed for valid escape characters prior to creating the
diagnostic and fix. This allows us to safely prefix with `r` to create a
raw string if no valid escape characters were found anywhere in the
f-string, and otherwise insert backslashes.

This fixes a bug in the original implementation: each "f-string part"
was treated separately, so it was not possible to tell whether a valid
escape character was or would be used elsewhere in the f-string.

Progress towards #11491 but format specifiers are not handled in this
PR.
2024-12-04 06:59:14 -06:00
Dhruv Manilawala
1685d95ed2 [red-knot] Add fuzzer to catch panics for invalid syntax (#14678)
## Summary

This PR adds a fuzzer harness for red knot that runs the type checker on
source code that contains invalid syntax.

Additionally, this PR also updates the `init-fuzzer.sh` script to
increase the corpus size to:
* Include various crates that includes Python source code
* Use the 3.13 CPython source code

And, remove any non-Python files from the final corpus so that when the
fuzzer tries to minify the corpus, it doesn't produce files that only
contains documentation content as that's just noise.

## Test Plan

Run `./fuzz/init-fuzzer.sh`, say no to the large dataset.
Run the fuzzer with `cargo +night fuzz run red_knot_check_invalid_syntax
-- -timeout=5`
2024-12-04 14:36:58 +05:30
Dhruv Manilawala
575deb5d4d Check AIR001 from builtin or providers operators module (#14631)
## Summary

This PR makes changes to the `AIR001` rule as per
https://github.com/astral-sh/ruff/pull/14627#discussion_r1860212307.

Additionally,
* Avoid returning the `Diagnostic` and update the checker in the rule
logic for consistency
* Remove test case for different keyword position (I don't think it's
required here)

## Test Plan

Add test cases for multiple operators from various modules.
2024-12-04 13:30:47 +05:30
Wei Lee
edce559431 [airflow]: extend removed names (AIR302) (#14734) 2024-12-03 21:39:43 +00:00
Brent Westbrook
62e358e929 [ruff] Extend unnecessary-regular-expression to non-literal strings (RUF055) (#14679)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2024-12-03 15:17:20 +00:00
Alex Waygood
81bfcc9899 Minor followups to RUF052 (#14755)
## Summary

Just some minor followups to the recently merged RUF052 rule, that was
added in bf0fd04:
- Some small tweaks to the docs
- A minor code-style nit
- Some more tests for my peace of mind, just to check that the new
methods on the semantic model are working correctly

I'm adding the "internal" label as this doesn't deserve a changelog
entry. RUF052 is a new rule that hasn't been released yet.

## Test Plan

`cargo test -p ruff_linter`
2024-12-03 13:33:29 +00:00
David Peter
74309008fd [red-knot] Property tests (#14178)
## Summary

This PR adds a new `property_tests` module with quickcheck-based tests
that verify certain properties of types. The following properties are
currently checked:

* `is_equivalent_to`:
  * is reflexive: `T` is equivalent to itself
* `is_subtype_of`:
  * is reflexive: `T` is a subtype of `T`
* is antisymmetric: if `S <: T` and `T <: S`, then `S` is equivalent to
`T`
  * is transitive: `S <: T` & `T <: U` => `S <: U`
* `is_disjoint_from`:
  * is irreflexive: `T` is not disjoint from `T`
  * is symmetric: `S` disjoint from `T` => `T` disjoint from `S`
* `is_assignable_to`:
  * is reflexive
* `negate`:
  * is an involution: `T.negate().negate()` is equivalent to `T`

There are also some tests that validate higher-level properties like:

* `S <: T` implies that `S` is not disjoint from `T`
* `S <: T` implies that `S` is assignable to `T`
* A singleton type must also be single-valued

These tests found a few bugs so far:

- #14177 
- #14195 
- #14196 
- #14210
- #14731

Some additional notes:

- Quickcheck-based property tests are non-deterministic and finding
counter-examples might take an arbitrary long time. This makes them bad
candidates for running in CI (for every PR). We can think of running
them in a cron-job way from time to time, similar to fuzzing. But for
now, it's only possible to run them locally (see instructions in source
code).
- Some tests currently find false positive "counterexamples" because our
understanding of equivalence of types is not yet complete. We do not
understand that `int | str` is the same as `str | int`, for example.
These tests are in a separate `property_tests::flaky` module.
- Properties can not be formulated in every way possible, due to the
fact that `is_disjoint_from` and `is_subtype_of` can produce false
negative answers.
- The current shrinking implementation is very naive, which leads to
counterexamples that are very long (`str & Any & ~tuple[Any] &
~tuple[Unknown] & ~Literal[""] & ~Literal["a"] | str & int & ~tuple[Any]
& ~tuple[Unknown]`), requiring the developer to simplify manually. It
has not been a major issue so far, but there is a comment in the code
how this can be improved.
- The tests are currently implemented using a macro. This is a single
commit on top which can easily be reverted, if we prefer the plain code
instead. With the macro:
  ```rs
  // `S <: T` implies that `S` can be assigned to `T`.
  type_property_test!(
      subtype_of_implies_assignable_to, db,
forall types s, t. s.is_subtype_of(db, t) => s.is_assignable_to(db, t)
  );
  ```
  without the macro:
  ```rs
  /// `S <: T` implies that `S` can be assigned to `T`.
  #[quickcheck]
  fn subtype_of_implies_assignable_to(s: Ty, t: Ty) -> bool {
      let db = get_cached_db();
  
      let s = s.into_type(&db);
      let t = t.into_type(&db);
  
      !s.is_subtype_of(&*db, t) || s.is_assignable_to(&*db, t)
  }
  ```

## Test Plan

```bash
while cargo test --release -p red_knot_python_semantic --features property_tests types::property_tests; do :; done
```
2024-12-03 13:54:54 +01:00
David Peter
a255d79087 [red-knot] is_subtype_of fix for KnownInstance types (#14750)
## Summary

`KnownInstance::instance_fallback` may return instances of supertypes.
For example, it returns an instance of `_SpecialForm` for `Literal`.
This means it can't be used on the right-hand side of `is_subtype_of`
relationships, because it might lead to false positives.

I can lead to false negatives on the left hand side of `is_subtype_of`,
but this is at least a known limitation. False negatives are fine for
most applications, but false positives can lead to wrong results in
intersection-simplification, for example.

closes #14731

## Test Plan

Added regression test
2024-12-03 12:03:26 +01:00
Alex Waygood
70bd10614f Improve docs for flake8-use-pathlib rules (#14741)
Flag the perf impact more clearly, add more links, clarify the rule
about the glob module
2024-12-03 07:47:31 +00:00
Lokejoke
bf0fd04e4e [ruff] Implemented used-dummy-variable (RUF052) (#14611)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-12-03 08:36:16 +01:00
David Peter
a69dfd4a74 [red-knot] Simplify tuples containing Never (#14744)
## Summary

Simplify tuples containing `Never` to `Never`:

```py
from typing import Never

def never() -> Never: ...

reveal_type((1, never(), "foo"))  # revealed: Never
```

I should note that mypy and pyright do *not* perform this
simplification. I don't know why.


There is [only one
place](5137fcc9c8/crates/red_knot_python_semantic/src/types/infer.rs (L1477-L1484))
where we use `TupleType::new` directly (instead of `Type::tuple`, which
changes behavior here). This appears when creating `TypeVar`
constraints, and it looks to me like it should stay this way, because
we're using `TupleType` to store a list of constraints there, instead of
an actual type. We also store `tuple[constraint1, constraint2, …]` as
the type for the `constraint1, constraint2, …` tuple expression. This
would mean that we infer a type of `tuple[str, Never]` for the following
type variable constraints, without simplifying it to `Never`. This seems
like a weird edge case that's maybe not worth looking further into?!
```py
from typing import Never

#         vvvvvvvvvv
def f[T: (str, Never)](x: T):
    pass
```

## Test Plan

- Added a new unit test. Did not add additional Markdown tests as that
seems superfluous.
- Tested the example above using red knot, mypy, pyright.
- Verified that this allows us to remove `contains_never` from the
property tests
(https://github.com/astral-sh/ruff/pull/14178#discussion_r1866473192)
2024-12-03 08:28:36 +01:00
Micha Reiser
c2e17d0399 Possible fix for flaky file watching test (#14543) 2024-12-03 08:22:42 +01:00
Dylan
10fef8bd5d [flake8-import-conventions] Improve syntax check for aliases supplied in configuration for unconventional-import-alias (ICN001) (#14745)
This PR improves on #14477 by:

- Ensuring user's do not require the module alias "__debug__", which is unassignable
- Validating the linter settings for
`lint.flake8-import-conventions.extend-aliases` (whereas previously we
only did this for `lint.flake8-import-conventions.aliases`).

Closes #14662
2024-12-02 22:41:47 -06:00
InSync
246a6df87d [red-knot] Deeper understanding of LiteralString (#14649)
## Summary

Resolves #14648.

## Test Plan

Markdown tests.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-12-03 03:31:58 +00:00
Connor Skees
3e702e12f7 red-knot: support narrowing for bool(E) (#14668)
Resolves https://github.com/astral-sh/ruff/issues/14547 by delegating
narrowing to `E` for `bool(E)` where `E` is some expression.

This change does not include other builtin class constructors which
should also work in this position, like `int(..)` or `float(..)`, as the
original issue does not mention these. It should be easy enough to add
checks for these as well if we want to.

I don't see a lot of markdown tests for malformed input, maybe there's a
better place for the no args and too many args cases to go?

I did see after the fact that it looks like this task was intended for a
new hire.. my apologies. I got here from
https://github.com/astral-sh/ruff/issues/13694, which is marked
help-wanted.

---------

Co-authored-by: David Peter <mail@david-peter.de>
2024-12-03 03:04:59 +00:00
Dylan
91e2d9a139 [refurb] Handle non-finite decimals in verbose-decimal-constructor (FURB157) (#14596)
This PR extends the Decimal parsing used in [verbose-decimal-constructor
(FURB157)](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor/)
to better handle non-finite `Decimal` objects, avoiding some false
negatives.

Closes #14587

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2024-12-02 18:13:20 -06:00
David Peter
5137fcc9c8 [red-knot] Re-enable linter corpus tests (#14736)
## Summary

Seeing the fuzzing results from @dhruvmanila in #13778, I think we can
re-enable these tests. We also had one regression that would have been
caught by these tests, so there is some value in having them enabled.
2024-12-02 20:11:30 +01:00
Matt Ord
83651deac7 [pylint] Ignore overload in PLR0904 (#14730)
Fixes #14727

## Summary

Fixes #14727

## Test Plan

cargo test

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2024-12-02 14:36:51 +00:00
Alex Waygood
6dfe125f44 Improve error messages and docs for flake8-comprehensions rules (#14729) 2024-12-02 13:36:15 +00:00
Micha Reiser
f96dfc179f Revert: [pyflakes] Avoid false positives in @no_type_check contexts (F821, F722) (#14615) (#14726) 2024-12-02 14:28:27 +01:00
Tzu-ping Chung
76d2e56501 [airflow] Avoid deprecated values (AIR302) (#14582) 2024-12-02 07:39:26 +00:00
Micha Reiser
30d80d9746 Sort discovered workspace packages for consistent cross-platform package discovery (#14725) 2024-12-02 07:36:08 +00:00
renovate[bot]
5a67d3269b Update pre-commit dependencies (#14719) 2024-12-02 06:02:56 +00:00
renovate[bot]
02d1e6a94a Update dawidd6/action-download-artifact action to v7 (#14722) 2024-12-02 01:25:51 +00:00
Simon Brugman
48ec3a8add [refurb] Guard hashlib imports and mark hashlib-digest-hex fix as safe (FURB181) (#14694)
## Summary

- Check if `hashlib` and `crypt` imports have been seen for `FURB181`
and `S324`
- Mark the fix for `FURB181` as safe: I think it was accidentally marked
as unsafe in the first place. The rule does not support user-defined
classes as the "fix safety" section suggests.
- Removed `hashlib._Hash`, as it's not part of the `hashlib` module.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

Updated the test snapshots
2024-12-01 20:24:49 -05:00
renovate[bot]
289a938ae8 Update astral-sh/setup-uv action to v4 (#14721) 2024-12-02 01:24:22 +00:00
renovate[bot]
3e5ab6cf38 Update NPM Development dependencies (#14720) 2024-12-02 01:24:09 +00:00
renovate[bot]
48d33595b9 Update dependency tomli to v2.2.1 (#14718) 2024-12-02 01:22:18 +00:00
renovate[bot]
23ee7a954e Update cloudflare/wrangler-action action to v3.13.0 (#14716) 2024-12-02 01:18:43 +00:00
renovate[bot]
d4a7c098dc Update Rust crate ureq to v2.11.0 (#14715) 2024-12-02 01:17:31 +00:00
renovate[bot]
0c5f03a059 Update dependency ruff to v0.8.1 (#14717) 2024-12-02 01:13:13 +00:00
renovate[bot]
239bfb6de7 Update Rust crate similar to v2.6.0 (#14714) 2024-12-01 20:04:07 -05:00
renovate[bot]
3c3ec6755c Update Rust crate rustc-hash to v2.1.0 (#14713) 2024-12-01 20:04:00 -05:00
renovate[bot]
4c05f2c8b4 Update tokio-tracing monorepo (#14710) 2024-12-01 20:03:50 -05:00
renovate[bot]
d594796e3a Update rust-wasm-bindgen monorepo (#14709) 2024-12-01 20:03:43 -05:00
renovate[bot]
b5ef2844ef Update Rust crate syn to v2.0.90 (#14708) 2024-12-01 20:03:36 -05:00
renovate[bot]
06183bd8a1 Update Rust crate pathdiff to v0.2.3 (#14707) 2024-12-01 20:03:29 -05:00
renovate[bot]
4068006c5f Update Rust crate libc to v0.2.167 (#14705) 2024-12-01 20:03:23 -05:00
renovate[bot]
145c97c94f Update Rust crate ordermap to v0.5.4 (#14706) 2024-12-01 20:03:05 -05:00
github-actions[bot]
84748be163 Sync vendored typeshed stubs (#14696)
Close and reopen this PR to trigger CI

Co-authored-by: typeshedbot <>
2024-12-01 01:38:31 +00:00
Brent Westbrook
9e017634cb [pep8-naming] Avoid false positive for class Bar(type(foo)) (N804) (#14683) 2024-11-30 22:37:28 +00:00
Simon Brugman
56ae73a925 [pylint] Fix false negatives for ascii and sorted in len-as-condition (PLC1802) (#14692) 2024-11-30 14:10:30 -06:00
InSync
be07424e80 Increase rule set size (#14689) 2024-11-30 15:12:10 +01:00
Connor Skees
579ef01294 mdtest: include test name in printed rerun command (#14684)
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-11-30 11:01:06 +00:00
Micha Reiser
90487b8cbd Skip panda rules if panda module hasn't been seen (#14671) 2024-11-29 21:32:51 +00:00
Alex Waygood
f3d8c023d3 [ruff] Avoid emitting assignment-in-assert when all references to the assigned variable are themselves inside asserts (RUF018) (#14661) 2024-11-29 13:36:59 +00:00
Micha Reiser
b63c2e126b Upgrade Rust toolchain to 1.83 (#14677) 2024-11-29 12:05:05 +00:00
Connor Skees
a6402fb51e mdtest: allow specifying a specific test inside a file (#14670) 2024-11-29 12:59:07 +01:00
Dhruv Manilawala
b3b2c982cd Update CHANGELOG.md with the new commits for 0.8.1 (#14664)
The 0.8.1 release was delayed, so this PR updates the CHANGELOG.md with
the latest commits on `main`.
2024-11-29 03:15:36 +00:00
Simon Brugman
abb3c6ea95 [flake8-pyi] Avoid rewriting invalid type expressions in unnecessary-type-union (PYI055) (#14660) 2024-11-28 18:30:50 +00:00
Brent Westbrook
224fe75a76 [ruff] Implement unnecessary-regular-expression (RUF055) (#14659)
Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Simon Brugman <sbrugman@users.noreply.github.com>
2024-11-28 18:29:23 +00:00
Simon Brugman
dc29f52750 [flake8-pyi, ruff] Fix traversal of nested literals and unions (PYI016, PYI051, PYI055, PYI062, RUF041) (#14641) 2024-11-28 18:07:12 +00:00
476 changed files with 12797 additions and 3880 deletions

View File

@@ -32,6 +32,8 @@ jobs:
# Flag that is raised when any code is changed
# This is superset of the linter and formatter
code: ${{ steps.changed.outputs.code_any_changed }}
# Flag that is raised when any code that affects the fuzzer is changed
fuzz: ${{ steps.changed.outputs.fuzz_any_changed }}
steps:
- uses: actions/checkout@v4
with:
@@ -79,6 +81,11 @@ jobs:
- python/**
- .github/workflows/ci.yaml
fuzz:
- fuzz/Cargo.toml
- fuzz/Cargo.lock
- fuzz/fuzz_targets/**
code:
- "**/*"
- "!**/*.md"
@@ -288,7 +295,7 @@ jobs:
name: "cargo fuzz build"
runs-on: ubuntu-latest
needs: determine_changes
if: ${{ github.ref == 'refs/heads/main' }}
if: ${{ github.ref == 'refs/heads/main' || needs.determine_changes.outputs.fuzz == 'true' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
@@ -383,7 +390,7 @@ jobs:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v6
- uses: dawidd6/action-download-artifact@v7
name: Download baseline Ruff binary
with:
name: ruff
@@ -559,7 +566,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v4
- uses: Swatinem/rust-cache@v2
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}

View File

@@ -17,7 +17,7 @@ jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v6
- uses: dawidd6/action-download-artifact@v7
name: Download pull request number
with:
name: pr-number
@@ -33,7 +33,7 @@ jobs:
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
fi
- uses: dawidd6/action-download-artifact@v6
- uses: dawidd6/action-download-artifact@v7
name: "Download ecosystem results"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number

View File

@@ -47,7 +47,7 @@ jobs:
working-directory: playground
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@v3.12.1
uses: cloudflare/wrangler-action@v3.13.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@v3
uses: astral-sh/setup-uv@v4
- uses: actions/download-artifact@v4
with:
pattern: wheels-*

View File

@@ -22,7 +22,7 @@ repos:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.18
rev: 0.7.19
hooks:
- id: mdformat
additional_dependencies:
@@ -36,7 +36,7 @@ repos:
)$
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.42.0
rev: v0.43.0
hooks:
- id: markdownlint-fix
exclude: |
@@ -59,7 +59,7 @@ repos:
- black==24.10.0
- repo: https://github.com/crate-ci/typos
rev: v1.27.3
rev: v1.28.1
hooks:
- id: typos
@@ -73,7 +73,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.4
rev: v0.8.1
hooks:
- id: ruff-format
- id: ruff
@@ -83,7 +83,7 @@ repos:
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.3.3
rev: v3.4.1
hooks:
- id: prettier
types: [yaml]

View File

@@ -192,7 +192,7 @@ flag or `unsafe-fixes` configuration option can be used to enable unsafe fixes.
See the [docs](https://docs.astral.sh/ruff/configuration/#fix-safety) for details.
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
### Remove formatter-conflicting rules from the default rule set ([#7900](https://github.com/astral-sh/ruff/pull/7900))
Previously, Ruff enabled all implemented rules in Pycodestyle (`E`) by default. Ruff now only includes the
Pycodestyle prefixes `E4`, `E7`, and `E9` to exclude rules that conflict with automatic formatters. Consequently,

View File

@@ -1,5 +1,42 @@
# Changelog
## 0.8.2
### Preview features
- \[`airflow`\] Avoid deprecated values (`AIR302`) ([#14582](https://github.com/astral-sh/ruff/pull/14582))
- \[`airflow`\] Extend removed names for `AIR302` ([#14734](https://github.com/astral-sh/ruff/pull/14734))
- \[`ruff`\] Extend `unnecessary-regular-expression` to non-literal strings (`RUF055`) ([#14679](https://github.com/astral-sh/ruff/pull/14679))
- \[`ruff`\] Implement `used-dummy-variable` (`RUF052`) ([#14611](https://github.com/astral-sh/ruff/pull/14611))
- \[`ruff`\] Implement `unnecessary-cast-to-int` (`RUF046`) ([#14697](https://github.com/astral-sh/ruff/pull/14697))
### Rule changes
- \[`airflow`\] Check `AIR001` from builtin or providers `operators` module ([#14631](https://github.com/astral-sh/ruff/pull/14631))
- \[`flake8-pytest-style`\] Remove `@` in `pytest.mark.parametrize` rule messages ([#14770](https://github.com/astral-sh/ruff/pull/14770))
- \[`pandas-vet`\] Skip rules if the `panda` module hasn't been seen ([#14671](https://github.com/astral-sh/ruff/pull/14671))
- \[`pylint`\] Fix false negatives for `ascii` and `sorted` in `len-as-condition` (`PLC1802`) ([#14692](https://github.com/astral-sh/ruff/pull/14692))
- \[`refurb`\] Guard `hashlib` imports and mark `hashlib-digest-hex` fix as safe (`FURB181`) ([#14694](https://github.com/astral-sh/ruff/pull/14694))
### Configuration
- \[`flake8-import-conventions`\] Improve syntax check for aliases supplied in configuration for `unconventional-import-alias` (`ICN001`) ([#14745](https://github.com/astral-sh/ruff/pull/14745))
### Bug fixes
- Revert: [pyflakes] Avoid false positives in `@no_type_check` contexts (`F821`, `F722`) (#14615) ([#14726](https://github.com/astral-sh/ruff/pull/14726))
- \[`pep8-naming`\] Avoid false positive for `class Bar(type(foo))` (`N804`) ([#14683](https://github.com/astral-sh/ruff/pull/14683))
- \[`pycodestyle`\] Handle f-strings properly for `invalid-escape-sequence` (`W605`) ([#14748](https://github.com/astral-sh/ruff/pull/14748))
- \[`pylint`\] Ignore `@overload` in `PLR0904` ([#14730](https://github.com/astral-sh/ruff/pull/14730))
- \[`refurb`\] Handle non-finite decimals in `verbose-decimal-constructor` (`FURB157`) ([#14596](https://github.com/astral-sh/ruff/pull/14596))
- \[`ruff`\] Avoid emitting `assignment-in-assert` when all references to the assigned variable are themselves inside `assert`s (`RUF018`) ([#14661](https://github.com/astral-sh/ruff/pull/14661))
### Documentation
- Improve docs for `flake8-use-pathlib` rules ([#14741](https://github.com/astral-sh/ruff/pull/14741))
- Improve error messages and docs for `flake8-comprehensions` rules ([#14729](https://github.com/astral-sh/ruff/pull/14729))
- \[`flake8-type-checking`\] Expands `TC006` docs to better explain itself ([#14749](https://github.com/astral-sh/ruff/pull/14749))
## 0.8.1
### Preview features
@@ -21,6 +58,7 @@
- \[`ruff`\] Auto-add `r` prefix when string has no backslashes for `unraw-re-pattern` (`RUF039`) ([#14536](https://github.com/astral-sh/ruff/pull/14536))
- \[`ruff`\] Implement `invalid-assert-message-literal-argument` (`RUF040`) ([#14488](https://github.com/astral-sh/ruff/pull/14488))
- \[`ruff`\] Implement `unnecessary-nested-literal` (`RUF041`) ([#14323](https://github.com/astral-sh/ruff/pull/14323))
- \[`ruff`\] Implement `unnecessary-regular-expression` (`RUF055`) ([#14659](https://github.com/astral-sh/ruff/pull/14659))
### Rule changes
@@ -34,6 +72,8 @@
- Avoid fixing code to `None | None` for `redundant-none-literal` (`PYI061`) and `never-union` (`RUF020`) ([#14583](https://github.com/astral-sh/ruff/pull/14583), [#14589](https://github.com/astral-sh/ruff/pull/14589))
- \[`flake8-bugbear`\] Fix `mutable-contextvar-default` to resolve annotated function calls properly (`B039`) ([#14532](https://github.com/astral-sh/ruff/pull/14532))
- \[`flake8-pyi`, `ruff`\] Fix traversal of nested literals and unions (`PYI016`, `PYI051`, `PYI055`, `PYI062`, `RUF041`) ([#14641](https://github.com/astral-sh/ruff/pull/14641))
- \[`flake8-pyi`\] Avoid rewriting invalid type expressions in `unnecessary-type-union` (`PYI055`) ([#14660](https://github.com/astral-sh/ruff/pull/14660))
- \[`flake8-type-checking`\] Avoid syntax errors and type checking problem for quoted annotations autofix (`TC003`, `TC006`) ([#14634](https://github.com/astral-sh/ruff/pull/14634))
- \[`pylint`\] Do not wrap function calls in parentheses in the fix for unnecessary-dunder-call (`PLC2801`) ([#14601](https://github.com/astral-sh/ruff/pull/14601))
- \[`ruff`\] Handle `attrs`'s `auto_attribs` correctly (`RUF009`) ([#14520](https://github.com/astral-sh/ruff/pull/14520))
@@ -254,7 +294,7 @@ The following fixes have been stabilized:
### Preview features
- Fix `E221` and `E222` to flag missing or extra whitespace around `==` operator ([#13890](https://github.com/astral-sh/ruff/pull/13890))
- Formatter: Alternate quotes for strings inside f-strings in preview ([#13860](https://github.com/astral-sh/ruff/pull/13860))
- Formatter: Alternate quotes for strings inside f-strings in preview ([#13860](https://github.com/astral-sh/ruff/pull/13860))
- Formatter: Join implicit concatenated strings when they fit on a line ([#13663](https://github.com/astral-sh/ruff/pull/13663))
- \[`pylint`\] Restrict `iteration-over-set` to only work on sets of literals (`PLC0208`) ([#13731](https://github.com/astral-sh/ruff/pull/13731))
@@ -1269,7 +1309,7 @@ To read more about this exciting milestone, check out our [blog post](https://as
### Preview features
- \[`pycodestyle`\] Ignore end-of-line comments when determining blank line rules ([#11342](https://github.com/astral-sh/ruff/pull/11342))
- \[`pylint`\] Detect `pathlib.Path.open` calls in `unspecified-encoding` (`PLW1514`) ([#11288](https://github.com/astral-sh/ruff/pull/11288))
- \[`pylint`\] Detect `pathlib.Path.open` calls in `unspecified-encoding` (`PLW1514`) ([#11288](https://github.com/astral-sh/ruff/pull/11288))
- \[`flake8-pyi`\] Implement `PYI059` (`generic-not-last-base-class`) ([#11233](https://github.com/astral-sh/ruff/pull/11233))
- \[`flake8-pyi`\] Implement `PYI062` (`duplicate-literal-member`) ([#11269](https://github.com/astral-sh/ruff/pull/11269))
@@ -1644,7 +1684,7 @@ To setup `ruff server` with your editor, refer to the [README.md](https://github
- \[`pycodestyle`\] Do not ignore lines before the first logical line in blank lines rules. ([#10382](https://github.com/astral-sh/ruff/pull/10382))
- \[`pycodestyle`\] Do not trigger `E225` and `E275` when the next token is a ')' ([#10315](https://github.com/astral-sh/ruff/pull/10315))
- \[`pylint`\] Avoid false-positive slot non-assignment for `__dict__` (`PLE0237`) ([#10348](https://github.com/astral-sh/ruff/pull/10348))
- Gate f-string struct size test for Rustc \< 1.76 ([#10371](https://github.com/astral-sh/ruff/pull/10371))
- Gate f-string struct size test for Rustc < 1.76 ([#10371](https://github.com/astral-sh/ruff/pull/10371))
### Documentation

View File

@@ -863,7 +863,7 @@ each configuration file.
The package root is used to determine a file's "module path". Consider, again, `baz.py`. In that
case, `./my_project/src/foo` was identified as the package root, so the module path for `baz.py`
would resolve to `foo.bar.baz` — as computed by taking the relative path from the package root
would resolve to `foo.bar.baz` — as computed by taking the relative path from the package root
(inclusive of the root itself). The module path can be thought of as "the path you would use to
import the module" (e.g., `import foo.bar.baz`).

243
Cargo.lock generated
View File

@@ -413,7 +413,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -693,7 +693,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -704,7 +704,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -774,7 +774,7 @@ dependencies = [
"glob",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -826,7 +826,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1246,7 +1246,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1314,9 +1314,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
@@ -1420,7 +1420,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1485,10 +1485,11 @@ checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae"
[[package]]
name = "js-sys"
version = "0.3.72"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705"
dependencies = [
"once_cell",
"wasm-bindgen",
]
@@ -1520,9 +1521,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.164"
version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libcst"
@@ -1546,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
dependencies = [
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -1833,9 +1834,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordermap"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f2bd7b03bf2c767e1bb7b91505dbe022833776e60480275e6f2fb0db0c7503"
checksum = "f80a48eb68b6a7da9829b8b0429011708f775af80676a91063d023a66a656106"
dependencies = [
"indexmap",
]
@@ -1910,9 +1911,9 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]]
name = "pathdiff"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "peg"
@@ -1965,7 +1966,7 @@ dependencies = [
"once_cell",
"pep440_rs",
"regex",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"serde",
"smallvec",
"thiserror 1.0.67",
@@ -2012,7 +2013,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -2172,6 +2173,26 @@ dependencies = [
"memchr",
]
[[package]]
name = "quickcheck"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"rand",
]
[[package]]
name = "quickcheck_macros"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "quote"
version = "1.0.37"
@@ -2272,6 +2293,8 @@ dependencies = [
"itertools 0.13.0",
"memchr",
"ordermap",
"quickcheck",
"quickcheck_macros",
"red_knot_test",
"red_knot_vendored",
"ruff_db",
@@ -2282,7 +2305,7 @@ dependencies = [
"ruff_python_stdlib",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"salsa",
"serde",
"smallvec",
@@ -2309,7 +2332,7 @@ dependencies = [
"ruff_python_ast",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"serde",
"serde_json",
"shellexpand",
@@ -2322,6 +2345,7 @@ name = "red_knot_test"
version = "0.0.0"
dependencies = [
"anyhow",
"camino",
"colored",
"memchr",
"red_knot_python_semantic",
@@ -2332,7 +2356,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"salsa",
"smallvec",
]
@@ -2380,7 +2404,7 @@ dependencies = [
"ruff_db",
"ruff_python_ast",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"salsa",
"serde",
"thiserror 2.0.3",
@@ -2489,7 +2513,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.8.1"
version = "0.8.2"
dependencies = [
"anyhow",
"argfile",
@@ -2529,7 +2553,7 @@ dependencies = [
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"serde",
"serde_json",
"shellexpand",
@@ -2560,7 +2584,7 @@ dependencies = [
"ruff_python_formatter",
"ruff_python_parser",
"ruff_python_trivia",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"serde",
"serde_json",
"tikv-jemallocator",
@@ -2602,7 +2626,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"salsa",
"serde",
"tempfile",
@@ -2671,7 +2695,7 @@ dependencies = [
"ruff_cache",
"ruff_macros",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
"static_assertions",
@@ -2708,7 +2732,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.8.1"
version = "0.8.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2749,7 +2773,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
"serde_json",
@@ -2775,7 +2799,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -2811,7 +2835,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
]
@@ -2857,7 +2881,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
"serde_json",
@@ -2904,7 +2928,7 @@ dependencies = [
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"static_assertions",
"unicode-ident",
"unicode-normalization",
@@ -2935,7 +2959,7 @@ dependencies = [
"ruff_python_parser",
"ruff_python_stdlib",
"ruff_text_size",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
]
@@ -2993,7 +3017,7 @@ dependencies = [
"ruff_source_file",
"ruff_text_size",
"ruff_workspace",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"serde",
"serde_json",
"shellexpand",
@@ -3023,7 +3047,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.8.1"
version = "0.8.2"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3074,7 +3098,7 @@ dependencies = [
"ruff_python_semantic",
"ruff_python_stdlib",
"ruff_source_file",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"schemars",
"serde",
"shellexpand",
@@ -3101,9 +3125,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustix"
@@ -3120,9 +3144,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.10"
version = "0.23.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
dependencies = [
"log",
"once_cell",
@@ -3135,15 +3159,15 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.7.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
[[package]]
name = "rustls-webpki"
version = "0.102.5"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
@@ -3175,7 +3199,7 @@ dependencies = [
"indexmap",
"lazy_static",
"parking_lot",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"salsa-macro-rules",
"salsa-macros",
"smallvec",
@@ -3195,7 +3219,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"synstructure",
]
@@ -3229,7 +3253,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3278,7 +3302,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3289,7 +3313,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3312,7 +3336,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3353,7 +3377,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3387,9 +3411,9 @@ dependencies = [
[[package]]
name = "similar"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e"
[[package]]
name = "siphasher"
@@ -3461,7 +3485,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3472,9 +3496,20 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.89"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -3489,7 +3524,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3552,7 +3587,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3563,7 +3598,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"test-case-core",
]
@@ -3593,7 +3628,7 @@ checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3604,7 +3639,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -3708,9 +3743,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.40"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
@@ -3720,20 +3755,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.27"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
@@ -3775,9 +3810,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term 0.46.0",
@@ -3926,18 +3961,21 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.10.1"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
checksum = "b30e6f97efe1fa43535ee241ee76967d3ff6ff3953ebb430d8d55c5393029e7b"
dependencies = [
"base64 0.22.0",
"flate2",
"litemap",
"log",
"once_cell",
"rustls",
"rustls-pki-types",
"url",
"webpki-roots",
"yoke",
"zerofrom",
]
[[package]]
@@ -3996,7 +4034,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -4071,9 +4109,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c"
dependencies = [
"cfg-if",
"once_cell",
@@ -4082,36 +4120,37 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.45"
version = "0.4.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4119,32 +4158,32 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.45"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426"
checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201"
dependencies = [
"console_error_panic_hook",
"js-sys",
"minicov",
"once_cell",
"scoped-tls",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -4153,20 +4192,20 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.45"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
name = "web-sys"
version = "0.3.69"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4462,7 +4501,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"synstructure",
]
@@ -4483,7 +4522,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]
@@ -4503,7 +4542,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
"synstructure",
]
@@ -4532,7 +4571,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.90",
]
[[package]]

View File

@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.8.1/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.8.1/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.8.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.8.2/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.8.1
rev: v0.8.2
hooks:
# Run the linter.
- id: ruff

View File

@@ -103,7 +103,7 @@ called **once**.
## Profiling
Red Knot generates a folded stack trace to the current directory named `tracing.folded` when setting the environment variable `RED_KNOT_LOG_PROFILE` to `1` or `true`.
Red Knot generates a folded stack trace to the current directory named `tracing.folded` when setting the environment variable `RED_KNOT_LOG_PROFILE` to `1` or `true`.
```bash
RED_KNOT_LOG_PROFILE=1 red_knot -- --current-directory=../test -vvv

View File

@@ -5,11 +5,11 @@
pub enum TargetVersion {
Py37,
Py38,
#[default]
Py39,
Py310,
Py311,
Py312,
#[default]
Py313,
}

View File

@@ -1,25 +1,23 @@
#![allow(clippy::disallowed_names)]
use std::io::Write;
use std::time::Duration;
use std::time::{Duration, Instant};
use anyhow::{anyhow, Context};
use red_knot_python_semantic::{resolve_module, ModuleName, Program, PythonVersion, SitePackages};
use red_knot_workspace::db::{Db, RootDatabase};
use red_knot_workspace::watch;
use red_knot_workspace::watch::{directory_watcher, WorkspaceWatcher};
use red_knot_workspace::watch::{directory_watcher, ChangeEvent, WorkspaceWatcher};
use red_knot_workspace::workspace::settings::{Configuration, SearchPathConfiguration};
use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_db::files::{system_path_to_file, File, FileError};
use ruff_db::source::source_text;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
use ruff_db::testing::{setup_logging, setup_logging_with_filter};
use ruff_db::Upcast;
struct TestCase {
db: RootDatabase,
watcher: Option<WorkspaceWatcher>,
changes_receiver: crossbeam::channel::Receiver<Vec<watch::ChangeEvent>>,
changes_receiver: crossbeam::channel::Receiver<Vec<ChangeEvent>>,
/// The temporary directory that contains the test files.
/// We need to hold on to it in the test case or the temp files get deleted.
_temp_dir: tempfile::TempDir,
@@ -40,12 +38,36 @@ impl TestCase {
&self.db
}
fn stop_watch(&mut self) -> Vec<watch::ChangeEvent> {
self.try_stop_watch(Duration::from_secs(10))
.expect("Expected watch changes but observed none")
#[track_caller]
fn stop_watch<M>(&mut self, matcher: M) -> Vec<ChangeEvent>
where
M: MatchEvent,
{
// track_caller is unstable for lambdas -> That's why this is a fn
#[track_caller]
fn panic_with_formatted_events(events: Vec<ChangeEvent>) -> Vec<ChangeEvent> {
panic!(
"Didn't observe expected change:\n{}",
events
.into_iter()
.map(|event| format!(" - {event:?}"))
.collect::<Vec<_>>()
.join("\n")
)
}
self.try_stop_watch(matcher, Duration::from_secs(10))
.unwrap_or_else(panic_with_formatted_events)
}
fn try_stop_watch(&mut self, timeout: Duration) -> Option<Vec<watch::ChangeEvent>> {
fn try_stop_watch<M>(
&mut self,
mut matcher: M,
timeout: Duration,
) -> Result<Vec<ChangeEvent>, Vec<ChangeEvent>>
where
M: MatchEvent,
{
tracing::debug!("Try stopping watch with timeout {:?}", timeout);
let watcher = self
@@ -53,36 +75,50 @@ impl TestCase {
.take()
.expect("Cannot call `stop_watch` more than once");
let mut all_events = self
.changes_receiver
.recv_timeout(timeout)
.unwrap_or_default();
let start = Instant::now();
let mut all_events = Vec::new();
loop {
let events = self
.changes_receiver
.recv_timeout(Duration::from_millis(100))
.unwrap_or_default();
if events
.iter()
.any(|event| matcher.match_event(event) || event.is_rescan())
{
all_events.extend(events);
break;
}
all_events.extend(events);
if start.elapsed() > timeout {
return Err(all_events);
}
}
watcher.flush();
tracing::debug!("Flushed file watcher");
watcher.stop();
tracing::debug!("Stopping file watcher");
// Consume remaining events
for event in &self.changes_receiver {
all_events.extend(event);
}
if all_events.is_empty() {
return None;
}
Some(all_events)
Ok(all_events)
}
fn take_watch_changes(&self) -> Vec<watch::ChangeEvent> {
fn take_watch_changes(&self) -> Vec<ChangeEvent> {
self.try_take_watch_changes(Duration::from_secs(10))
.expect("Expected watch changes but observed none")
}
fn try_take_watch_changes(&self, timeout: Duration) -> Option<Vec<watch::ChangeEvent>> {
let Some(watcher) = &self.watcher else {
return None;
};
fn try_take_watch_changes(&self, timeout: Duration) -> Option<Vec<ChangeEvent>> {
let watcher = self.watcher.as_ref()?;
let mut all_events = self
.changes_receiver
@@ -104,7 +140,7 @@ impl TestCase {
Some(all_events)
}
fn apply_changes(&mut self, changes: Vec<watch::ChangeEvent>) {
fn apply_changes(&mut self, changes: Vec<ChangeEvent>) {
self.db.apply_changes(changes, Some(&self.configuration));
}
@@ -140,6 +176,23 @@ impl TestCase {
}
}
trait MatchEvent {
fn match_event(&mut self, event: &ChangeEvent) -> bool;
}
fn event_for_file(name: &str) -> impl MatchEvent + '_ {
|event: &ChangeEvent| event.file_name() == Some(name)
}
impl<F> MatchEvent for F
where
F: FnMut(&ChangeEvent) -> bool,
{
fn match_event(&mut self, event: &ChangeEvent) -> bool {
(*self)(event)
}
}
trait SetupFiles {
fn setup(self, root_path: &SystemPath, workspace_path: &SystemPath) -> anyhow::Result<()>;
}
@@ -315,7 +368,7 @@ fn new_file() -> anyhow::Result<()> {
std::fs::write(foo_path.as_std_path(), "print('Hello')")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -338,7 +391,7 @@ fn new_ignored_file() -> anyhow::Result<()> {
std::fs::write(foo_path.as_std_path(), "print('Hello')")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -360,7 +413,7 @@ fn changed_file() -> anyhow::Result<()> {
update_file(&foo_path, "print('Version 2')")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
assert!(!changes.is_empty());
@@ -385,7 +438,7 @@ fn deleted_file() -> anyhow::Result<()> {
std::fs::remove_file(foo_path.as_std_path())?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -417,7 +470,7 @@ fn move_file_to_trash() -> anyhow::Result<()> {
trash_path.join("foo.py").as_std_path(),
)?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -449,7 +502,7 @@ fn move_file_to_workspace() -> anyhow::Result<()> {
std::fs::rename(foo_path.as_std_path(), foo_in_workspace_path.as_std_path())?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -477,7 +530,7 @@ fn rename_file() -> anyhow::Result<()> {
std::fs::rename(foo_path.as_std_path(), bar_path.as_std_path())?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("bar.py"));
case.apply_changes(changes);
@@ -521,7 +574,7 @@ fn directory_moved_to_workspace() -> anyhow::Result<()> {
std::fs::rename(sub_original_path.as_std_path(), sub_new_path.as_std_path())
.with_context(|| "Failed to move sub directory")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("sub"));
case.apply_changes(changes);
@@ -580,7 +633,7 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
std::fs::rename(sub_path.as_std_path(), trashed_sub.as_std_path())
.with_context(|| "Failed to move the sub directory to the trash")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("sub"));
case.apply_changes(changes);
@@ -604,8 +657,6 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
#[test]
fn directory_renamed() -> anyhow::Result<()> {
let _tracing = setup_logging_with_filter("file_watching=TRACE,red_knot=TRACE");
let mut case = setup([
("bar.py", "import sub.a"),
("sub/__init__.py", ""),
@@ -644,11 +695,8 @@ fn directory_renamed() -> anyhow::Result<()> {
std::fs::rename(sub_path.as_std_path(), foo_baz.as_std_path())
.with_context(|| "Failed to move the sub directory")?;
let changes = case.stop_watch();
for event in &changes {
tracing::debug!("Event: {:?}", event);
}
// Linux and windows only emit an event for the newly created root directory, but not for every new component.
let changes = case.stop_watch(event_for_file("sub"));
case.apply_changes(changes);
@@ -721,7 +769,7 @@ fn directory_deleted() -> anyhow::Result<()> {
std::fs::remove_dir_all(sub_path.as_std_path())
.with_context(|| "Failed to remove the sub directory")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("sub"));
case.apply_changes(changes);
@@ -758,7 +806,7 @@ fn search_path() -> anyhow::Result<()> {
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("a.py"));
case.apply_changes(changes);
@@ -789,7 +837,7 @@ fn add_search_path() -> anyhow::Result<()> {
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("a.py"));
case.apply_changes(changes);
@@ -818,9 +866,9 @@ fn remove_search_path() -> anyhow::Result<()> {
std::fs::write(site_packages.join("a.py").as_std_path(), "class A: ...")?;
let changes = case.try_stop_watch(Duration::from_millis(100));
let changes = case.try_stop_watch(|_: &ChangeEvent| true, Duration::from_millis(100));
assert_eq!(changes, None);
assert_eq!(changes, Err(vec![]));
Ok(())
}
@@ -858,7 +906,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
"os: 3.0-",
)?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("VERSIONS"));
case.apply_changes(changes);
@@ -911,7 +959,7 @@ fn hard_links_in_workspace() -> anyhow::Result<()> {
// Write to the hard link target.
update_file(foo_path, "print('Version 2')").context("Failed to update foo.py")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -982,7 +1030,7 @@ fn hard_links_to_target_outside_workspace() -> anyhow::Result<()> {
// Write to the hard link target.
update_file(foo_path, "print('Version 2')").context("Failed to update foo.py")?;
let changes = case.stop_watch();
let changes = case.stop_watch(ChangeEvent::is_changed);
case.apply_changes(changes);
@@ -1021,7 +1069,7 @@ mod unix {
)
.with_context(|| "Failed to set file permissions.")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("foo.py"));
case.apply_changes(changes);
@@ -1119,7 +1167,7 @@ mod unix {
update_file(baz_workspace, "def baz(): print('Version 3')")
.context("Failed to update bar/baz.py")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("baz.py"));
case.apply_changes(changes);
@@ -1190,7 +1238,7 @@ mod unix {
update_file(&patched_bar_baz, "def baz(): print('Version 2')")
.context("Failed to update bar/baz.py")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("baz.py"));
case.apply_changes(changes);
@@ -1298,7 +1346,7 @@ mod unix {
update_file(&baz_original, "def baz(): print('Version 2')")
.context("Failed to update bar/baz.py")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("baz.py"));
case.apply_changes(changes);
@@ -1352,7 +1400,7 @@ fn nested_packages_delete_root() -> anyhow::Result<()> {
std::fs::remove_file(case.workspace_path("pyproject.toml").as_std_path())?;
let changes = case.stop_watch();
let changes = case.stop_watch(ChangeEvent::is_deleted);
case.apply_changes(changes);
@@ -1364,7 +1412,6 @@ fn nested_packages_delete_root() -> anyhow::Result<()> {
#[test]
fn added_package() -> anyhow::Result<()> {
let _ = setup_logging();
let mut case = setup([
(
"pyproject.toml",
@@ -1406,7 +1453,7 @@ fn added_package() -> anyhow::Result<()> {
)
.context("failed to write pyproject.toml for package b")?;
let changes = case.stop_watch();
let changes = case.stop_watch(event_for_file("pyproject.toml"));
case.apply_changes(changes);
@@ -1449,7 +1496,7 @@ fn removed_package() -> anyhow::Result<()> {
std::fs::remove_dir_all(case.workspace_path("packages/b").as_std_path())
.context("failed to remove package 'b'")?;
let changes = case.stop_watch();
let changes = case.stop_watch(ChangeEvent::is_deleted);
case.apply_changes(changes);

View File

@@ -49,6 +49,8 @@ anyhow = { workspace = true }
dir-test = { workspace = true }
insta = { workspace = true }
tempfile = { workspace = true }
quickcheck = { version = "1.0.3", default-features = false }
quickcheck_macros = { version = "1.0.0" }
[lints]
workspace = true

View File

@@ -0,0 +1,75 @@
# Any
## Annotation
`typing.Any` is a way to name the Any type.
```py
from typing import Any
x: Any = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
## Aliased to a different name
If you alias `typing.Any` to another name, we still recognize that as a spelling of the Any type.
```py
from typing import Any as RenamedAny
x: RenamedAny = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
## Shadowed class
If you define your own class named `Any`, using that in a type expression refers to your class, and
isn't a spelling of the Any type.
```py
class Any:
pass
x: Any
def f():
reveal_type(x) # revealed: Any
# This verifies that we're not accidentally seeing typing.Any, since str is assignable
# to that but not to our locally defined class.
y: Any = "not an Any" # error: [invalid-assignment]
```
## Subclass
The spec allows you to define subclasses of `Any`.
TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The
assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment
to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore
assignable to `int`.
```py
from typing import Any
class Subclass(Any):
pass
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
x: Subclass = 1 # error: [invalid-assignment]
# TODO: no diagnostic
y: int = Subclass() # error: [invalid-assignment]
def f() -> Subclass:
pass
reveal_type(f()) # revealed: Subclass
```

View File

@@ -0,0 +1,128 @@
# `LiteralString`
`LiteralString` represents a string that is either defined directly within the source code or is
made up of such components.
Parts of the testcases defined here were adapted from [the specification's examples][1].
## Usages
### Valid places
It can be used anywhere a type is accepted:
```py
from typing import LiteralString
x: LiteralString
def f():
reveal_type(x) # revealed: LiteralString
```
### Within `Literal`
`LiteralString` cannot be used within `Literal`:
```py
from typing import Literal, LiteralString
bad_union: Literal["hello", LiteralString] # error: [invalid-literal-parameter]
bad_nesting: Literal[LiteralString] # error: [invalid-literal-parameter]
```
### Parametrized
`LiteralString` cannot be parametrized.
```py
from typing import LiteralString
a: LiteralString[str] # error: [invalid-type-parameter]
b: LiteralString["foo"] # error: [invalid-type-parameter]
```
### As a base class
Subclassing `LiteralString` leads to a runtime error.
```py
from typing import LiteralString
class C(LiteralString): ... # error: [invalid-base]
```
## Inference
### Common operations
```py
foo: LiteralString = "foo"
reveal_type(foo) # revealed: Literal["foo"]
bar: LiteralString = "bar"
reveal_type(foo + bar) # revealed: Literal["foobar"]
baz: LiteralString = "baz"
baz += foo
reveal_type(baz) # revealed: Literal["bazfoo"]
qux = (foo, bar)
reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]]
# TODO: Infer "LiteralString"
reveal_type(foo.join(qux)) # revealed: @Todo(call todo)
template: LiteralString = "{}, {}"
reveal_type(template) # revealed: Literal["{}, {}"]
# TODO: Infer `LiteralString`
reveal_type(template.format(foo, bar)) # revealed: @Todo(call todo)
```
### Assignability
`Literal[""]` is assignable to `LiteralString`, and `LiteralString` is assignable to `str`, but not
vice versa.
```py
def coinflip() -> bool:
return True
foo_1: Literal["foo"] = "foo"
bar_1: LiteralString = foo_1 # fine
foo_2 = "foo" if coinflip() else "bar"
reveal_type(foo_2) # revealed: Literal["foo", "bar"]
bar_2: LiteralString = foo_2 # fine
foo_3: LiteralString = "foo" * 1_000_000_000
bar_3: str = foo_2 # fine
baz_1: str = str()
qux_1: LiteralString = baz_1 # error: [invalid-assignment]
baz_2: LiteralString = "baz" * 1_000_000_000
qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment]
baz_3 = "foo" if coinflip() else 1
reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1]
qux_3: LiteralString = baz_3 # error: [invalid-assignment]
```
### Narrowing
```py
lorem: LiteralString = "lorem" * 1_000_000_000
reveal_type(lorem) # revealed: LiteralString
if lorem == "ipsum":
reveal_type(lorem) # revealed: Literal["ipsum"]
reveal_type(lorem) # revealed: LiteralString
if "" < lorem == "ipsum":
reveal_type(lorem) # revealed: Literal["ipsum"]
```
[1]: https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring

View File

@@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
if True or (x := 1):
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Never
reveal_type(x) # revealed: Literal[1]
if True and (x := 1):
# TODO: infer that the second arm is always executed, do not raise a diagnostic

View File

@@ -0,0 +1,219 @@
# Length (`len()`)
## Literal and constructed iterables
### Strings and bytes literals
```py
reveal_type(len("no\rmal")) # revealed: Literal[6]
reveal_type(len(r"aw stri\ng")) # revealed: Literal[10]
reveal_type(len(r"conca\t" "ena\tion")) # revealed: Literal[14]
reveal_type(len(b"ytes lite" rb"al")) # revealed: Literal[11]
reveal_type(len("𝒰𝕹🄸©🕲𝕕ℇ")) # revealed: Literal[7]
reveal_type( # revealed: Literal[7]
len(
"""foo
bar"""
)
)
reveal_type( # revealed: Literal[9]
len(
r"""foo\r
bar"""
)
)
reveal_type( # revealed: Literal[7]
len(
b"""foo
bar"""
)
)
reveal_type( # revealed: Literal[9]
len(
rb"""foo\r
bar"""
)
)
```
### Tuples
```py
reveal_type(len(())) # revealed: Literal[0]
reveal_type(len((1,))) # revealed: Literal[1]
reveal_type(len((1, 2))) # revealed: Literal[2]
# TODO: Handle constructor calls
reveal_type(len(tuple())) # revealed: int
# TODO: Handle star unpacks; Should be: Literal[0]
reveal_type(len((*[],))) # revealed: Literal[1]
# TODO: Handle star unpacks; Should be: Literal[1]
reveal_type( # revealed: Literal[2]
len(
(
*[],
1,
)
)
)
# TODO: Handle star unpacks; Should be: Literal[2]
reveal_type(len((*[], 1, 2))) # revealed: Literal[3]
# TODO: Handle star unpacks; Should be: Literal[0]
reveal_type(len((*[], *{}))) # revealed: Literal[2]
```
### Lists, sets and dictionaries
```py
reveal_type(len([])) # revealed: int
reveal_type(len([1])) # revealed: int
reveal_type(len([1, 2])) # revealed: int
reveal_type(len([*{}, *dict()])) # revealed: int
reveal_type(len({})) # revealed: int
reveal_type(len({**{}})) # revealed: int
reveal_type(len({**{}, **{}})) # revealed: int
reveal_type(len({1})) # revealed: int
reveal_type(len({1, 2})) # revealed: int
reveal_type(len({*[], 2})) # revealed: int
reveal_type(len(list())) # revealed: int
reveal_type(len(set())) # revealed: int
reveal_type(len(dict())) # revealed: int
reveal_type(len(frozenset())) # revealed: int
```
## `__len__`
The returned value of `__len__` is implicitly and recursively converted to `int`.
### Literal integers
```py
from typing import Literal
class Zero:
def __len__(self) -> Literal[0]: ...
class ZeroOrOne:
def __len__(self) -> Literal[0, 1]: ...
class ZeroOrTrue:
def __len__(self) -> Literal[0, True]: ...
class OneOrFalse:
def __len__(self) -> Literal[1] | Literal[False]: ...
class OneOrFoo:
def __len__(self) -> Literal[1, "foo"]: ...
class ZeroOrStr:
def __len__(self) -> Literal[0] | str: ...
reveal_type(len(Zero())) # revealed: Literal[0]
reveal_type(len(ZeroOrOne())) # revealed: Literal[0, 1]
reveal_type(len(ZeroOrTrue())) # revealed: Literal[0, 1]
reveal_type(len(OneOrFalse())) # revealed: Literal[0, 1]
# TODO: Emit a diagnostic
reveal_type(len(OneOrFoo())) # revealed: int
# TODO: Emit a diagnostic
reveal_type(len(ZeroOrStr())) # revealed: int
```
### Literal booleans
```py
from typing import Literal
class LiteralTrue:
def __len__(self) -> Literal[True]: ...
class LiteralFalse:
def __len__(self) -> Literal[False]: ...
reveal_type(len(LiteralTrue())) # revealed: Literal[1]
reveal_type(len(LiteralFalse())) # revealed: Literal[0]
```
### Enums
```py
from enum import Enum, auto
from typing import Literal
class SomeEnum(Enum):
AUTO = auto()
INT = 2
STR = "4"
TUPLE = (8, "16")
INT_2 = 3_2
class Auto:
def __len__(self) -> Literal[SomeEnum.AUTO]: ...
class Int:
def __len__(self) -> Literal[SomeEnum.INT]: ...
class Str:
def __len__(self) -> Literal[SomeEnum.STR]: ...
class Tuple:
def __len__(self) -> Literal[SomeEnum.TUPLE]: ...
class IntUnion:
def __len__(self) -> Literal[SomeEnum.INT, SomeEnum.INT_2]: ...
reveal_type(len(Auto())) # revealed: int
reveal_type(len(Int())) # revealed: Literal[2]
reveal_type(len(Str())) # revealed: int
reveal_type(len(Tuple())) # revealed: int
reveal_type(len(IntUnion())) # revealed: Literal[2, 32]
```
### Negative integers
```py
from typing import Literal
class Negative:
def __len__(self) -> Literal[-1]: ...
# TODO: Emit a diagnostic
reveal_type(len(Negative())) # revealed: int
```
### Wrong signature
```py
from typing import Literal
class SecondOptionalArgument:
def __len__(self, v: int = 0) -> Literal[0]: ...
class SecondRequiredArgument:
def __len__(self, v: int) -> Literal[1]: ...
# TODO: Emit a diagnostic
reveal_type(len(SecondOptionalArgument())) # revealed: Literal[0]
# TODO: Emit a diagnostic
reveal_type(len(SecondRequiredArgument())) # revealed: Literal[1]
```
### No `__len__`
```py
class NoDunderLen:
pass
# TODO: Emit a diagnostic
reveal_type(len(NoDunderLen())) # revealed: int
```

View File

@@ -0,0 +1,64 @@
# Syntax errors
Test cases to ensure that red knot does not panic if there are syntax errors in the source code.
## Keyword as identifiers
When keywords are used as identifiers, the parser recovers from this syntax error by emitting an
error and including the text value of the keyword to create the `Identifier` node.
### Name expression
```py
# error: [invalid-syntax]
pass = 1
# error: [invalid-syntax]
# error: [invalid-syntax]
type pass = 1
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
def True(for):
# error: [invalid-syntax]
pass
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [unresolved-reference] "Name `pass` used when not defined"
for while in pass:
pass
# error: [invalid-syntax]
# error: [unresolved-reference] "Name `in` used when not defined"
while in:
pass
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [unresolved-reference] "Name `match` used when not defined"
match while:
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [invalid-syntax]
# error: [unresolved-reference] "Name `case` used when not defined"
case in:
# error: [invalid-syntax]
# error: [invalid-syntax]
pass
```
### Attribute expression
```py
# TODO: Check when support for attribute expressions is added
# error: [invalid-syntax]
# error: [unresolved-reference] "Name `foo` used when not defined"
for x in foo.pass:
pass
```

View File

@@ -0,0 +1,32 @@
## Narrowing for `bool(..)` checks
```py
def flag() -> bool: ...
x = 1 if flag() else None
# valid invocation, positive
reveal_type(x) # revealed: Literal[1] | None
if bool(x is not None):
reveal_type(x) # revealed: Literal[1]
# valid invocation, negative
reveal_type(x) # revealed: Literal[1] | None
if not bool(x is not None):
reveal_type(x) # revealed: None
# no args/narrowing
reveal_type(x) # revealed: Literal[1] | None
if not bool():
reveal_type(x) # revealed: Literal[1] | None
# invalid invocation, too many positional args
reveal_type(x) # revealed: Literal[1] | None
if bool(x is not None, 5): # TODO diagnostic
reveal_type(x) # revealed: Literal[1] | None
# invalid invocation, too many kwargs
reveal_type(x) # revealed: Literal[1] | None
if bool(x is not None, y=5): # TODO diagnostic
reveal_type(x) # revealed: Literal[1] | None
```

View File

@@ -1,303 +0,0 @@
# Statically-known branches
## Always false
### If
```py
x = 1
if False:
x = 2
reveal_type(x) # revealed: Literal[1]
```
### Else
```py
x = 1
if True:
pass
else:
x = 2
reveal_type(x) # revealed: Literal[1]
```
## Always true
### If
```py
x = 1
if True:
x = 2
reveal_type(x) # revealed: Literal[2]
```
### Else
```py
x = 1
if False:
pass
else:
x = 2
reveal_type(x) # revealed: Literal[2]
```
## Combination
```py
x = 1
if True:
x = 2
else:
x = 3
reveal_type(x) # revealed: Literal[2]
```
## Nested
```py path=nested_if_true_if_true.py
x = 1
if True:
if True:
x = 2
else:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[2]
```
```py path=nested_if_true_if_false.py
x = 1
if True:
if False:
x = 2
else:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[3]
```
```py path=nested_if_true_if_bool.py
def flag() -> bool: ...
x = 1
if True:
if flag():
x = 2
else:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[2, 3]
```
```py path=nested_if_bool_if_true.py
def flag() -> bool: ...
x = 1
if flag():
if True:
x = 2
else:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[2, 4]
```
```py path=nested_else_if_true.py
x = 1
if False:
x = 2
else:
if True:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[3]
```
```py path=nested_else_if_false.py
x = 1
if False:
x = 2
else:
if False:
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[4]
```
```py path=nested_else_if_bool.py
def flag() -> bool: ...
x = 1
if False:
x = 2
else:
if flag():
x = 3
else:
x = 4
reveal_type(x) # revealed: Literal[3, 4]
```
## If-expressions
### Always true
```py
x = 1 if True else 2
reveal_type(x) # revealed: Literal[1]
```
### Always false
```py
x = 1 if False else 2
reveal_type(x) # revealed: Literal[2]
```
## Boolean expressions
### Always true
```py
(x := 1) == 1 or (x := 2)
reveal_type(x) # revealed: Literal[1]
```
### Always false
```py
(x := 1) == 0 or (x := 2)
reveal_type(x) # revealed: Literal[2]
```
## Conditional declarations
```py path=if_false.py
x: str
if False:
x: int
def f() -> None:
reveal_type(x) # revealed: str
```
```py path=if_true_else.py
x: str
if True:
pass
else:
x: int
def f() -> None:
reveal_type(x) # revealed: str
```
```py path=if_true.py
x: str
if True:
x: int
def f() -> None:
reveal_type(x) # revealed: int
```
```py path=if_false_else.py
x: str
if False:
pass
else:
x: int
def f() -> None:
reveal_type(x) # revealed: int
```
```py path=if_bool.py
def flag() -> bool: ...
x: str
if flag():
x: int
def f() -> None:
reveal_type(x) # revealed: str | int
```
## Conditionally defined functions
```py
def f() -> int: ...
def g() -> int: ...
if True:
def f() -> str: ...
else:
def g() -> str: ...
reveal_type(f()) # revealed: str
reveal_type(g()) # revealed: int
```
## Conditionally defined class attributes
```py
class C:
if True:
x: int = 1
else:
x: str = "a"
reveal_type(C.x) # revealed: int
```
## TODO
- declarations vs bindings => NoDefault: NoDefaultType
- conditional imports
- conditional class definitions
- compare with tests in if.md=>Statically known branches
- boundness
- TODO in `issubclass.md`

View File

@@ -49,14 +49,14 @@ sometimes not:
```py
import sys
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: Literal[True]
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: Literal[True]
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: bool
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: bool
# TODO: While this won't fail at runtime, the user has probably made a mistake
# if they're comparing a tuple of length >5 with `sys.version_info`
# (`sys.version_info` is a tuple of length 5). It might be worth
# emitting a lint diagnostic of some kind warning them about the probable error?
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: Literal[True]
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: bool
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: Literal[False]
```
@@ -102,8 +102,8 @@ The fields of `sys.version_info` can be accessed by name:
import sys
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 14) # revealed: Literal[False]
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
```
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
@@ -125,14 +125,14 @@ The fields of `sys.version_info` can be accessed by index or by slice:
import sys
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
reveal_type(sys.version_info[1] > 13) # revealed: Literal[False]
reveal_type(sys.version_info[1] > 9) # revealed: Literal[False]
# revealed: tuple[Literal[3], Literal[13], int, Literal["alpha", "beta", "candidate", "final"], int]
# revealed: tuple[Literal[3], Literal[9], int, Literal["alpha", "beta", "candidate", "final"], int]
reveal_type(sys.version_info[:5])
reveal_type(sys.version_info[:2] >= (3, 13)) # revealed: Literal[True]
reveal_type(sys.version_info[0:2] >= (3, 14)) # revealed: Literal[False]
reveal_type(sys.version_info[:3] >= (3, 14, 1)) # revealed: Literal[False]
reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
reveal_type(sys.version_info[3] == "final") # revealed: bool
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
```

View File

@@ -267,3 +267,42 @@ reveal_type(b) # revealed: LiteralString
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(c) # revealed: @Todo(starred unpacking)
```
### Unicode
```py
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "é"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: Unknown
```
### Unicode escape (1)
```py
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "\u9E6C"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: Unknown
```
### Unicode escape (2)
```py
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "\U0010FFFF"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: Unknown
```
### Surrogates
```py
(a, b) = "\uD800\uDFFF"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
```

View File

@@ -11,8 +11,13 @@ pub trait Db: SourceDb + Upcast<dyn SourceDb> {
pub(crate) mod tests {
use std::sync::Arc;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::ProgramSettings;
use anyhow::Context;
use ruff_db::files::{File, Files};
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem};
use ruff_db::vendored::VendoredFileSystem;
use ruff_db::{Db as SourceDb, Upcast};
@@ -108,4 +113,66 @@ pub(crate) mod tests {
events.push(event);
}
}
pub(crate) struct TestDbBuilder<'a> {
/// Target Python version
python_version: PythonVersion,
/// Path to a custom typeshed directory
custom_typeshed: Option<SystemPathBuf>,
/// Path and content pairs for files that should be present
files: Vec<(&'a str, &'a str)>,
}
impl<'a> TestDbBuilder<'a> {
pub(crate) fn new() -> Self {
Self {
python_version: PythonVersion::default(),
custom_typeshed: None,
files: vec![],
}
}
pub(crate) fn with_python_version(mut self, version: PythonVersion) -> Self {
self.python_version = version;
self
}
pub(crate) fn with_custom_typeshed(mut self, path: &str) -> Self {
self.custom_typeshed = Some(SystemPathBuf::from(path));
self
}
pub(crate) fn with_file(mut self, path: &'a str, content: &'a str) -> Self {
self.files.push((path, content));
self
}
pub(crate) fn build(self) -> anyhow::Result<TestDb> {
let mut db = TestDb::new();
let src_root = SystemPathBuf::from("/src");
db.memory_file_system().create_directory_all(&src_root)?;
db.write_files(self.files)
.context("Failed to write test files")?;
let mut search_paths = SearchPathSettings::new(src_root);
search_paths.custom_typeshed = self.custom_typeshed;
Program::from_settings(
&db,
&ProgramSettings {
target_version: self.python_version,
search_paths,
},
)
.context("Failed to configure Program settings")?;
Ok(db)
}
}
pub(crate) fn setup_db() -> TestDb {
TestDbBuilder::new().build().expect("valid TestDb setup")
}
}

View File

@@ -416,7 +416,7 @@ impl<'db> Iterator for SearchPathIterator<'db> {
}
}
impl<'db> FusedIterator for SearchPathIterator<'db> {}
impl FusedIterator for SearchPathIterator<'_> {}
/// Represents a single `.pth` file in a `site-packages` directory.
/// One or more lines in a `.pth` file may be a (relative or absolute)

View File

@@ -39,7 +39,7 @@ impl PythonVersion {
impl Default for PythonVersion {
fn default() -> Self {
Self::PY313 // TODO: temporarily changed to 3.13 to activate all sys.version_info branches
Self::PY39
}
}

View File

@@ -1229,32 +1229,4 @@ match 1:
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
}
#[test]
#[ignore]
fn if_statement() {
let TestCase { db, file } = test_case(
"
x = False
if True:
x: bool
",
);
let index = semantic_index(&db, file);
// let global_table = index.symbol_table(FileScopeId::global());
let use_def = index.use_def_map(FileScopeId::global());
// use_def
use_def.print(&db);
assert!(false);
// let binding = use_def
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
// .expect("Expected with item definition for {name}");
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
}
}

View File

@@ -23,7 +23,7 @@ use crate::semantic_index::symbol::{
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
SymbolTableBuilder,
};
use crate::semantic_index::use_def::{ActiveConstraintsSnapshot, FlowSnapshot, UseDefMapBuilder};
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
use crate::semantic_index::SemanticIndex;
use crate::unpack::Unpack;
use crate::Db;
@@ -200,20 +200,12 @@ impl<'db> SemanticIndexBuilder<'db> {
self.current_use_def_map().snapshot()
}
fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
self.current_use_def_map().constraints_snapshot()
}
fn flow_restore(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
fn flow_restore(&mut self, state: FlowSnapshot) {
self.current_use_def_map_mut().restore(state);
self.current_use_def_map_mut()
.restore_constraints(active_constraints);
}
fn flow_merge(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
fn flow_merge(&mut self, state: FlowSnapshot) {
self.current_use_def_map_mut().merge(state);
self.current_use_def_map_mut()
.restore_constraints(active_constraints);
}
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
@@ -773,7 +765,6 @@ where
ast::Stmt::If(node) => {
self.visit_expr(&node.test);
let pre_if = self.flow_snapshot();
let pre_if_constraints = self.constraints_snapshot();
let constraint = self.record_expression_constraint(&node.test);
let mut constraints = vec![constraint];
self.visit_body(&node.body);
@@ -799,7 +790,7 @@ where
post_clauses.push(self.flow_snapshot());
// we can only take an elif/else branch if none of the previous ones were
// taken, so the block entry state is always `pre_if`
self.flow_restore(pre_if.clone(), pre_if_constraints.clone());
self.flow_restore(pre_if.clone());
for constraint in &constraints {
self.record_negated_constraint(*constraint);
}
@@ -810,7 +801,7 @@ where
self.visit_body(clause_body);
}
for post_clause_state in post_clauses {
self.flow_merge(post_clause_state, pre_if_constraints.clone());
self.flow_merge(post_clause_state);
}
}
ast::Stmt::While(ast::StmtWhile {
@@ -822,7 +813,6 @@ where
self.visit_expr(test);
let pre_loop = self.flow_snapshot();
let pre_loop_constraints = self.constraints_snapshot();
// Save aside any break states from an outer loop
let saved_break_states = std::mem::take(&mut self.loop_break_states);
@@ -841,13 +831,13 @@ where
// We may execute the `else` clause without ever executing the body, so merge in
// the pre-loop state before visiting `else`.
self.flow_merge(pre_loop, pre_loop_constraints.clone());
self.flow_merge(pre_loop);
self.visit_body(orelse);
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
// states after visiting `else`.
for break_state in break_states {
self.flow_merge(break_state, pre_loop_constraints.clone()); // TODO?
self.flow_merge(break_state);
}
}
ast::Stmt::With(ast::StmtWith {
@@ -890,7 +880,6 @@ where
self.visit_expr(iter);
let pre_loop = self.flow_snapshot();
let pre_loop_constraints = self.constraints_snapshot();
let saved_break_states = std::mem::take(&mut self.loop_break_states);
debug_assert_eq!(&self.current_assignments, &[]);
@@ -911,13 +900,13 @@ where
// We may execute the `else` clause without ever executing the body, so merge in
// the pre-loop state before visiting `else`.
self.flow_merge(pre_loop, pre_loop_constraints.clone());
self.flow_merge(pre_loop);
self.visit_body(orelse);
// Breaking out of a `for` loop bypasses the `else` clause, so merge in the break
// states after visiting `else`.
for break_state in break_states {
self.flow_merge(break_state, pre_loop_constraints.clone());
self.flow_merge(break_state);
}
}
ast::Stmt::Match(ast::StmtMatch {
@@ -929,7 +918,6 @@ where
self.visit_expr(subject);
let after_subject = self.flow_snapshot();
let after_subject_cs = self.constraints_snapshot();
let Some((first, remaining)) = cases.split_first() else {
return;
};
@@ -939,18 +927,18 @@ where
let mut post_case_snapshots = vec![];
for case in remaining {
post_case_snapshots.push(self.flow_snapshot());
self.flow_restore(after_subject.clone(), after_subject_cs.clone());
self.flow_restore(after_subject.clone());
self.add_pattern_constraint(subject, &case.pattern);
self.visit_match_case(case);
}
for post_clause_state in post_case_snapshots {
self.flow_merge(post_clause_state, after_subject_cs.clone());
self.flow_merge(post_clause_state);
}
if !cases
.last()
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
{
self.flow_merge(after_subject, after_subject_cs.clone());
self.flow_merge(after_subject);
}
}
ast::Stmt::Try(ast::StmtTry {
@@ -968,7 +956,6 @@ where
// We will merge this state with all of the intermediate
// states during the `try` block before visiting those suites.
let pre_try_block_state = self.flow_snapshot();
let pre_try_block_constraints = self.constraints_snapshot();
self.try_node_context_stack_manager.push_context();
@@ -989,17 +976,14 @@ where
// as there necessarily must have been 0 `except` blocks executed
// if we hit the `else` block.
let post_try_block_state = self.flow_snapshot();
let post_try_block_constraints = self.constraints_snapshot();
// Prepare for visiting the `except` block(s)
self.flow_restore(pre_try_block_state, pre_try_block_constraints.clone());
self.flow_restore(pre_try_block_state);
for state in try_block_snapshots {
self.flow_merge(state, pre_try_block_constraints.clone());
// TODO?
self.flow_merge(state);
}
let pre_except_state = self.flow_snapshot();
let pre_except_constraints = self.constraints_snapshot();
let num_handlers = handlers.len();
for (i, except_handler) in handlers.iter().enumerate() {
@@ -1038,22 +1022,19 @@ where
// as we'll immediately call `self.flow_restore()` to a different state
// as soon as this loop over the handlers terminates.
if i < (num_handlers - 1) {
self.flow_restore(
pre_except_state.clone(),
pre_except_constraints.clone(),
);
self.flow_restore(pre_except_state.clone());
}
}
// If we get to the `else` block, we know that 0 of the `except` blocks can have been executed,
// and the entire `try` block must have been executed:
self.flow_restore(post_try_block_state, post_try_block_constraints);
self.flow_restore(post_try_block_state);
}
self.visit_body(orelse);
for post_except_state in post_except_states {
self.flow_merge(post_except_state, pre_try_block_constraints.clone());
self.flow_merge(post_except_state);
}
// TODO: there's lots of complexity here that isn't yet handled by our model.
@@ -1210,17 +1191,19 @@ where
ast::Expr::If(ast::ExprIf {
body, test, orelse, ..
}) => {
// TODO detect statically known truthy or falsy test (via type inference, not naive
// AST inspection, so we can't simplify here, need to record test expression for
// later checking)
self.visit_expr(test);
let pre_if = self.flow_snapshot();
let pre_if_constraints = self.constraints_snapshot();
let constraint = self.record_expression_constraint(test);
self.visit_expr(body);
let post_body = self.flow_snapshot();
self.flow_restore(pre_if, pre_if_constraints.clone());
self.flow_restore(pre_if);
self.record_negated_constraint(constraint);
self.visit_expr(orelse);
self.flow_merge(post_body, pre_if_constraints);
self.flow_merge(post_body);
}
ast::Expr::ListComp(
list_comprehension @ ast::ExprListComp {
@@ -1281,7 +1264,7 @@ where
// AST inspection, so we can't simplify here, need to record test expression for
// later checking)
let mut snapshots = vec![];
let pre_op_constraints = self.constraints_snapshot();
for (index, value) in values.iter().enumerate() {
self.visit_expr(value);
// In the last value we don't need to take a snapshot nor add a constraint
@@ -1296,7 +1279,7 @@ where
}
}
for snapshot in snapshots {
self.flow_merge(snapshot, pre_op_constraints.clone());
self.flow_merge(snapshot);
}
}
_ => {

View File

@@ -221,8 +221,6 @@
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
//! visits a `StmtIf` node.
use std::collections::HashSet;
use self::symbol_state::{
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
@@ -270,109 +268,6 @@ pub(crate) struct UseDefMap<'db> {
}
impl<'db> UseDefMap<'db> {
#[cfg(test)]
pub(crate) fn print(&self, db: &dyn crate::db::Db) {
use crate::semantic_index::constraint::ConstraintNode;
println!("all_definitions:");
println!("================");
for (id, d) in self.all_definitions.iter_enumerated() {
println!(
"{:?}: {:?} {:?} {:?}",
id,
d.category(db),
d.scope(db),
d.symbol(db),
);
println!(" {:?}", d.kind(db));
println!();
}
println!("all_constraints:");
println!("================");
for (id, c) in self.all_constraints.iter_enumerated() {
println!("{:?}: {:?}", id, c.node);
if let ConstraintNode::Expression(e) = c.node {
println!(" {:?}", e.node_ref(db));
}
}
println!();
println!("bindings_by_use:");
println!("================");
for (id, bindings) in self.bindings_by_use.iter_enumerated() {
println!("{:?}:", id);
for binding in bindings.iter() {
let definition = self.all_definitions[binding.definition];
let mut constraint_ids = binding.constraint_ids.peekable();
let mut active_constraint_ids =
binding.constraints_active_at_binding_ids.peekable();
println!(" * {:?}", definition);
if constraint_ids.peek().is_some() {
println!(" Constraints:");
for constraint_id in constraint_ids {
println!(" {:?}", self.all_constraints[constraint_id]);
}
} else {
println!(" No constraints");
}
println!();
if active_constraint_ids.peek().is_some() {
println!(" Active constraints at binding:");
for constraint_id in active_constraint_ids {
println!(" {:?}", self.all_constraints[constraint_id]);
}
} else {
println!(" No active constraints at binding");
}
}
}
println!();
println!("public_symbols:");
println!("================");
for (id, symbol) in self.public_symbols.iter_enumerated() {
println!("{:?}:", id);
println!(" * Bindings:");
for binding in symbol.bindings().iter() {
let definition = self.all_definitions[binding.definition];
let mut constraint_ids = binding.constraint_ids.peekable();
println!(" {:?}", definition);
if constraint_ids.peek().is_some() {
println!(" Constraints:");
for constraint_id in constraint_ids {
println!(" {:?}", self.all_constraints[constraint_id]);
}
} else {
println!(" No constraints");
}
}
println!(" * Declarations:");
for (declaration, _) in symbol.declarations().iter() {
let definition = self.all_definitions[declaration];
println!(" {:?}", definition);
}
println!();
}
println!();
println!();
}
pub(crate) fn bindings_at_use(
&self,
use_id: ScopedUseId,
@@ -457,7 +352,6 @@ impl<'db> UseDefMap<'db> {
) -> DeclarationsIterator<'a, 'db> {
DeclarationsIterator {
all_definitions: &self.all_definitions,
all_constraints: &self.all_constraints,
inner: declarations.iter(),
may_be_undeclared: declarations.may_be_undeclared(),
}
@@ -471,7 +365,7 @@ enum SymbolDefinitions {
Declarations(SymbolDeclarations),
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
@@ -490,10 +384,6 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
all_constraints: self.all_constraints,
constraint_ids: def_id_with_constraints.constraint_ids,
},
constraints_active_at_binding: ConstraintsIterator {
all_constraints: self.all_constraints,
constraint_ids: def_id_with_constraints.constraints_active_at_binding_ids,
},
})
}
}
@@ -503,16 +393,14 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
pub(crate) struct BindingWithConstraints<'map, 'db> {
pub(crate) binding: Definition<'db>,
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
pub(crate) constraints_active_at_binding: ConstraintsIterator<'map, 'db>,
}
#[derive(Debug, Clone)]
pub(crate) struct ConstraintsIterator<'map, 'db> {
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
constraint_ids: ConstraintIdIterator<'map>,
}
impl<'map, 'db> Iterator for ConstraintsIterator<'map, 'db> {
impl<'db> Iterator for ConstraintsIterator<'_, 'db> {
type Item = Constraint<'db>;
fn next(&mut self) -> Option<Self::Item> {
@@ -526,7 +414,6 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
pub(crate) struct DeclarationsIterator<'map, 'db> {
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
inner: DeclarationIdIterator<'map>,
may_be_undeclared: bool,
}
@@ -537,19 +424,11 @@ impl DeclarationsIterator<'_, '_> {
}
}
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
type Item = (Definition<'db>, ConstraintsIterator<'map, 'db>);
impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
type Item = Definition<'db>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|(def_id, constraints)| {
(
self.all_definitions[def_id],
ConstraintsIterator {
all_constraints: self.all_constraints,
constraint_ids: constraints,
},
)
})
self.inner.next().map(|def_id| self.all_definitions[def_id])
}
}
@@ -561,9 +440,6 @@ pub(super) struct FlowSnapshot {
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
}
#[derive(Clone, Debug)]
pub(super) struct ActiveConstraintsSnapshot(HashSet<ScopedConstraintId>);
#[derive(Debug, Default)]
pub(super) struct UseDefMapBuilder<'db> {
/// Append-only array of [`Definition`].
@@ -572,8 +448,6 @@ pub(super) struct UseDefMapBuilder<'db> {
/// Append-only array of [`Constraint`].
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
active_constraints: HashSet<ScopedConstraintId>,
/// Live bindings at each so-far-recorded use.
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
@@ -597,7 +471,7 @@ impl<'db> UseDefMapBuilder<'db> {
binding,
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
);
symbol_state.record_binding(def_id, &self.active_constraints);
symbol_state.record_binding(def_id);
}
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
@@ -605,7 +479,6 @@ impl<'db> UseDefMapBuilder<'db> {
for state in &mut self.symbol_states {
state.record_constraint(constraint_id);
}
self.active_constraints.insert(constraint_id);
}
pub(super) fn record_declaration(
@@ -619,7 +492,7 @@ impl<'db> UseDefMapBuilder<'db> {
declaration,
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
);
symbol_state.record_declaration(def_id, &self.active_constraints);
symbol_state.record_declaration(def_id);
}
pub(super) fn record_declaration_and_binding(
@@ -630,8 +503,8 @@ impl<'db> UseDefMapBuilder<'db> {
// We don't need to store anything in self.definitions_by_definition.
let def_id = self.all_definitions.push(definition);
let symbol_state = &mut self.symbol_states[symbol];
symbol_state.record_declaration(def_id, &self.active_constraints);
symbol_state.record_binding(def_id, &self.active_constraints);
symbol_state.record_declaration(def_id);
symbol_state.record_binding(def_id);
}
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
@@ -650,10 +523,6 @@ impl<'db> UseDefMapBuilder<'db> {
}
}
pub(super) fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
ActiveConstraintsSnapshot(self.active_constraints.clone())
}
/// Restore the current builder symbols state to the given snapshot.
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
@@ -672,10 +541,6 @@ impl<'db> UseDefMapBuilder<'db> {
.resize(num_symbols, SymbolState::undefined());
}
pub(super) fn restore_constraints(&mut self, snapshot: ActiveConstraintsSnapshot) {
self.active_constraints = snapshot.0;
}
/// Merge the given snapshot into the current state, reflecting that we might have taken either
/// path to get here. The new state for each symbol should include definitions from both the
/// prior state and the snapshot.

View File

@@ -122,7 +122,7 @@ impl<const B: usize> BitSet<B> {
}
/// Iterator over values in a [`BitSet`].
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(super) struct BitSetIterator<'a, const B: usize> {
/// The blocks we are iterating over.
blocks: &'a [u64],

View File

@@ -43,8 +43,6 @@
//!
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
//! similar to tracking live bindings.
use std::collections::HashSet;
use super::bitset::{BitSet, BitSetIterator};
use ruff_index::newtype_index;
use smallvec::SmallVec;
@@ -89,8 +87,6 @@ pub(super) struct SymbolDeclarations {
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
live_declarations: Declarations,
constraints_active_at_declaration: Constraints, // TODO: rename to constraints_active_at_declaration
/// Could the symbol be un-declared at this point?
may_be_undeclared: bool,
}
@@ -99,27 +95,14 @@ impl SymbolDeclarations {
fn undeclared() -> Self {
Self {
live_declarations: Declarations::default(),
constraints_active_at_declaration: Constraints::default(),
may_be_undeclared: true,
}
}
/// Record a newly-encountered declaration for this symbol.
fn record_declaration(
&mut self,
declaration_id: ScopedDefinitionId,
active_constraints: &HashSet<ScopedConstraintId>,
) {
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
self.live_declarations = Declarations::with(declaration_id.into());
self.may_be_undeclared = false;
// TODO: unify code with below
self.constraints_active_at_declaration = Constraints::with_capacity(1);
self.constraints_active_at_declaration
.push(BitSet::default());
for active_constraint_id in active_constraints {
self.constraints_active_at_declaration[0].insert(active_constraint_id.as_u32());
}
}
/// Add undeclared as a possibility for this symbol.
@@ -131,7 +114,6 @@ impl SymbolDeclarations {
pub(super) fn iter(&self) -> DeclarationIdIterator {
DeclarationIdIterator {
inner: self.live_declarations.iter(),
constraints_active_at_binding: self.constraints_active_at_declaration.iter(),
}
}
@@ -156,8 +138,6 @@ pub(super) struct SymbolBindings {
/// binding in `live_bindings`.
constraints: Constraints,
constraints_active_at_binding: Constraints,
/// Could the symbol be unbound at this point?
may_be_unbound: bool,
}
@@ -167,7 +147,6 @@ impl SymbolBindings {
Self {
live_bindings: Bindings::default(),
constraints: Constraints::default(),
constraints_active_at_binding: Constraints::default(),
may_be_unbound: true,
}
}
@@ -178,21 +157,12 @@ impl SymbolBindings {
}
/// Record a newly-encountered binding for this symbol.
pub(super) fn record_binding(
&mut self,
binding_id: ScopedDefinitionId,
active_constraints: &HashSet<ScopedConstraintId>,
) {
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
// The new binding replaces all previous live bindings in this path, and has no
// constraints.
self.live_bindings = Bindings::with(binding_id.into());
self.constraints = Constraints::with_capacity(1);
self.constraints.push(BitSet::default());
self.constraints_active_at_binding = Constraints::with_capacity(1);
self.constraints_active_at_binding.push(BitSet::default());
for active_constraint_id in active_constraints {
self.constraints_active_at_binding[0].insert(active_constraint_id.as_u32());
}
self.may_be_unbound = false;
}
@@ -208,7 +178,6 @@ impl SymbolBindings {
BindingIdWithConstraintsIterator {
definitions: self.live_bindings.iter(),
constraints: self.constraints.iter(),
constraints_active_at_binding: self.constraints_active_at_binding.iter(),
}
}
@@ -238,12 +207,8 @@ impl SymbolState {
}
/// Record a newly-encountered binding for this symbol.
pub(super) fn record_binding(
&mut self,
binding_id: ScopedDefinitionId,
active_constraints: &HashSet<ScopedConstraintId>,
) {
self.bindings.record_binding(binding_id, active_constraints);
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
self.bindings.record_binding(binding_id);
}
/// Add given constraint to all live bindings.
@@ -257,13 +222,8 @@ impl SymbolState {
}
/// Record a newly-encountered declaration of this symbol.
pub(super) fn record_declaration(
&mut self,
declaration_id: ScopedDefinitionId,
active_constraints: &HashSet<ScopedConstraintId>,
) {
self.declarations
.record_declaration(declaration_id, active_constraints);
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
self.declarations.record_declaration(declaration_id);
}
/// Merge another [`SymbolState`] into this one.
@@ -272,93 +232,24 @@ impl SymbolState {
bindings: SymbolBindings {
live_bindings: Bindings::default(),
constraints: Constraints::default(),
constraints_active_at_binding: Constraints::default(), // TODO
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
},
declarations: SymbolDeclarations {
live_declarations: self.declarations.live_declarations.clone(),
constraints_active_at_declaration: Constraints::default(), // TODO
may_be_undeclared: self.declarations.may_be_undeclared
|| b.declarations.may_be_undeclared,
},
};
// let mut constraints_active_at_binding = BitSet::default();
// for active_constraint_id in active_constraints.0 {
// constraints_active_at_binding.insert(active_constraint_id.as_u32());
// }
std::mem::swap(&mut a, self);
// self.declarations
// .live_declarations
// .union(&b.declarations.live_declarations);
let mut a_decls_iter = a.declarations.live_declarations.iter();
let mut b_decls_iter = b.declarations.live_declarations.iter();
let mut a_constraints_active_at_declaration_iter =
a.declarations.constraints_active_at_declaration.into_iter();
let mut b_constraints_active_at_declaration_iter =
b.declarations.constraints_active_at_declaration.into_iter();
let mut opt_a_decl: Option<u32> = a_decls_iter.next();
let mut opt_b_decl: Option<u32> = b_decls_iter.next();
let push = |decl,
constraints_active_at_declaration_iter: &mut ConstraintsIntoIterator,
merged: &mut Self| {
merged.declarations.live_declarations.insert(decl);
let constraints_active_at_binding = constraints_active_at_declaration_iter
.next()
.expect("declarations and constraints_active_at_binding length mismatch");
merged
.declarations
.constraints_active_at_declaration
.push(constraints_active_at_binding);
};
loop {
match (opt_a_decl, opt_b_decl) {
(Some(a_decl), Some(b_decl)) => match a_decl.cmp(&b_decl) {
std::cmp::Ordering::Less => {
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
opt_a_decl = a_decls_iter.next();
}
std::cmp::Ordering::Greater => {
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
opt_b_decl = b_decls_iter.next();
}
std::cmp::Ordering::Equal => {
push(a_decl, &mut b_constraints_active_at_declaration_iter, self);
self.declarations
.constraints_active_at_declaration
.last_mut()
.unwrap()
.intersect(&a_constraints_active_at_declaration_iter.next().unwrap());
opt_a_decl = a_decls_iter.next();
opt_b_decl = b_decls_iter.next();
}
},
(Some(a_decl), None) => {
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
opt_a_decl = a_decls_iter.next();
}
(None, Some(b_decl)) => {
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
opt_b_decl = b_decls_iter.next();
}
(None, None) => break,
}
}
self.declarations
.live_declarations
.union(&b.declarations.live_declarations);
let mut a_defs_iter = a.bindings.live_bindings.iter();
let mut b_defs_iter = b.bindings.live_bindings.iter();
let mut a_constraints_iter = a.bindings.constraints.into_iter();
let mut b_constraints_iter = b.bindings.constraints.into_iter();
let mut a_constraints_active_at_binding_iter =
a.bindings.constraints_active_at_binding.into_iter();
let mut b_constraints_active_at_binding_iter =
b.bindings.constraints_active_at_binding.into_iter();
let mut opt_a_def: Option<u32> = a_defs_iter.next();
let mut opt_b_def: Option<u32> = b_defs_iter.next();
@@ -370,10 +261,7 @@ impl SymbolState {
// path is irrelevant.
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
let push = |def,
constraints_iter: &mut ConstraintsIntoIterator,
constraints_active_at_binding_iter: &mut ConstraintsIntoIterator,
merged: &mut Self| {
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
merged.bindings.live_bindings.insert(def);
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
@@ -383,14 +271,7 @@ impl SymbolState {
let constraints = constraints_iter
.next()
.expect("definitions and constraints length mismatch");
let constraints_active_at_binding = constraints_active_at_binding_iter
.next()
.expect("definitions and constraints_active_at_binding length mismatch");
merged.bindings.constraints.push(constraints);
merged
.bindings
.constraints_active_at_binding
.push(constraints_active_at_binding);
};
loop {
@@ -398,32 +279,17 @@ impl SymbolState {
(Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) {
std::cmp::Ordering::Less => {
// Next definition ID is only in `a`, push it to `self` and advance `a`.
push(
a_def,
&mut a_constraints_iter,
&mut a_constraints_active_at_binding_iter,
self,
);
push(a_def, &mut a_constraints_iter, self);
opt_a_def = a_defs_iter.next();
}
std::cmp::Ordering::Greater => {
// Next definition ID is only in `b`, push it to `self` and advance `b`.
push(
b_def,
&mut b_constraints_iter,
&mut b_constraints_active_at_binding_iter,
self,
);
push(b_def, &mut b_constraints_iter, self);
opt_b_def = b_defs_iter.next();
}
std::cmp::Ordering::Equal => {
// Next definition is in both; push to `self` and intersect constraints.
push(
a_def,
&mut b_constraints_iter,
&mut b_constraints_active_at_binding_iter,
self,
);
push(a_def, &mut b_constraints_iter, self);
// SAFETY: we only ever create SymbolState with either no definitions and
// no constraint bitsets (`::unbound`) or one definition and one constraint
// bitset (`::with`), and `::merge` always pushes one definition and one
@@ -432,11 +298,6 @@ impl SymbolState {
let a_constraints = a_constraints_iter
.next()
.expect("definitions and constraints length mismatch");
// let _a_constraints_active_at_binding =
// a_constraints_active_at_binding_iter.next().expect(
// "definitions and constraints_active_at_binding length mismatch",
// ); // TODO: perform check that we see the same constraints in both paths
// If the same definition is visible through both paths, any constraint
// that applies on only one path is irrelevant to the resulting type from
// unioning the two paths, so we intersect the constraints.
@@ -445,29 +306,18 @@ impl SymbolState {
.last_mut()
.unwrap()
.intersect(&a_constraints);
opt_a_def = a_defs_iter.next();
opt_b_def = b_defs_iter.next();
}
},
(Some(a_def), None) => {
// We've exhausted `b`, just push the def from `a` and move on to the next.
push(
a_def,
&mut a_constraints_iter,
&mut a_constraints_active_at_binding_iter,
self,
);
push(a_def, &mut a_constraints_iter, self);
opt_a_def = a_defs_iter.next();
}
(None, Some(b_def)) => {
// We've exhausted `a`, just push the def from `b` and move on to the next.
push(
b_def,
&mut b_constraints_iter,
&mut b_constraints_active_at_binding_iter,
self,
);
push(b_def, &mut b_constraints_iter, self);
opt_b_def = b_defs_iter.next();
}
(None, None) => break,
@@ -503,37 +353,26 @@ impl Default for SymbolState {
pub(super) struct BindingIdWithConstraints<'a> {
pub(super) definition: ScopedDefinitionId,
pub(super) constraint_ids: ConstraintIdIterator<'a>,
pub(super) constraints_active_at_binding_ids: ConstraintIdIterator<'a>,
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(super) struct BindingIdWithConstraintsIterator<'a> {
definitions: BindingsIterator<'a>,
constraints: ConstraintsIterator<'a>,
constraints_active_at_binding: ConstraintsIterator<'a>,
}
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
type Item = BindingIdWithConstraints<'a>;
fn next(&mut self) -> Option<Self::Item> {
match (
self.definitions.next(),
self.constraints.next(),
self.constraints_active_at_binding.next(),
) {
(None, None, None) => None,
(Some(def), Some(constraints), Some(constraints_active_at_binding)) => {
Some(BindingIdWithConstraints {
definition: ScopedDefinitionId::from_u32(def),
constraint_ids: ConstraintIdIterator {
wrapped: constraints.iter(),
},
constraints_active_at_binding_ids: ConstraintIdIterator {
wrapped: constraints_active_at_binding.iter(),
},
})
}
match (self.definitions.next(), self.constraints.next()) {
(None, None) => None,
(Some(def), Some(constraints)) => Some(BindingIdWithConstraints {
definition: ScopedDefinitionId::from_u32(def),
constraint_ids: ConstraintIdIterator {
wrapped: constraints.iter(),
},
}),
// SAFETY: see above.
_ => unreachable!("definitions and constraints length mismatch"),
}
@@ -542,7 +381,7 @@ impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(super) struct ConstraintIdIterator<'a> {
wrapped: BitSetIterator<'a, INLINE_CONSTRAINT_BLOCKS>,
}
@@ -560,25 +399,13 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
#[derive(Debug)]
pub(super) struct DeclarationIdIterator<'a> {
inner: DeclarationsIterator<'a>,
constraints_active_at_binding: ConstraintsIterator<'a>,
}
impl<'a> Iterator for DeclarationIdIterator<'a> {
type Item = (ScopedDefinitionId, ConstraintIdIterator<'a>);
impl Iterator for DeclarationIdIterator<'_> {
type Item = ScopedDefinitionId;
fn next(&mut self) -> Option<Self::Item> {
// self.inner.next().map(ScopedDefinitionId::from_u32)
match (self.inner.next(), self.constraints_active_at_binding.next()) {
(None, None) => None,
(Some(declaration), Some(constraints_active_at_binding)) => Some((
ScopedDefinitionId::from_u32(declaration),
ConstraintIdIterator {
wrapped: constraints_active_at_binding.iter(),
},
)),
// SAFETY: see above.
_ => unreachable!("declarations and constraints_active_at_binding length mismatch"),
}
self.inner.next().map(ScopedDefinitionId::from_u32)
}
}
@@ -586,7 +413,7 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
#[cfg(test)]
mod tests {
use super::{ScopedConstraintId, SymbolState};
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
@@ -618,7 +445,7 @@ mod tests {
let actual = symbol
.declarations()
.iter()
.map(|(d, _)| d.as_u32()) // TODO: constraints
.map(ScopedDefinitionId::as_u32)
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}
@@ -630,76 +457,76 @@ mod tests {
assert_bindings(&sym, true, &[]);
}
// #[test]
// fn with() {
// let mut sym = SymbolState::undefined();
// sym.record_binding(ScopedDefinitionId::from_u32(0));
#[test]
fn with() {
let mut sym = SymbolState::undefined();
sym.record_binding(ScopedDefinitionId::from_u32(0));
// assert_bindings(&sym, false, &["0<>"]);
// }
assert_bindings(&sym, false, &["0<>"]);
}
// #[test]
// fn set_may_be_unbound() {
// let mut sym = SymbolState::undefined();
// sym.record_binding(ScopedDefinitionId::from_u32(0));
// sym.set_may_be_unbound();
#[test]
fn set_may_be_unbound() {
let mut sym = SymbolState::undefined();
sym.record_binding(ScopedDefinitionId::from_u32(0));
sym.set_may_be_unbound();
// assert_bindings(&sym, true, &["0<>"]);
// }
assert_bindings(&sym, true, &["0<>"]);
}
// #[test]
// fn record_constraint() {
// let mut sym = SymbolState::undefined();
// sym.record_binding(ScopedDefinitionId::from_u32(0));
// sym.record_constraint(ScopedConstraintId::from_u32(0));
#[test]
fn record_constraint() {
let mut sym = SymbolState::undefined();
sym.record_binding(ScopedDefinitionId::from_u32(0));
sym.record_constraint(ScopedConstraintId::from_u32(0));
// assert_bindings(&sym, false, &["0<0>"]);
// }
assert_bindings(&sym, false, &["0<0>"]);
}
// #[test]
// fn merge() {
// // merging the same definition with the same constraint keeps the constraint
// let mut sym0a = SymbolState::undefined();
// sym0a.record_binding(ScopedDefinitionId::from_u32(0));
// sym0a.record_constraint(ScopedConstraintId::from_u32(0));
#[test]
fn merge() {
// merging the same definition with the same constraint keeps the constraint
let mut sym0a = SymbolState::undefined();
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
// let mut sym0b = SymbolState::undefined();
// sym0b.record_binding(ScopedDefinitionId::from_u32(0));
// sym0b.record_constraint(ScopedConstraintId::from_u32(0));
let mut sym0b = SymbolState::undefined();
sym0b.record_binding(ScopedDefinitionId::from_u32(0));
sym0b.record_constraint(ScopedConstraintId::from_u32(0));
// sym0a.merge(sym0b);
// let mut sym0 = sym0a;
// assert_bindings(&sym0, false, &["0<0>"]);
sym0a.merge(sym0b);
let mut sym0 = sym0a;
assert_bindings(&sym0, false, &["0<0>"]);
// // merging the same definition with differing constraints drops all constraints
// let mut sym1a = SymbolState::undefined();
// sym1a.record_binding(ScopedDefinitionId::from_u32(1));
// sym1a.record_constraint(ScopedConstraintId::from_u32(1));
// merging the same definition with differing constraints drops all constraints
let mut sym1a = SymbolState::undefined();
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
// let mut sym1b = SymbolState::undefined();
// sym1b.record_binding(ScopedDefinitionId::from_u32(1));
// sym1b.record_constraint(ScopedConstraintId::from_u32(2));
let mut sym1b = SymbolState::undefined();
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
// sym1a.merge(sym1b);
// let sym1 = sym1a;
// assert_bindings(&sym1, false, &["1<>"]);
sym1a.merge(sym1b);
let sym1 = sym1a;
assert_bindings(&sym1, false, &["1<>"]);
// // merging a constrained definition with unbound keeps both
// let mut sym2a = SymbolState::undefined();
// sym2a.record_binding(ScopedDefinitionId::from_u32(2));
// sym2a.record_constraint(ScopedConstraintId::from_u32(3));
// merging a constrained definition with unbound keeps both
let mut sym2a = SymbolState::undefined();
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
// let sym2b = SymbolState::undefined();
let sym2b = SymbolState::undefined();
// sym2a.merge(sym2b);
// let sym2 = sym2a;
// assert_bindings(&sym2, true, &["2<3>"]);
sym2a.merge(sym2b);
let sym2 = sym2a;
assert_bindings(&sym2, true, &["2<3>"]);
// // merging different definitions keeps them each with their existing constraints
// sym0.merge(sym2);
// let sym = sym0;
// assert_bindings(&sym, true, &["0<0>", "2<3>"]);
// }
// merging different definitions keeps them each with their existing constraints
sym0.merge(sym2);
let sym = sym0;
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
}
#[test]
fn no_declaration() {
@@ -708,54 +535,54 @@ mod tests {
assert_declarations(&sym, true, &[]);
}
// #[test]
// fn record_declaration() {
// let mut sym = SymbolState::undefined();
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
#[test]
fn record_declaration() {
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
// assert_declarations(&sym, false, &[1]);
// }
assert_declarations(&sym, false, &[1]);
}
// #[test]
// fn record_declaration_override() {
// let mut sym = SymbolState::undefined();
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
// sym.record_declaration(ScopedDefinitionId::from_u32(2));
#[test]
fn record_declaration_override() {
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
sym.record_declaration(ScopedDefinitionId::from_u32(2));
// assert_declarations(&sym, false, &[2]);
// }
assert_declarations(&sym, false, &[2]);
}
// #[test]
// fn record_declaration_merge() {
// let mut sym = SymbolState::undefined();
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
#[test]
fn record_declaration_merge() {
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
// let mut sym2 = SymbolState::undefined();
// sym2.record_declaration(ScopedDefinitionId::from_u32(2));
let mut sym2 = SymbolState::undefined();
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
// sym.merge(sym2);
sym.merge(sym2);
// assert_declarations(&sym, false, &[1, 2]);
// }
assert_declarations(&sym, false, &[1, 2]);
}
// #[test]
// fn record_declaration_merge_partial_undeclared() {
// let mut sym = SymbolState::undefined();
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
#[test]
fn record_declaration_merge_partial_undeclared() {
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
// let sym2 = SymbolState::undefined();
let sym2 = SymbolState::undefined();
// sym.merge(sym2);
sym.merge(sym2);
// assert_declarations(&sym, true, &[1]);
// }
assert_declarations(&sym, true, &[1]);
}
// #[test]
// fn set_may_be_undeclared() {
// let mut sym = SymbolState::undefined();
// sym.record_declaration(ScopedDefinitionId::from_u32(0));
// sym.set_may_be_undeclared();
#[test]
fn set_may_be_undeclared() {
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(0));
sym.set_may_be_undeclared();
// assert_declarations(&sym, true, &[0]);
// }
assert_declarations(&sym, true, &[0]);
}
}

View File

@@ -166,31 +166,15 @@ impl_binding_has_ty!(ast::ParameterWithDefault);
mod tests {
use ruff_db::files::system_path_to_file;
use ruff_db::parsed::parsed_module;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::{HasTy, ProgramSettings, SemanticModel};
fn setup_db<'a>(files: impl IntoIterator<Item = (&'a str, &'a str)>) -> anyhow::Result<TestDb> {
let mut db = TestDb::new();
db.write_files(files)?;
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings::new(SystemPathBuf::from("/src")),
},
)?;
Ok(db)
}
use crate::db::tests::TestDbBuilder;
use crate::{HasTy, SemanticModel};
#[test]
fn function_ty() -> anyhow::Result<()> {
let db = setup_db([("/src/foo.py", "def test(): pass")])?;
let db = TestDbBuilder::new()
.with_file("/src/foo.py", "def test(): pass")
.build()?;
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
@@ -207,7 +191,9 @@ mod tests {
#[test]
fn class_ty() -> anyhow::Result<()> {
let db = setup_db([("/src/foo.py", "class Test: pass")])?;
let db = TestDbBuilder::new()
.with_file("/src/foo.py", "class Test: pass")
.build()?;
let foo = system_path_to_file(&db, "/src/foo.py").unwrap();
@@ -224,10 +210,10 @@ mod tests {
#[test]
fn alias_ty() -> anyhow::Result<()> {
let db = setup_db([
("/src/foo.py", "class Test: pass"),
("/src/bar.py", "from foo import Test"),
])?;
let db = TestDbBuilder::new()
.with_file("/src/foo.py", "class Test: pass")
.with_file("/src/bar.py", "from foo import Test")
.build()?;
let bar = system_path_to_file(&db, "/src/bar.py").unwrap();

View File

@@ -90,7 +90,7 @@ impl<'db> Symbol<'db> {
#[cfg(test)]
mod tests {
use super::*;
use crate::types::tests::setup_db;
use crate::db::tests::setup_db;
#[test]
fn test_symbol_or_fall_back_to() {

View File

@@ -15,7 +15,6 @@ pub(crate) use self::infer::{
pub(crate) use self::signatures::Signature;
use crate::module_resolver::file_to_module;
use crate::semantic_index::ast_ids::HasScopedExpressionId;
use crate::semantic_index::constraint::ConstraintNode;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId};
use crate::semantic_index::{
@@ -29,7 +28,7 @@ use crate::symbol::{Boundness, Symbol};
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
use crate::types::narrow::narrowing_constraint;
use crate::{Db, FxOrderSet, Module, Program};
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
mod builder;
mod diagnostic;
@@ -41,6 +40,9 @@ mod signatures;
mod string_annotation;
mod unpacker;
#[cfg(test)]
mod property_tests;
#[salsa::tracked(return_ref)]
pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
let _span = tracing::trace_span!("check_types", file=?file.path(db)).entered();
@@ -223,12 +225,6 @@ fn definition_expression_ty<'db>(
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum UnconditionallyVisible {
Yes,
No,
}
/// Infer the combined type of an iterator of bindings.
///
/// Will return a union if there is more than one binding.
@@ -236,88 +232,29 @@ fn bindings_ty<'db>(
db: &'db dyn Db,
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
) -> Option<Type<'db>> {
let def_types = bindings_with_constraints.map(
let mut def_types = bindings_with_constraints.map(
|BindingWithConstraints {
binding,
constraints,
constraints_active_at_binding,
}| {
let test_expr_tys = || {
constraints_active_at_binding.clone().map(|c| {
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
let inference = infer_expression_types(db, test_expr);
let scope = test_expr.scope(db);
inference
.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
} else {
// TODO: handle other constraint nodes
todo_type!()
};
let mut constraint_tys = constraints
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
.peekable();
(c, ty)
})
};
if test_expr_tys().any(|(c, test_expr_ty)| {
if c.is_positive {
test_expr_ty.bool(db).is_always_false()
} else {
test_expr_ty.bool(db).is_always_true()
}
}) {
// TODO: do we need to call binding_ty(…) even if we don't need the result?
(Type::Never, UnconditionallyVisible::No)
let binding_ty = binding_ty(db, binding);
if constraint_tys.peek().is_some() {
constraint_tys
.fold(
IntersectionBuilder::new(db).add_positive(binding_ty),
IntersectionBuilder::add_positive,
)
.build()
} else {
let mut test_expr_tys_iter = test_expr_tys().peekable();
let unconditionally_visible = if test_expr_tys_iter.peek().is_some()
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
if c.is_positive {
test_expr_ty.bool(db).is_always_true()
} else {
test_expr_ty.bool(db).is_always_false()
}
}) {
UnconditionallyVisible::Yes
} else {
UnconditionallyVisible::No
};
let mut constraint_tys = constraints
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
.peekable();
let binding_ty = binding_ty(db, binding);
if constraint_tys.peek().is_some() {
let intersection_ty = constraint_tys
.fold(
IntersectionBuilder::new(db).add_positive(binding_ty),
IntersectionBuilder::add_positive,
)
.build();
(intersection_ty, unconditionally_visible)
} else {
(binding_ty, unconditionally_visible)
}
binding_ty
}
},
);
// TODO: get rid of all the collects and clean up, obviously
let def_types: Vec<_> = def_types.collect();
// shrink the vector to only include everything from the last unconditionally visible binding
let def_types: Vec<_> = def_types
.iter()
.rev()
.take_while_inclusive(|(_, unconditionally_visible)| {
*unconditionally_visible != UnconditionallyVisible::Yes
})
.map(|(ty, _)| *ty)
.collect();
let mut def_types = def_types.into_iter().rev();
if let Some(first) = def_types.next() {
if let Some(second) = def_types.next() {
Some(UnionType::from_elements(
@@ -353,63 +290,7 @@ fn declarations_ty<'db>(
declarations: DeclarationsIterator<'_, 'db>,
undeclared_ty: Option<Type<'db>>,
) -> DeclaredTypeResult<'db> {
let decl_types = declarations.map(|(declaration, constraints_active_at_declaration)| {
let test_expr_tys = || {
constraints_active_at_declaration.clone().map(|c| {
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
let inference = infer_expression_types(db, test_expr);
let scope = test_expr.scope(db);
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
} else {
// TODO: handle other constraint nodes
todo_type!()
};
(c, ty)
})
};
if test_expr_tys().any(|(c, test_expr_ty)| {
if c.is_positive {
test_expr_ty.bool(db).is_always_false()
} else {
test_expr_ty.bool(db).is_always_true()
}
}) {
(Type::Never, UnconditionallyVisible::No)
} else {
let mut test_expr_tys_iter = test_expr_tys().peekable();
if test_expr_tys_iter.peek().is_some()
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
if c.is_positive {
test_expr_ty.bool(db).is_always_true()
} else {
test_expr_ty.bool(db).is_always_false()
}
})
{
(declaration_ty(db, declaration), UnconditionallyVisible::Yes)
} else {
(declaration_ty(db, declaration), UnconditionallyVisible::No)
}
}
});
// TODO: get rid of all the collects and clean up, obviously
let decl_types: Vec<_> = decl_types.collect();
// shrink the vector to only include everything from the last unconditionally visible binding
let decl_types: Vec<_> = decl_types
.iter()
.rev()
.take_while_inclusive(|(_, unconditionally_visible)| {
*unconditionally_visible != UnconditionallyVisible::Yes
})
.map(|(ty, _)| *ty)
.collect();
let decl_types = decl_types.into_iter().rev();
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
let mut all_types = undeclared_ty.into_iter().chain(decl_types);
@@ -421,13 +302,7 @@ fn declarations_ty<'db>(
let declared_ty = if let Some(second) = all_types.next() {
let mut builder = UnionBuilder::new(db).add(first);
for other in [second].into_iter().chain(all_types) {
// Make sure not to emit spurious errors relating to `Type::Todo`,
// since we only infer this type due to a limitation in our current model.
//
// `Unknown` is different here, since we might infer `Unknown`
// for one of these due to a variable being defined in one possible
// control-flow branch but not another one.
if !first.is_equivalent_to(db, other) && !first.is_todo() && !other.is_todo() {
if !first.is_equivalent_to(db, other) {
conflicting.push(other);
}
builder = builder.add(other);
@@ -696,8 +571,11 @@ impl<'db> Type<'db> {
Self::BytesLiteral(BytesLiteralType::new(db, bytes))
}
pub fn tuple(db: &'db dyn Db, elements: &[Type<'db>]) -> Self {
Self::Tuple(TupleType::new(db, elements))
pub fn tuple<T: Into<Type<'db>>>(
db: &'db dyn Db,
elements: impl IntoIterator<Item = T>,
) -> Self {
TupleType::from_elements(db, elements)
}
#[must_use]
@@ -716,11 +594,16 @@ impl<'db> Type<'db> {
/// Return true if this type is a [subtype of] type `target`.
///
/// This method returns `false` if either `self` or `other` is not fully static.
///
/// [subtype of]: https://typing.readthedocs.io/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool {
if self.is_equivalent_to(db, target) {
return true;
}
if !self.is_fully_static(db) || !target.is_fully_static(db) {
return false;
}
match (self, target) {
(Type::Unknown | Type::Any | Type::Todo(_), _) => false,
(_, Type::Unknown | Type::Any | Type::Todo(_)) => false,
@@ -841,9 +724,6 @@ impl<'db> Type<'db> {
(Type::KnownInstance(left), right) => {
left.instance_fallback(db).is_subtype_of(db, right)
}
(left, Type::KnownInstance(right)) => {
left.is_subtype_of(db, right.instance_fallback(db))
}
(Type::Instance(left), Type::Instance(right)) => left.is_instance_of(db, right.class),
// TODO
_ => false,
@@ -883,14 +763,48 @@ impl<'db> Type<'db> {
}
}
/// Return true if this type is equivalent to type `other`.
/// Return true if this type is [equivalent to] type `other`.
///
/// This method returns `false` if either `self` or `other` is not fully static.
///
/// [equivalent to]: https://typing.readthedocs.io/en/latest/spec/glossary.html#term-equivalent
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
if !(self.is_fully_static(db) && other.is_fully_static(db)) {
return false;
}
// TODO equivalent but not identical structural types, differently-ordered unions and
// intersections, other cases?
// TODO: Once we have support for final classes, we can establish that
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
self == other || matches!((self, other), (Type::Todo(_), Type::Todo(_)))
// TODO: The following is a workaround that is required to unify the two different versions
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
// we understand `sys.version_info` branches.
self == other
|| matches!((self, other),
(
Type::Instance(InstanceType { class: self_class }),
Type::Instance(InstanceType { class: target_class })
)
if {
let self_known = self_class.known(db);
matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType))
&& self_known == target_class.known(db)
}
)
}
/// Returns true if both `self` and `other` are the same gradual form
/// (limited to `Any`, `Unknown`, or `Todo`).
pub(crate) fn is_same_gradual_form(self, other: Type<'db>) -> bool {
matches!(
(self, other),
(Type::Unknown, Type::Unknown)
| (Type::Any, Type::Any)
| (Type::Todo(_), Type::Todo(_))
)
}
/// Return true if this type and `other` have no common elements.
@@ -1099,6 +1013,63 @@ impl<'db> Type<'db> {
}
}
/// Returns true if the type does not contain any gradual forms (as a sub-part).
pub(crate) fn is_fully_static(self, db: &'db dyn Db) -> bool {
match self {
Type::Any | Type::Unknown | Type::Todo(_) => false,
Type::Never
| Type::FunctionLiteral(..)
| Type::ModuleLiteral(..)
| Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::StringLiteral(_)
| Type::LiteralString
| Type::BytesLiteral(_)
| Type::SliceLiteral(_)
| Type::KnownInstance(_) => true,
Type::ClassLiteral(_) | Type::SubclassOf(_) | Type::Instance(_) => {
// TODO: Ideally, we would iterate over the MRO of the class, check if all
// bases are fully static, and only return `true` if that is the case.
//
// This does not work yet, because we currently infer `Unknown` for some
// generic base classes that we don't understand yet. For example, `str`
// is defined as `class str(Sequence[str])` in typeshed and we currently
// compute its MRO as `(str, Unknown, object)`. This would make us think
// that `str` is a gradual type, which causes all sorts of downstream
// issues because it does not participate in equivalence/subtyping etc.
//
// Another problem is that we run into problems if we eagerly query the
// MRO of class literals here. I have not fully investigated this, but
// iterating over the MRO alone, without even acting on it, causes us to
// infer `Unknown` for many classes.
true
}
Type::Union(union) => union
.elements(db)
.iter()
.all(|elem| elem.is_fully_static(db)),
Type::Intersection(intersection) => {
intersection
.positive(db)
.iter()
.all(|elem| elem.is_fully_static(db))
&& intersection
.negative(db)
.iter()
.all(|elem| elem.is_fully_static(db))
}
Type::Tuple(tuple) => tuple
.elements(db)
.iter()
.all(|elem| elem.is_fully_static(db)),
// TODO: Once we support them, make sure that we return `false` for other types
// containing gradual forms such as `tuple[Any, ...]` or `Callable[..., str]`.
// Conversely, make sure to return `true` for homogeneous tuples such as
// `tuple[int, ...]`, once we add support for them.
}
}
/// Return true if there is just a single inhabitant for this type.
///
/// Note: This function aims to have no false positives, but might return `false`
@@ -1446,21 +1417,76 @@ impl<'db> Type<'db> {
}
}
/// Return the type of `len()` on a type if it is known more precisely than `int`,
/// or `None` otherwise.
///
/// In the second case, the return type of `len()` in `typeshed` (`int`)
/// is used as a fallback.
fn len(&self, db: &'db dyn Db) -> Option<Type<'db>> {
fn non_negative_int_literal<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
match ty {
// TODO: Emit diagnostic for non-integers and negative integers
Type::IntLiteral(value) => (value >= 0).then_some(ty),
Type::BooleanLiteral(value) => Some(Type::IntLiteral(value.into())),
Type::Union(union) => {
let mut builder = UnionBuilder::new(db);
for element in union.elements(db) {
builder = builder.add(non_negative_int_literal(db, *element)?);
}
Some(builder.build())
}
_ => None,
}
}
let usize_len = match self {
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
Type::StringLiteral(string) => Some(string.python_len(db)),
Type::Tuple(tuple) => Some(tuple.len(db)),
_ => None,
};
if let Some(usize_len) = usize_len {
return usize_len.try_into().ok().map(Type::IntLiteral);
}
let return_ty = match self.call_dunder(db, "__len__", &[*self]) {
// TODO: emit a diagnostic
CallDunderResult::MethodNotAvailable => return None,
CallDunderResult::CallOutcome(outcome) | CallDunderResult::PossiblyUnbound(outcome) => {
outcome.return_ty(db)?
}
};
non_negative_int_literal(db, return_ty)
}
/// Return the outcome of calling an object of this type.
#[must_use]
fn call(self, db: &'db dyn Db, arg_types: &[Type<'db>]) -> CallOutcome<'db> {
match self {
// TODO validate typed call arguments vs callable signature
Type::FunctionLiteral(function_type) => {
if function_type.is_known(db, KnownFunction::RevealType) {
CallOutcome::revealed(
function_type.signature(db).return_ty,
*arg_types.first().unwrap_or(&Type::Unknown),
)
} else {
CallOutcome::callable(function_type.signature(db).return_ty)
Type::FunctionLiteral(function_type) => match function_type.known(db) {
Some(KnownFunction::RevealType) => CallOutcome::revealed(
function_type.signature(db).return_ty,
*arg_types.first().unwrap_or(&Type::Unknown),
),
Some(KnownFunction::Len) => {
let normal_return_ty = function_type.signature(db).return_ty;
let [only_arg] = arg_types else {
// TODO: Emit a diagnostic
return CallOutcome::callable(normal_return_ty);
};
let len_ty = only_arg.len(db);
CallOutcome::callable(len_ty.unwrap_or(normal_return_ty))
}
}
_ => CallOutcome::callable(function_type.signature(db).return_ty),
},
// TODO annotated return type on `__new__` or metaclass `__call__`
Type::ClassLiteral(ClassLiteralType { class }) => {
@@ -1509,7 +1535,7 @@ impl<'db> Type<'db> {
// `Any` is callable, and its return type is also `Any`.
Type::Any => CallOutcome::callable(Type::Any),
Type::Todo(_) => CallOutcome::callable(todo_type!()),
Type::Todo(_) => CallOutcome::callable(todo_type!("call todo")),
Type::Unknown => CallOutcome::callable(Type::Unknown),
@@ -1662,6 +1688,8 @@ impl<'db> Type<'db> {
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
Type::Never
}
Type::KnownInstance(KnownInstanceType::LiteralString) => Type::LiteralString,
Type::KnownInstance(KnownInstanceType::Any) => Type::Any,
_ => todo_type!(),
}
}
@@ -1898,8 +1926,8 @@ impl<'db> KnownClass {
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
// singleton, but not for `typing._NoDefaultType`. So we need to switch
// to `typing.NoDefault` for newer versions:
if python_version.major >= 3 && python_version.minor >= 13 {
// to `typing._NoDefaultType` for newer versions:
if python_version >= PythonVersion::PY313 {
CoreStdlibModule::Typing
} else {
CoreStdlibModule::TypingExtensions
@@ -1968,7 +1996,7 @@ impl<'db> KnownClass {
}
/// Return `true` if the module of `self` matches `module_name`
fn check_module(self, db: &dyn Db, module: &Module) -> bool {
fn check_module(self, db: &'db dyn Db, module: &Module) -> bool {
if !module.search_path().is_standard_library() {
return false;
}
@@ -2002,6 +2030,8 @@ impl<'db> KnownClass {
pub enum KnownInstanceType<'db> {
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
Literal,
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
LiteralString,
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
Optional,
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
@@ -2010,6 +2040,8 @@ pub enum KnownInstanceType<'db> {
NoReturn,
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
Never,
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
Any,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
@@ -2021,11 +2053,13 @@ impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
Self::Literal => "Literal",
Self::LiteralString => "LiteralString",
Self::Optional => "Optional",
Self::Union => "Union",
Self::TypeVar(_) => "TypeVar",
Self::NoReturn => "NoReturn",
Self::Never => "Never",
Self::Any => "Any",
Self::TypeAliasType(_) => "TypeAliasType",
}
}
@@ -2034,11 +2068,13 @@ impl<'db> KnownInstanceType<'db> {
pub const fn bool(self) -> Truthiness {
match self {
Self::Literal
| Self::LiteralString
| Self::Optional
| Self::TypeVar(_)
| Self::Union
| Self::NoReturn
| Self::Never
| Self::Any
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
}
}
@@ -2047,10 +2083,12 @@ impl<'db> KnownInstanceType<'db> {
pub fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Literal => "typing.Literal",
Self::LiteralString => "typing.LiteralString",
Self::Optional => "typing.Optional",
Self::Union => "typing.Union",
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::Any => "typing.Any",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
}
@@ -2060,10 +2098,12 @@ impl<'db> KnownInstanceType<'db> {
pub const fn class(self) -> KnownClass {
match self {
Self::Literal => KnownClass::SpecialForm,
Self::LiteralString => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::Union => KnownClass::SpecialForm,
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::Any => KnownClass::Object,
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
}
@@ -2083,7 +2123,9 @@ impl<'db> KnownInstanceType<'db> {
return None;
}
match (module.name().as_str(), instance_name) {
("typing", "Any") => Some(Self::Any),
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
("typing" | "typing_extensions", "LiteralString") => Some(Self::LiteralString),
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
("typing" | "typing_extensions", "Union") => Some(Self::Union),
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
@@ -2137,7 +2179,7 @@ impl<'db> TypeVarInstance<'db> {
}
#[allow(unused)]
pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&[Type<'db>]> {
pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&'db [Type<'db>]> {
if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
Some(tuple.elements(db))
} else {
@@ -2510,14 +2552,6 @@ impl Truthiness {
matches!(self, Truthiness::Ambiguous)
}
const fn is_always_false(self) -> bool {
matches!(self, Truthiness::AlwaysFalse)
}
const fn is_always_true(self) -> bool {
matches!(self, Truthiness::AlwaysTrue)
}
const fn negate(self) -> Self {
match self {
Self::AlwaysTrue => Self::AlwaysFalse,
@@ -2626,13 +2660,15 @@ pub enum KnownFunction {
ConstraintFunction(KnownConstraintFunction),
/// `builtins.reveal_type`, `typing.reveal_type` or `typing_extensions.reveal_type`
RevealType,
/// `builtins.len`
Len,
}
impl KnownFunction {
pub fn constraint_function(self) -> Option<KnownConstraintFunction> {
match self {
Self::ConstraintFunction(f) => Some(f),
Self::RevealType => None,
Self::RevealType | Self::Len => None,
}
}
@@ -2649,6 +2685,7 @@ impl KnownFunction {
"issubclass" if definition.is_builtin_definition(db) => Some(
KnownFunction::ConstraintFunction(KnownConstraintFunction::IsSubclass),
),
"len" if definition.is_builtin_definition(db) => Some(KnownFunction::Len),
_ => None,
}
}
@@ -2689,7 +2726,7 @@ impl<'db> Class<'db> {
///
/// Were this not a salsa query, then the calling query
/// would depend on the class's AST and rerun for every change in that file.
fn explicit_bases(self, db: &'db dyn Db) -> &[Type<'db>] {
fn explicit_bases(self, db: &'db dyn Db) -> &'db [Type<'db>] {
self.explicit_bases_query(db)
}
@@ -2758,7 +2795,11 @@ impl<'db> Class<'db> {
pub fn is_subclass_of(self, db: &'db dyn Db, other: Class) -> bool {
// `is_subclass_of` is checking the subtype relation, in which gradual types do not
// participate, so we should not return `True` if we find `Any/Unknown` in the MRO.
self.iter_mro(db).contains(&ClassBase::Class(other))
self.is_subclass_of_base(db, other)
}
fn is_subclass_of_base(self, db: &'db dyn Db, other: impl Into<ClassBase<'db>>) -> bool {
self.iter_mro(db).contains(&other.into())
}
/// Return the explicit `metaclass` of this class, if one is defined.
@@ -3099,8 +3140,9 @@ pub struct StringLiteralType<'db> {
}
impl<'db> StringLiteralType<'db> {
pub fn len(&self, db: &'db dyn Db) -> usize {
self.value(db).len()
/// The length of the string, as would be returned by Python's `len()`.
pub fn python_len(&self, db: &'db dyn Db) -> usize {
self.value(db).chars().count()
}
}
@@ -3110,6 +3152,12 @@ pub struct BytesLiteralType<'db> {
value: Box<[u8]>,
}
impl<'db> BytesLiteralType<'db> {
pub fn python_len(&self, db: &'db dyn Db) -> usize {
self.value(db).len()
}
}
#[salsa::interned]
pub struct SliceLiteralType<'db> {
start: Option<i32>,
@@ -3117,7 +3165,7 @@ pub struct SliceLiteralType<'db> {
step: Option<i32>,
}
impl<'db> SliceLiteralType<'db> {
impl SliceLiteralType<'_> {
fn as_tuple(self, db: &dyn Db) -> (Option<i32>, Option<i32>, Option<i32>) {
(self.start(db), self.stop(db), self.step(db))
}
@@ -3129,6 +3177,23 @@ pub struct TupleType<'db> {
}
impl<'db> TupleType<'db> {
pub fn from_elements<T: Into<Type<'db>>>(
db: &'db dyn Db,
types: impl IntoIterator<Item = T>,
) -> Type<'db> {
let mut elements = vec![];
for ty in types {
let ty = ty.into();
if ty.is_never() {
return Type::Never;
}
elements.push(ty);
}
Type::Tuple(Self::new(db, elements.into_boxed_slice()))
}
pub fn get(&self, db: &'db dyn Db, index: usize) -> Option<Type<'db>> {
self.elements(db).get(index).copied()
}
@@ -3146,46 +3211,20 @@ static_assertions::assert_eq_size!(Type, [u8; 16]);
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::db::tests::{setup_db, TestDb, TestDbBuilder};
use crate::stdlib::typing_symbol;
use crate::ProgramSettings;
use crate::PythonVersion;
use ruff_db::files::system_path_to_file;
use ruff_db::parsed::parsed_module;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_db::system::DbWithTestSystem;
use ruff_db::testing::assert_function_query_was_not_run;
use ruff_python_ast as ast;
use test_case::test_case;
pub(crate) fn setup_db_with_python_version(python_version: PythonVersion) -> 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: python_version,
search_paths: SearchPathSettings::new(src_root),
},
)
.expect("Valid search path settings");
db
}
pub(crate) fn setup_db() -> TestDb {
setup_db_with_python_version(PythonVersion::default())
}
/// A test representation of a type that can be transformed unambiguously into a real Type,
/// given a db.
#[derive(Debug, Clone)]
enum Ty {
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Ty {
Never,
Unknown,
None,
@@ -3209,7 +3248,7 @@ pub(crate) mod tests {
}
impl Ty {
fn into_type(self, db: &TestDb) -> Type<'_> {
pub(crate) fn into_type(self, db: &TestDb) -> Type<'_> {
match self {
Ty::Never => Type::Never,
Ty::Unknown => Type::Unknown,
@@ -3240,13 +3279,21 @@ pub(crate) mod tests {
builder.build()
}
Ty::Tuple(tys) => {
let elements: Vec<Type> = tys.into_iter().map(|ty| ty.into_type(db)).collect();
Type::tuple(db, &elements)
let elements = tys.into_iter().map(|ty| ty.into_type(db));
Type::tuple(db, elements)
}
}
}
}
#[test_case(Ty::Tuple(vec![Ty::Never]))]
#[test_case(Ty::Tuple(vec![Ty::BuiltinInstance("str"), Ty::Never, Ty::BuiltinInstance("int")]))]
#[test_case(Ty::Tuple(vec![Ty::Tuple(vec![Ty::Never])]))]
fn tuple_containing_never_simplifies_to_never(ty: Ty) {
let db = setup_db();
assert_eq!(ty.into_type(&db), Type::Never);
}
#[test_case(Ty::BuiltinInstance("str"), Ty::BuiltinInstance("object"))]
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinInstance("object"))]
#[test_case(Ty::Unknown, Ty::IntLiteral(1))]
@@ -3339,7 +3386,9 @@ pub(crate) mod tests {
}
#[test_case(Ty::BuiltinInstance("object"), Ty::BuiltinInstance("int"))]
#[test_case(Ty::Unknown, Ty::Unknown)]
#[test_case(Ty::Unknown, Ty::IntLiteral(1))]
#[test_case(Ty::Any, Ty::Any)]
#[test_case(Ty::Any, Ty::IntLiteral(1))]
#[test_case(Ty::IntLiteral(1), Ty::Unknown)]
#[test_case(Ty::IntLiteral(1), Ty::Any)]
@@ -3360,6 +3409,7 @@ pub(crate) mod tests {
#[test_case(Ty::IntLiteral(1), Ty::Intersection{pos: vec![Ty::BuiltinInstance("int")], neg: vec![Ty::IntLiteral(1)]})]
#[test_case(Ty::BuiltinClassLiteral("int"), Ty::BuiltinClassLiteral("object"))]
#[test_case(Ty::BuiltinInstance("int"), Ty::BuiltinClassLiteral("int"))]
#[test_case(Ty::TypingInstance("_SpecialForm"), Ty::TypingLiteral)]
fn is_not_subtype_of(from: Ty, to: Ty) {
let db = setup_db();
assert!(!from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));
@@ -3446,6 +3496,18 @@ pub(crate) mod tests {
assert!(from.into_type(&db).is_equivalent_to(&db, to.into_type(&db)));
}
#[test_case(Ty::Any, Ty::Any)]
#[test_case(Ty::Any, Ty::None)]
#[test_case(Ty::Unknown, Ty::Unknown)]
#[test_case(Ty::Todo, Ty::Todo)]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(0)]))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2), Ty::IntLiteral(3)]))]
fn is_not_equivalent_to(from: Ty, to: Ty) {
let db = setup_db();
assert!(!from.into_type(&db).is_equivalent_to(&db, to.into_type(&db)));
}
#[test_case(Ty::Never, Ty::Never)]
#[test_case(Ty::Never, Ty::None)]
#[test_case(Ty::Never, Ty::BuiltinInstance("int"))]
@@ -3635,11 +3697,16 @@ pub(crate) mod tests {
assert!(from.into_type(&db).is_singleton(&db));
}
/// TODO: test documentation
/// Explicitly test for Python version <3.13 and >=3.13, to ensure that
/// the fallback to `typing_extensions` is working correctly.
/// See [`KnownClass::canonical_module`] for more information.
#[test_case(PythonVersion::PY312)]
#[test_case(PythonVersion::PY313)]
fn no_default_type_is_singleton(python_version: PythonVersion) {
let db = setup_db_with_python_version(python_version);
let db = TestDbBuilder::new()
.with_python_version(python_version)
.build()
.unwrap();
let no_default = Ty::KnownClassInstance(KnownClass::NoDefaultType).into_type(&db);
@@ -3684,6 +3751,41 @@ pub(crate) mod tests {
assert!(!from.into_type(&db).is_singleton(&db));
}
#[test_case(Ty::Never)]
#[test_case(Ty::None)]
#[test_case(Ty::IntLiteral(1))]
#[test_case(Ty::BooleanLiteral(true))]
#[test_case(Ty::StringLiteral("abc"))]
#[test_case(Ty::LiteralString)]
#[test_case(Ty::BytesLiteral("abc"))]
#[test_case(Ty::KnownClassInstance(KnownClass::Str))]
#[test_case(Ty::KnownClassInstance(KnownClass::Object))]
#[test_case(Ty::KnownClassInstance(KnownClass::Type))]
#[test_case(Ty::BuiltinClassLiteral("str"))]
#[test_case(Ty::TypingLiteral)]
#[test_case(Ty::Union(vec![Ty::KnownClassInstance(KnownClass::Str), Ty::None]))]
#[test_case(Ty::Intersection{pos: vec![Ty::KnownClassInstance(KnownClass::Str)], neg: vec![Ty::LiteralString]})]
#[test_case(Ty::Tuple(vec![]))]
#[test_case(Ty::Tuple(vec![Ty::KnownClassInstance(KnownClass::Int), Ty::KnownClassInstance(KnownClass::Object)]))]
fn is_fully_static(from: Ty) {
let db = setup_db();
assert!(from.into_type(&db).is_fully_static(&db));
}
#[test_case(Ty::Any)]
#[test_case(Ty::Unknown)]
#[test_case(Ty::Todo)]
#[test_case(Ty::Union(vec![Ty::Any, Ty::KnownClassInstance(KnownClass::Str)]))]
#[test_case(Ty::Union(vec![Ty::KnownClassInstance(KnownClass::Str), Ty::Unknown]))]
#[test_case(Ty::Intersection{pos: vec![Ty::Any], neg: vec![Ty::LiteralString]})]
#[test_case(Ty::Tuple(vec![Ty::KnownClassInstance(KnownClass::Int), Ty::Any]))]
fn is_not_fully_static(from: Ty) {
let db = setup_db();
assert!(!from.into_type(&db).is_fully_static(&db));
}
#[test_case(Ty::IntLiteral(1); "is_int_literal_truthy")]
#[test_case(Ty::IntLiteral(-1))]
#[test_case(Ty::StringLiteral("foo"))]
@@ -3738,7 +3840,10 @@ pub(crate) mod tests {
#[test]
fn typing_vs_typeshed_no_default() {
let db = setup_db();
let db = TestDbBuilder::new()
.with_python_version(PythonVersion::PY313)
.build()
.unwrap();
let typing_no_default = typing_symbol(&db, "NoDefault").expect_type();
let typing_extensions_no_default = typing_extensions_symbol(&db, "NoDefault").expect_type();
@@ -3858,19 +3963,6 @@ pub(crate) mod tests {
let todo3 = todo_type!();
let todo4 = todo_type!();
assert!(todo1.is_equivalent_to(&db, todo2));
assert!(todo3.is_equivalent_to(&db, todo4));
assert!(todo1.is_equivalent_to(&db, todo3));
assert!(todo1.is_subtype_of(&db, todo2));
assert!(todo2.is_subtype_of(&db, todo1));
assert!(todo3.is_subtype_of(&db, todo4));
assert!(todo4.is_subtype_of(&db, todo3));
assert!(todo1.is_subtype_of(&db, todo3));
assert!(todo3.is_subtype_of(&db, todo1));
let int = KnownClass::Int.to_instance(&db);
assert!(int.is_assignable_to(&db, todo1));

View File

@@ -73,7 +73,8 @@ impl<'db> UnionBuilder<'db> {
// supertype of bool. Therefore, we are done.
break;
}
if ty.is_subtype_of(self.db, *element) {
if ty.is_same_gradual_form(*element) || ty.is_subtype_of(self.db, *element) {
return self;
} else if element.is_subtype_of(self.db, ty) {
to_remove.push(index);
@@ -259,7 +260,9 @@ impl<'db> InnerIntersectionBuilder<'db> {
let mut to_remove = SmallVec::<[usize; 1]>::new();
for (index, existing_positive) in self.positive.iter().enumerate() {
// S & T = S if S <: T
if existing_positive.is_subtype_of(db, new_positive) {
if existing_positive.is_subtype_of(db, new_positive)
|| existing_positive.is_same_gradual_form(new_positive)
{
return;
}
// same rule, reverse order
@@ -375,36 +378,14 @@ impl<'db> InnerIntersectionBuilder<'db> {
#[cfg(test)]
mod tests {
use super::{IntersectionBuilder, IntersectionType, Type, UnionType};
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::stdlib::typing_symbol;
use crate::db::tests::{setup_db, TestDb};
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
use crate::ProgramSettings;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_db::system::DbWithTestSystem;
use test_case::test_case;
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();
@@ -497,6 +478,17 @@ mod tests {
assert_eq!(u1.expect_union().elements(&db), &[t1, t0]);
}
#[test]
fn build_union_simplify_multiple_unknown() {
let db = setup_db();
let t0 = KnownClass::Str.to_instance(&db);
let t1 = Type::Unknown;
let u = UnionType::from_elements(&db, [t0, t1, t1]);
assert_eq!(u.expect_union().elements(&db), &[t0, t1]);
}
#[test]
fn build_union_subsume_multiple() {
let db = setup_db();
@@ -604,6 +596,42 @@ mod tests {
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_multiple_unknown() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::Unknown)
.add_positive(Type::Unknown)
.build();
assert_eq!(ty, Type::Unknown);
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::Unknown)
.add_negative(Type::Unknown)
.build();
assert_eq!(ty, Type::Unknown);
let ty = IntersectionBuilder::new(&db)
.add_negative(Type::Unknown)
.add_negative(Type::Unknown)
.build();
assert_eq!(ty, Type::Unknown);
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::Unknown)
.add_positive(Type::IntLiteral(0))
.add_negative(Type::Unknown)
.build();
assert_eq!(
ty,
IntersectionBuilder::new(&db)
.add_positive(Type::Unknown)
.add_positive(Type::IntLiteral(0))
.build()
);
}
#[test]
fn intersection_distributes_over_union() {
let db = setup_db();
@@ -626,59 +654,85 @@ mod tests {
#[test]
fn intersection_negation_distributes_over_union() {
let db = setup_db();
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
let ht = typing_symbol(&db, "Hashable")
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
// sh_t: Sized & Hashable
let sh_t = IntersectionBuilder::new(&db)
.add_positive(st)
.add_positive(ht)
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
// intersection: A & B
let intersection = IntersectionBuilder::new(&db)
.add_positive(a)
.add_positive(b)
.build()
.expect_intersection();
assert_eq!(sh_t.pos_vec(&db), &[st, ht]);
assert_eq!(sh_t.neg_vec(&db), &[]);
assert_eq!(intersection.pos_vec(&db), &[a, b]);
assert_eq!(intersection.neg_vec(&db), &[]);
// ~sh_t => ~Sized | ~Hashable
let not_s_h_t = IntersectionBuilder::new(&db)
.add_negative(Type::Intersection(sh_t))
// ~intersection => ~A | ~B
let negated_intersection = IntersectionBuilder::new(&db)
.add_negative(Type::Intersection(intersection))
.build()
.expect_union();
// should have as elements: (~Sized),(~Hashable)
let not_st = st.negate(&db);
let not_ht = ht.negate(&db);
assert_eq!(not_s_h_t.elements(&db), &[not_st, not_ht]);
// should have as elements ~A and ~B
let not_a = a.negate(&db);
let not_b = b.negate(&db);
assert_eq!(negated_intersection.elements(&db), &[not_a, not_b]);
}
#[test]
fn mixed_intersection_negation_distributes_over_union() {
let db = setup_db();
let it = KnownClass::Int.to_instance(&db);
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
let ht = typing_symbol(&db, "Hashable")
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
// s_not_h_t: Sized & ~Hashable
let s_not_h_t = IntersectionBuilder::new(&db)
.add_positive(st)
.add_negative(ht)
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
let int = KnownClass::Int.to_instance(&db);
// a_not_b: A & ~B
let a_not_b = IntersectionBuilder::new(&db)
.add_positive(a)
.add_negative(b)
.build()
.expect_intersection();
assert_eq!(s_not_h_t.pos_vec(&db), &[st]);
assert_eq!(s_not_h_t.neg_vec(&db), &[ht]);
assert_eq!(a_not_b.pos_vec(&db), &[a]);
assert_eq!(a_not_b.neg_vec(&db), &[b]);
// let's build int & ~(Sized & ~Hashable)
let tt = IntersectionBuilder::new(&db)
.add_positive(it)
.add_negative(Type::Intersection(s_not_h_t))
// let's build
// int & ~(A & ~B)
// = int & ~(A & ~B)
// = int & (~A | B)
// = (int & ~A) | (int & B)
let t = IntersectionBuilder::new(&db)
.add_positive(int)
.add_negative(Type::Intersection(a_not_b))
.build();
// int & ~(Sized & ~Hashable)
// -> int & (~Sized | Hashable)
// -> (int & ~Sized) | (int & Hashable)
assert_eq!(tt.display(&db).to_string(), "int & ~Sized | int & Hashable");
assert_eq!(t.display(&db).to_string(), "int & ~A | int & B");
}
#[test]

View File

@@ -289,7 +289,7 @@ struct DisplayMaybeNegatedType<'db> {
negated: bool,
}
impl<'db> Display for DisplayMaybeNegatedType<'db> {
impl Display for DisplayMaybeNegatedType<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.negated {
f.write_str("~")?;
@@ -319,7 +319,7 @@ pub(crate) struct DisplayTypeArray<'b, 'db> {
db: &'db dyn Db,
}
impl<'db> Display for DisplayTypeArray<'_, 'db> {
impl Display for DisplayTypeArray<'_, '_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.join(", ")
.entries(self.types.iter().map(|ty| ty.display(self.db)))
@@ -357,31 +357,10 @@ impl Display for DisplayStringLiteralType<'_> {
#[cfg(test)]
mod tests {
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_db::system::DbWithTestSystem;
use crate::db::tests::TestDb;
use crate::db::tests::setup_db;
use crate::types::{global_symbol, SliceLiteralType, StringLiteralType, Type, UnionType};
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<()> {

View File

@@ -1660,7 +1660,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let value_ty = self.expression_ty(value);
let name_ast_id = name.scoped_expression_id(self.db, self.scope());
let target_ty = match assignment.target() {
let mut target_ty = match assignment.target() {
TargetKind::Sequence(unpack) => {
let unpacked = infer_unpack_types(self.db, unpack);
// Only copy the diagnostics if this is the first assignment to avoid duplicating the
@@ -1674,6 +1674,13 @@ impl<'db> TypeInferenceBuilder<'db> {
TargetKind::Name => value_ty,
};
if let Some(known_instance) = file_to_module(self.db, definition.file(self.db))
.as_ref()
.and_then(|module| KnownInstanceType::try_from_module_and_symbol(module, &name.id))
{
target_ty = Type::KnownInstance(known_instance);
}
self.store_expression_type(name, target_ty);
self.add_binding(name.into(), definition, target_ty);
}
@@ -1893,12 +1900,11 @@ impl<'db> TypeInferenceBuilder<'db> {
is_async: _,
} = for_statement;
self.infer_standalone_expression(iter);
// TODO more complex assignment targets
if let ast::Expr::Name(name) = &**target {
self.infer_definition(name);
} else {
self.infer_standalone_expression(iter);
self.infer_expression(target);
}
self.infer_body(body);
@@ -4537,7 +4543,7 @@ impl<'db> TypeInferenceBuilder<'db> {
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) {
todo_type!()
} else {
Type::tuple(self.db, &[single_element_ty])
Type::tuple(self.db, [single_element_ty])
}
}
}
@@ -4642,6 +4648,18 @@ impl<'db> TypeInferenceBuilder<'db> {
);
Type::Unknown
}
KnownInstanceType::LiteralString => {
self.diagnostics.add(
subscript.into(),
"invalid-type-parameter",
format_args!(
"Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?",
known_instance.repr(self.db)
),
);
Type::Unknown
}
KnownInstanceType::Any => Type::Any,
}
}
@@ -5027,73 +5045,19 @@ fn perform_membership_test_comparison<'db>(
#[cfg(test)]
mod tests {
use anyhow::Context;
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::{self, PythonVersion};
use crate::db::tests::{setup_db, TestDb, TestDbBuilder};
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::FileScopeId;
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
use crate::types::check_types;
use crate::{HasTy, ProgramSettings, SemanticModel};
use crate::{HasTy, SemanticModel};
use ruff_db::files::{system_path_to_file, File};
use ruff_db::parsed::parsed_module;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_db::system::DbWithTestSystem;
use ruff_db::testing::assert_function_query_was_not_run;
use test_case::test_case;
use super::*;
fn setup_db_with_python_version(python_version: PythonVersion) -> 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: python_version,
search_paths: SearchPathSettings::new(src_root),
},
)
.expect("Valid search path settings");
db
}
fn setup_db() -> TestDb {
setup_db_with_python_version(PythonVersion::default())
}
fn setup_db_with_custom_typeshed<'a>(
typeshed: &str,
files: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> anyhow::Result<TestDb> {
let mut db = TestDb::new();
let src_root = SystemPathBuf::from("/src");
db.write_files(files)
.context("Failed to write test files")?;
Program::from_settings(
&db,
&ProgramSettings {
target_version: PythonVersion::default(),
search_paths: SearchPathSettings {
custom_typeshed: Some(SystemPathBuf::from(typeshed)),
..SearchPathSettings::new(src_root)
},
},
)
.context("Failed to create Program")?;
Ok(db)
}
#[track_caller]
fn assert_public_ty(db: &TestDb, file_name: &str, symbol_name: &str, expected: &str) {
let file = system_path_to_file(db, file_name).expect("file to exist");
@@ -5340,10 +5304,9 @@ mod tests {
Ok(())
}
#[test_case(PythonVersion::PY39, "ellipsis")]
#[test_case(PythonVersion::PY310, "EllipsisType")]
fn ellipsis_type(version: PythonVersion, expected_type: &str) -> anyhow::Result<()> {
let mut db = setup_db_with_python_version(version);
#[test]
fn ellipsis_type() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_dedented(
"src/a.py",
@@ -5352,7 +5315,8 @@ mod tests {
",
)?;
assert_public_ty(&db, "src/a.py", "x", expected_type);
// TODO: sys.version_info
assert_public_ty(&db, "src/a.py", "x", "EllipsisType | ellipsis");
Ok(())
}
@@ -5488,17 +5452,15 @@ mod tests {
#[test]
fn builtin_symbol_custom_stdlib() -> anyhow::Result<()> {
let db = setup_db_with_custom_typeshed(
"/typeshed",
[
("/src/a.py", "c = copyright"),
(
"/typeshed/stdlib/builtins.pyi",
"def copyright() -> None: ...",
),
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
],
)?;
let db = TestDbBuilder::new()
.with_custom_typeshed("/typeshed")
.with_file("/src/a.py", "c = copyright")
.with_file(
"/typeshed/stdlib/builtins.pyi",
"def copyright() -> None: ...",
)
.with_file("/typeshed/stdlib/VERSIONS", "builtins: 3.8-")
.build()?;
assert_public_ty(&db, "/src/a.py", "c", "Literal[copyright]");
@@ -5507,14 +5469,12 @@ mod tests {
#[test]
fn unknown_builtin_later_defined() -> anyhow::Result<()> {
let db = setup_db_with_custom_typeshed(
"/typeshed",
[
("/src/a.py", "x = foo"),
("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1"),
("/typeshed/stdlib/VERSIONS", "builtins: 3.8-"),
],
)?;
let db = TestDbBuilder::new()
.with_custom_typeshed("/typeshed")
.with_file("/src/a.py", "x = foo")
.with_file("/typeshed/stdlib/builtins.pyi", "foo = bar; bar = 1")
.with_file("/typeshed/stdlib/VERSIONS", "builtins: 3.8-")
.build()?;
assert_public_ty(&db, "/src/a.py", "x", "Unknown");

View File

@@ -374,10 +374,12 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::Literal
| KnownInstanceType::LiteralString
| KnownInstanceType::Union
| KnownInstanceType::NoReturn
| KnownInstanceType::Never
| KnownInstanceType::Optional => None,
KnownInstanceType::Any => Some(Self::Any),
},
}
}
@@ -405,6 +407,12 @@ impl<'db> ClassBase<'db> {
}
}
impl<'db> From<Class<'db>> for ClassBase<'db> {
fn from(value: Class<'db>) -> Self {
ClassBase::Class(value)
}
}
impl<'db> From<ClassBase<'db>> for Type<'db> {
fn from(value: ClassBase<'db>) -> Self {
match value {

View File

@@ -54,6 +54,7 @@ pub(crate) fn narrowing_constraint<'db>(
}
}
#[allow(clippy::ref_option)]
#[salsa::tracked(return_ref)]
fn all_narrowing_constraints_for_pattern<'db>(
db: &'db dyn Db,
@@ -62,6 +63,7 @@ fn all_narrowing_constraints_for_pattern<'db>(
NarrowingConstraintsBuilder::new(db, ConstraintNode::Pattern(pattern), true).finish()
}
#[allow(clippy::ref_option)]
#[salsa::tracked(return_ref)]
fn all_narrowing_constraints_for_expression<'db>(
db: &'db dyn Db,
@@ -70,6 +72,7 @@ fn all_narrowing_constraints_for_expression<'db>(
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), true).finish()
}
#[allow(clippy::ref_option)]
#[salsa::tracked(return_ref)]
fn all_negative_narrowing_constraints_for_expression<'db>(
db: &'db dyn Db,
@@ -291,8 +294,15 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
.chain(comparators)
.tuple_windows::<(&ruff_python_ast::Expr, &ruff_python_ast::Expr)>();
let mut constraints = NarrowingConstraints::default();
let mut last_rhs_ty: Option<Type> = None;
for (op, (left, right)) in std::iter::zip(&**ops, comparator_tuples) {
let lhs_ty = last_rhs_ty.unwrap_or_else(|| {
inference.expression_ty(left.scoped_expression_id(self.db, scope))
});
let rhs_ty = inference.expression_ty(right.scoped_expression_id(self.db, scope));
last_rhs_ty = Some(rhs_ty);
match left {
ast::Expr::Name(ast::ExprName {
@@ -327,6 +337,9 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
constraints.insert(symbol, ty);
}
}
ast::CmpOp::Eq if lhs_ty.is_literal_string() => {
constraints.insert(symbol, rhs_ty);
}
_ => {
// TODO other comparison types
}
@@ -385,46 +398,58 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
let scope = self.scope();
let inference = infer_expression_types(self.db, expression);
let callable_ty =
inference.expression_ty(expr_call.func.scoped_expression_id(self.db, scope));
// TODO: add support for PEP 604 union types on the right hand side of `isinstance`
// and `issubclass`, for example `isinstance(x, str | (int | float))`.
match inference
.expression_ty(expr_call.func.scoped_expression_id(self.db, scope))
.into_function_literal()
.and_then(|f| f.known(self.db))
.and_then(KnownFunction::constraint_function)
{
Some(function) if expr_call.arguments.keywords.is_empty() => {
if let [ast::Expr::Name(ast::ExprName { id, .. }), class_info] =
match callable_ty {
Type::FunctionLiteral(function_type) if expr_call.arguments.keywords.is_empty() => {
let function = function_type
.known(self.db)
.and_then(KnownFunction::constraint_function)?;
let [ast::Expr::Name(ast::ExprName { id, .. }), class_info] =
&*expr_call.arguments.args
{
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
else {
return None;
};
let class_info_ty =
inference.expression_ty(class_info.scoped_expression_id(self.db, scope));
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
let to_constraint = match function {
KnownConstraintFunction::IsInstance => {
|class_literal: ClassLiteralType<'db>| {
Type::instance(class_literal.class)
}
let class_info_ty =
inference.expression_ty(class_info.scoped_expression_id(self.db, scope));
let to_constraint = match function {
KnownConstraintFunction::IsInstance => {
|class_literal: ClassLiteralType<'db>| Type::instance(class_literal.class)
}
KnownConstraintFunction::IsSubclass => {
|class_literal: ClassLiteralType<'db>| {
Type::subclass_of(class_literal.class)
}
KnownConstraintFunction::IsSubclass => {
|class_literal: ClassLiteralType<'db>| {
Type::subclass_of(class_literal.class)
}
}
};
}
};
generate_classinfo_constraint(self.db, &class_info_ty, to_constraint).map(
|constraint| {
let mut constraints = NarrowingConstraints::default();
constraints.insert(symbol, constraint.negate_if(self.db, !is_positive));
constraints
},
)
} else {
None
}
generate_classinfo_constraint(self.db, &class_info_ty, to_constraint).map(
|constraint| {
let mut constraints = NarrowingConstraints::default();
constraints.insert(symbol, constraint.negate_if(self.db, !is_positive));
constraints
},
)
}
// for the expression `bool(E)`, we further narrow the type based on `E`
Type::ClassLiteral(class_type)
if expr_call.arguments.args.len() == 1
&& expr_call.arguments.keywords.is_empty()
&& class_type.class.is_known(self.db, KnownClass::Bool) =>
{
self.evaluate_expression_node_constraint(
&expr_call.arguments.args[0],
expression,
is_positive,
)
}
_ => None,
}

View File

@@ -0,0 +1,243 @@
//! This module contains quickcheck-based property tests for `Type`s.
//!
//! These tests are disabled by default, as they are non-deterministic and slow. You can
//! run them explicitly using:
//!
//! ```sh
//! cargo test -p red_knot_python_semantic -- --ignored types::property_tests::stable
//! ```
//!
//! The number of tests (default: 100) can be controlled by setting the `QUICKCHECK_TESTS`
//! environment variable. For example:
//!
//! ```sh
//! QUICKCHECK_TESTS=10000 cargo test …
//! ```
//!
//! If you want to run these tests for a longer period of time, it's advisable to run them
//! in release mode. As some tests are slower than others, it's advisable to run them in a
//! loop until they fail:
//!
//! ```sh
//! export QUICKCHECK_TESTS=100000
//! while cargo test --release -p red_knot_python_semantic -- \
//! --ignored types::property_tests::stable; do :; done
//! ```
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use super::tests::Ty;
use crate::db::tests::{setup_db, TestDb};
use crate::types::KnownClass;
use quickcheck::{Arbitrary, Gen};
fn arbitrary_core_type(g: &mut Gen) -> Ty {
// We could select a random integer here, but this would make it much less
// likely to explore interesting edge cases:
let int_lit = Ty::IntLiteral(*g.choose(&[-2, -1, 0, 1, 2]).unwrap());
let bool_lit = Ty::BooleanLiteral(bool::arbitrary(g));
g.choose(&[
Ty::Never,
Ty::Unknown,
Ty::None,
Ty::Any,
int_lit,
bool_lit,
Ty::StringLiteral(""),
Ty::StringLiteral("a"),
Ty::LiteralString,
Ty::BytesLiteral(""),
Ty::BytesLiteral("\x00"),
Ty::KnownClassInstance(KnownClass::Object),
Ty::KnownClassInstance(KnownClass::Str),
Ty::KnownClassInstance(KnownClass::Int),
Ty::KnownClassInstance(KnownClass::Bool),
Ty::KnownClassInstance(KnownClass::List),
Ty::KnownClassInstance(KnownClass::Tuple),
Ty::KnownClassInstance(KnownClass::FunctionType),
Ty::KnownClassInstance(KnownClass::SpecialForm),
Ty::KnownClassInstance(KnownClass::TypeVar),
Ty::KnownClassInstance(KnownClass::TypeAliasType),
Ty::KnownClassInstance(KnownClass::NoDefaultType),
Ty::TypingLiteral,
Ty::BuiltinClassLiteral("str"),
Ty::BuiltinClassLiteral("int"),
Ty::BuiltinClassLiteral("bool"),
Ty::BuiltinClassLiteral("object"),
])
.unwrap()
.clone()
}
/// Constructs an arbitrary type.
///
/// The `size` parameter controls the depth of the type tree. For example,
/// a simple type like `int` has a size of 0, `Union[int, str]` has a size
/// of 1, `tuple[int, Union[str, bytes]]` has a size of 2, etc.
fn arbitrary_type(g: &mut Gen, size: u32) -> Ty {
if size == 0 {
arbitrary_core_type(g)
} else {
match u32::arbitrary(g) % 4 {
0 => arbitrary_core_type(g),
1 => Ty::Union(
(0..*g.choose(&[2, 3]).unwrap())
.map(|_| arbitrary_type(g, size - 1))
.collect(),
),
2 => Ty::Tuple(
(0..*g.choose(&[0, 1, 2]).unwrap())
.map(|_| arbitrary_type(g, size - 1))
.collect(),
),
3 => Ty::Intersection {
pos: (0..*g.choose(&[0, 1, 2]).unwrap())
.map(|_| arbitrary_type(g, size - 1))
.collect(),
neg: (0..*g.choose(&[0, 1, 2]).unwrap())
.map(|_| arbitrary_type(g, size - 1))
.collect(),
},
_ => unreachable!(),
}
}
}
impl Arbitrary for Ty {
fn arbitrary(g: &mut Gen) -> Ty {
const MAX_SIZE: u32 = 2;
arbitrary_type(g, MAX_SIZE)
}
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
// This is incredibly naive. We can do much better here by
// trying various subsets of the elements in unions, tuples,
// and intersections. For now, we only try to shrink by
// reducing unions/tuples/intersections to a single element.
match self.clone() {
Ty::Union(types) => Box::new(types.into_iter()),
Ty::Tuple(types) => Box::new(types.into_iter()),
Ty::Intersection { pos, neg } => Box::new(pos.into_iter().chain(neg)),
_ => Box::new(std::iter::empty()),
}
}
}
static CACHED_DB: OnceLock<Arc<Mutex<TestDb>>> = OnceLock::new();
fn get_cached_db() -> MutexGuard<'static, TestDb> {
let db = CACHED_DB.get_or_init(|| Arc::new(Mutex::new(setup_db())));
db.lock().unwrap()
}
/// A macro to define a property test for types.
///
/// The `$test_name` identifier specifies the name of the test function. The `$db` identifier
/// is used to refer to the salsa database in the property to be tested. The actual property is
/// specified using the syntax:
///
/// forall types t1, t2, ..., tn . <property>`
///
/// where `t1`, `t2`, ..., `tn` are identifiers that represent arbitrary types, and `<property>`
/// is an expression using these identifiers.
///
macro_rules! type_property_test {
($test_name:ident, $db:ident, forall types $($types:ident),+ . $property:expr) => {
#[quickcheck_macros::quickcheck]
#[ignore]
fn $test_name($($types: crate::types::tests::Ty),+) -> bool {
let db_cached = super::get_cached_db();
let $db = &*db_cached;
$(let $types = $types.into_type($db);)+
$property
}
};
// A property test with a logical implication.
($name:ident, $db:ident, forall types $($types:ident),+ . $premise:expr => $conclusion:expr) => {
type_property_test!($name, $db, forall types $($types),+ . !($premise) || ($conclusion));
};
}
mod stable {
// `T` is equivalent to itself.
type_property_test!(
equivalent_to_is_reflexive, db,
forall types t. t.is_equivalent_to(db, t)
);
// `T` is a subtype of itself.
type_property_test!(
subtype_of_is_reflexive, db,
forall types t. t.is_subtype_of(db, t)
);
// `S <: T` and `T <: U` implies that `S <: U`.
type_property_test!(
subtype_of_is_transitive, db,
forall types s, t, u. s.is_subtype_of(db, t) && t.is_subtype_of(db, u) => s.is_subtype_of(db, u)
);
// `T` is not disjoint from itself, unless `T` is `Never`.
type_property_test!(
disjoint_from_is_irreflexive, db,
forall types t. t.is_disjoint_from(db, t) => t.is_never()
);
// `S` is disjoint from `T` implies that `T` is disjoint from `S`.
type_property_test!(
disjoint_from_is_symmetric, db,
forall types s, t. s.is_disjoint_from(db, t) == t.is_disjoint_from(db, s)
);
// `S <: T` implies that `S` is not disjoint from `T`, unless `S` is `Never`.
type_property_test!(
subtype_of_implies_not_disjoint_from, db,
forall types s, t. s.is_subtype_of(db, t) => !s.is_disjoint_from(db, t) || s.is_never()
);
// `T` can be assigned to itself.
type_property_test!(
assignable_to_is_reflexive, db,
forall types t. t.is_assignable_to(db, t)
);
// `S <: T` implies that `S` can be assigned to `T`.
type_property_test!(
subtype_of_implies_assignable_to, db,
forall types s, t. s.is_subtype_of(db, t) => s.is_assignable_to(db, t)
);
// If `T` is a singleton, it is also single-valued.
type_property_test!(
singleton_implies_single_valued, db,
forall types t. t.is_singleton(db) => t.is_single_valued(db)
);
// If `T` contains a gradual form, it should not participate in subtyping
type_property_test!(
non_fully_static_types_do_not_participate_in_subtyping, db,
forall types s, t. !s.is_fully_static(db) => !s.is_subtype_of(db, t) && !t.is_subtype_of(db, s)
);
}
/// This module contains property tests that currently lead to many false positives.
///
/// The reason for this is our insufficient understanding of equivalence of types. For
/// example, we currently consider `int | str` and `str | int` to be different types.
/// Similar issues exist for intersection types. Once this is resolved, we can move these
/// tests to the `stable` section. In the meantime, it can still be useful to run these
/// tests (using [`types::property_tests::flaky`]), to see if there are any new obvious bugs.
mod flaky {
// `S <: T` and `T <: S` implies that `S` is equivalent to `T`.
type_property_test!(
subtype_of_is_antisymmetric, db,
forall types s, t. s.is_subtype_of(db, t) && t.is_subtype_of(db, s) => s.is_equivalent_to(db, t)
);
// Negating `T` twice is equivalent to `T`.
type_property_test!(
double_negation_is_identity, db,
forall types t. t.negate(db).negate(db).is_equivalent_to(db, t)
);
}

View File

@@ -189,32 +189,9 @@ impl<'db> Parameter<'db> {
#[cfg(test)]
mod tests {
use super::*;
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::db::tests::{setup_db, TestDb};
use crate::types::{global_symbol, FunctionType};
use crate::ProgramSettings;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
pub(crate) 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
}
use ruff_db::system::DbWithTestSystem;
#[track_caller]
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {

View File

@@ -95,7 +95,8 @@ impl<'db> Unpacker<'db> {
// there would be a cost and it's not clear that it's worth it.
let value_ty = Type::tuple(
self.db,
&vec![Type::LiteralString; string_literal_ty.len(self.db)],
std::iter::repeat(Type::LiteralString)
.take(string_literal_ty.python_len(self.db)),
);
self.unpack(target, value_ty, scope);
}

View File

@@ -1,7 +1,6 @@
use std::ffi::OsStr;
use std::path::Path;
use camino::Utf8Path;
use dir_test::{dir_test, Fixture};
use std::path::Path;
/// See `crates/red_knot_test/README.md` for documentation on these tests.
#[dir_test(
@@ -10,16 +9,46 @@ use dir_test::{dir_test, Fixture};
)]
#[allow(clippy::needless_pass_by_value)]
fn mdtest(fixture: Fixture<&str>) {
let fixture_path = Path::new(fixture.path());
let fixture_path = Utf8Path::new(fixture.path());
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let workspace_root = crate_dir.parent().and_then(Path::parent).unwrap();
let workspace_root = crate_dir.ancestors().nth(2).unwrap();
let long_title = fixture_path
.strip_prefix(workspace_root)
.unwrap()
.to_str()
.unwrap();
let short_title = fixture_path.file_name().and_then(OsStr::to_str).unwrap();
let long_title = fixture_path.strip_prefix(workspace_root).unwrap();
let short_title = fixture_path.file_name().unwrap();
red_knot_test::run(fixture_path, long_title, short_title);
let test_name = test_name("mdtest", fixture_path);
red_knot_test::run(fixture_path, long_title.as_str(), short_title, &test_name);
}
/// Constructs the test name used for individual markdown files
///
/// This code is copied from <https://github.com/fe-lang/dir-test/blob/1c0f41c480a3490bc2653a043ff6e3f8085a1f47/macros/src/lib.rs#L104-L138>
/// and should be updated if they diverge
fn test_name(test_func_name: &str, fixture_path: &Utf8Path) -> String {
assert!(fixture_path.is_file());
let dir_path = format!("{}/resources/mdtest", std::env!("CARGO_MANIFEST_DIR"));
let rel_path = fixture_path.strip_prefix(dir_path).unwrap();
assert!(rel_path.is_relative());
let mut test_name = test_func_name.to_owned();
test_name.push_str("__");
for component in rel_path.parent().unwrap().components() {
let component = component
.as_str()
.replace(|c: char| c.is_ascii_punctuation(), "_");
test_name.push_str(&component);
test_name.push('_');
}
test_name.push_str(
&rel_path
.file_stem()
.unwrap()
.replace(|c: char| c.is_ascii_punctuation(), "_"),
);
test_name
}

View File

@@ -26,7 +26,7 @@ pub(crate) struct Requester<'s> {
response_handlers: FxHashMap<lsp_server::RequestId, ResponseBuilder<'s>>,
}
impl<'s> Client<'s> {
impl Client<'_> {
pub(super) fn new(sender: ClientSender) -> Self {
Self {
notifier: Notifier(sender.clone()),

View File

@@ -20,6 +20,7 @@ ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
anyhow = { workspace = true }
camino = { workspace = true }
colored = { workspace = true }
memchr = { workspace = true }
regex = { workspace = true }

View File

@@ -184,8 +184,11 @@ The tests are run independently, in independent in-memory file systems and with
[Salsa](https://github.com/salsa-rs/salsa) databases. This means that each is a from-scratch run of
the type checker, with no data persisting from any previous test.
Due to `cargo test` limitations, an entire test suite (Markdown file) is run as a single Rust test,
so it's not possible to select individual tests within it to run.
It is possible to filter to individual tests within a single markdown file using the
`MDTEST_TEST_FILTER` environment variable. This variable will match any tests which contain the
value as a case-sensitive substring in its name. An example test name is
`unpacking.md - Unpacking - Tuple - Multiple assignment`, which contains the name of the markdown
file and its parent headers joined together with hyphens.
## Structured test suites

View File

@@ -1,3 +1,4 @@
use camino::Utf8Path;
use colored::Colorize;
use parser as test_parser;
use red_knot_python_semantic::types::check_types;
@@ -7,7 +8,6 @@ use ruff_db::parsed::parsed_module;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_source_file::LineIndex;
use ruff_text_size::TextSize;
use std::path::Path;
mod assertion;
mod db;
@@ -15,23 +15,30 @@ mod diagnostic;
mod matcher;
mod parser;
const MDTEST_TEST_FILTER: &str = "MDTEST_TEST_FILTER";
/// Run `path` as a markdown test suite with given `title`.
///
/// Panic on test failure, and print failure details.
#[allow(clippy::print_stdout)]
pub fn run(path: &Path, long_title: &str, short_title: &str) {
pub fn run(path: &Utf8Path, long_title: &str, short_title: &str, test_name: &str) {
let source = std::fs::read_to_string(path).unwrap();
let suite = match test_parser::parse(short_title, &source) {
Ok(suite) => suite,
Err(err) => {
panic!("Error parsing `{}`: {err}", path.to_str().unwrap())
panic!("Error parsing `{path}`: {err}")
}
};
let mut db = db::Db::setup(SystemPathBuf::from("/src"));
let filter = std::env::var(MDTEST_TEST_FILTER).ok();
let mut any_failures = false;
for test in suite.tests() {
if filter.as_ref().is_some_and(|f| !test.name().contains(f)) {
continue;
}
// Remove all files so that the db is in a "fresh" state.
db.memory_file_system().remove_all();
Files::sync_all(&mut db);
@@ -54,6 +61,15 @@ pub fn run(path: &Path, long_title: &str, short_title: &str) {
}
}
}
println!(
"\nTo rerun this specific test, set the environment variable: {MDTEST_TEST_FILTER}=\"{}\"",
test.name()
);
println!(
"{MDTEST_TEST_FILTER}=\"{}\" cargo test -p red_knot_python_semantic --test mdtest -- {test_name}",
test.name()
);
}
}

View File

@@ -1 +1 @@
5052fa2f18db4493892e0f2775030683c9d06531
0a2da01946a406ede42e9c66f416a7e7758991d6

View File

@@ -33,6 +33,7 @@ _contextvars: 3.7-
_csv: 3.0-
_ctypes: 3.0-
_curses: 3.0-
_curses_panel: 3.0-
_dbm: 3.0-
_decimal: 3.3-
_dummy_thread: 3.0-3.8
@@ -40,6 +41,7 @@ _dummy_threading: 3.0-3.8
_frozen_importlib: 3.0-
_frozen_importlib_external: 3.5-
_gdbm: 3.0-
_hashlib: 3.0-
_heapq: 3.0-
_imp: 3.0-
_interpchannels: 3.13-
@@ -52,6 +54,7 @@ _lsprof: 3.0-
_lzma: 3.3-
_markupbase: 3.0-
_msi: 3.0-3.12
_multibytecodec: 3.0-
_operator: 3.4-
_osx_support: 3.0-
_posixsubprocess: 3.2-
@@ -139,6 +142,12 @@ doctest: 3.0-
dummy_threading: 3.0-3.8
email: 3.0-
encodings: 3.0-
encodings.cp1125: 3.4-
encodings.cp273: 3.4-
encodings.cp858: 3.2-
encodings.koi8_t: 3.5-
encodings.kz1048: 3.5-
encodings.mac_centeuro: 3.0-3.8
ensurepip: 3.0-
enum: 3.4-
errno: 3.0-

View File

@@ -2,10 +2,13 @@ import codecs
import sys
from _typeshed import ReadableBuffer
from collections.abc import Callable
from typing import Literal, overload
from typing import Literal, final, overload, type_check_only
from typing_extensions import TypeAlias
# This type is not exposed; it is defined in unicodeobject.c
# At runtime it calls itself builtins.EncodingMap
@final
@type_check_only
class _EncodingMap:
def size(self) -> int: ...

View File

@@ -73,6 +73,7 @@ _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers.
@final
class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented
def __eq__(self, value: object, /) -> bool: ...
def __reversed__(self) -> Iterator[_KT_co]: ...
if sys.version_info >= (3, 13):
def isdisjoint(self, other: Iterable[_KT_co], /) -> bool: ...
if sys.version_info >= (3, 10):
@@ -81,6 +82,7 @@ class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented
@final
class dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]): # undocumented
def __reversed__(self) -> Iterator[_VT_co]: ...
if sys.version_info >= (3, 10):
@property
def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ...
@@ -88,6 +90,7 @@ class dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]): # undocumented
@final
class dict_items(ItemsView[_KT_co, _VT_co]): # undocumented
def __eq__(self, value: object, /) -> bool: ...
def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ...
if sys.version_info >= (3, 13):
def isdisjoint(self, other: Iterable[tuple[_KT_co, _VT_co]], /) -> bool: ...
if sys.version_info >= (3, 10):

View File

@@ -1,9 +1,9 @@
import csv
import sys
from _typeshed import SupportsWrite
from collections.abc import Iterable, Iterator
from typing import Any, Final
from typing_extensions import TypeAlias
from collections.abc import Iterable
from typing import Any, Final, type_check_only
from typing_extensions import Self, TypeAlias
__version__: Final[str]
@@ -45,17 +45,47 @@ class Dialect:
strict: bool = False,
) -> None: ...
class _reader(Iterator[list[str]]):
@property
def dialect(self) -> Dialect: ...
line_num: int
def __next__(self) -> list[str]: ...
if sys.version_info >= (3, 10):
# This class calls itself _csv.reader.
class Reader:
@property
def dialect(self) -> Dialect: ...
line_num: int
def __iter__(self) -> Self: ...
def __next__(self) -> list[str]: ...
class _writer:
@property
def dialect(self) -> Dialect: ...
def writerow(self, row: Iterable[Any]) -> Any: ...
def writerows(self, rows: Iterable[Iterable[Any]]) -> None: ...
# This class calls itself _csv.writer.
class Writer:
@property
def dialect(self) -> Dialect: ...
if sys.version_info >= (3, 13):
def writerow(self, row: Iterable[Any], /) -> Any: ...
def writerows(self, rows: Iterable[Iterable[Any]], /) -> None: ...
else:
def writerow(self, row: Iterable[Any]) -> Any: ...
def writerows(self, rows: Iterable[Iterable[Any]]) -> None: ...
# For the return types below.
# These aliases can be removed when typeshed drops support for 3.9.
_reader = Reader
_writer = Writer
else:
# This class is not exposed. It calls itself _csv.reader.
@type_check_only
class _reader:
@property
def dialect(self) -> Dialect: ...
line_num: int
def __iter__(self) -> Self: ...
def __next__(self) -> list[str]: ...
# This class is not exposed. It calls itself _csv.writer.
@type_check_only
class _writer:
@property
def dialect(self) -> Dialect: ...
def writerow(self, row: Iterable[Any]) -> Any: ...
def writerows(self, rows: Iterable[Iterable[Any]]) -> None: ...
def writer(
csvfile: SupportsWrite[str],

View File

@@ -1,9 +1,10 @@
import _typeshed
import sys
from _typeshed import ReadableBuffer, WriteableBuffer
from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer
from abc import abstractmethod
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
from ctypes import CDLL, ArgumentError as ArgumentError, c_void_p
from typing import Any, ClassVar, Generic, TypeVar, overload
from typing import Any, ClassVar, Generic, TypeVar, final, overload, type_check_only
from typing_extensions import Self, TypeAlias
if sys.version_info >= (3, 9):
@@ -47,46 +48,79 @@ if sys.platform == "win32":
def LoadLibrary(name: str, load_flags: int = 0, /) -> int: ...
def FreeLibrary(handle: int, /) -> None: ...
class _CDataMeta(type):
# By default mypy complains about the following two methods, because strictly speaking cls
# might not be a Type[_CT]. However this can never actually happen, because the only class that
# uses _CDataMeta as its metaclass is _CData. So it's safe to ignore the errors here.
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
else:
def dlclose(handle: int, /) -> None: ...
# The default for flag is RTLD_GLOBAL|RTLD_LOCAL, which is platform dependent.
def dlopen(name: StrOrBytesPath, flag: int = ..., /) -> int: ...
def dlsym(handle: int, name: str, /) -> int: ...
class _CData(metaclass=_CDataMeta):
if sys.version_info >= (3, 13):
# This class is not exposed. It calls itself _ctypes.CType_Type.
@type_check_only
class _CType_Type(type):
# By default mypy complains about the following two methods, because strictly speaking cls
# might not be a Type[_CT]. However this doesn't happen because this is only a
# metaclass for subclasses of _CData.
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
_CTypeBaseType = _CType_Type
else:
_CTypeBaseType = type
# This class is not exposed.
@type_check_only
class _CData:
_b_base_: int
_b_needsfree_: bool
_objects: Mapping[Any, int] | None
# At runtime the following classmethods are available only on classes, not
# on instances. This can't be reflected properly in the type system:
#
# Structure.from_buffer(...) # valid at runtime
# Structure(...).from_buffer(...) # invalid at runtime
#
@classmethod
def from_buffer(cls, source: WriteableBuffer, offset: int = ...) -> Self: ...
@classmethod
def from_buffer_copy(cls, source: ReadableBuffer, offset: int = ...) -> Self: ...
@classmethod
def from_address(cls, address: int) -> Self: ...
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
@classmethod
def in_dll(cls, library: CDLL, name: str) -> Self: ...
def __buffer__(self, flags: int, /) -> memoryview: ...
def __release_buffer__(self, buffer: memoryview, /) -> None: ...
def __ctypes_from_outparam__(self, /) -> Self: ...
class _SimpleCData(_CData, Generic[_T]):
# this is a union of all the subclasses of _CData, which is useful because of
# the methods that are present on each of those subclasses which are not present
# on _CData itself.
_CDataType: TypeAlias = _SimpleCData[Any] | _Pointer[Any] | CFuncPtr | Union | Structure | Array[Any]
# This class is not exposed. It calls itself _ctypes.PyCSimpleType.
@type_check_only
class _PyCSimpleType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(self: type[_CT], value: int, /) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(self: type[_CT], value: int, /) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class _SimpleCData(_CData, Generic[_T], metaclass=_PyCSimpleType):
value: _T
# The TypeVar can be unsolved here,
# but we can't use overloads without creating many, many mypy false-positive errors
def __init__(self, value: _T = ...) -> None: ... # pyright: ignore[reportInvalidTypeVarUse]
def __ctypes_from_outparam__(self, /) -> _T: ... # type: ignore[override]
class _CanCastTo(_CData): ...
class _PointerLike(_CanCastTo): ...
class _Pointer(_PointerLike, _CData, Generic[_CT]):
# This type is not exposed. It calls itself _ctypes.PyCPointerType.
@type_check_only
class _PyCPointerType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
def set_type(self, type: Any, /) -> None: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class _Pointer(_PointerLike, _CData, Generic[_CT], metaclass=_PyCPointerType):
_type_: type[_CT]
contents: _CT
@overload
@@ -105,16 +139,32 @@ def POINTER(type: None, /) -> type[c_void_p]: ...
def POINTER(type: type[_CT], /) -> type[_Pointer[_CT]]: ...
def pointer(obj: _CT, /) -> _Pointer[_CT]: ...
# This class is not exposed. It calls itself _ctypes.CArgObject.
@final
@type_check_only
class _CArgObject: ...
def byref(obj: _CData, offset: int = ...) -> _CArgObject: ...
def byref(obj: _CData | _CDataType, offset: int = ...) -> _CArgObject: ...
_ECT: TypeAlias = Callable[[_CData | None, CFuncPtr, tuple[_CData, ...]], _CData]
_ECT: TypeAlias = Callable[[_CData | _CDataType | None, CFuncPtr, tuple[_CData | _CDataType, ...]], _CDataType]
_PF: TypeAlias = tuple[int] | tuple[int, str | None] | tuple[int, str | None, Any]
class CFuncPtr(_PointerLike, _CData):
restype: type[_CData] | Callable[[int], Any] | None
argtypes: Sequence[type[_CData]]
# This class is not exposed. It calls itself _ctypes.PyCFuncPtrType.
@type_check_only
class _PyCFuncPtrType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class CFuncPtr(_PointerLike, _CData, metaclass=_PyCFuncPtrType):
restype: type[_CDataType] | Callable[[int], Any] | None
argtypes: Sequence[type[_CDataType]]
errcheck: _ECT
# Abstract attribute that must be defined on subclasses
_flags_: ClassVar[int]
@@ -129,7 +179,7 @@ class CFuncPtr(_PointerLike, _CData):
if sys.platform == "win32":
@overload
def __init__(
self, vtbl_index: int, name: str, paramflags: tuple[_PF, ...] | None = ..., iid: _CData | None = ..., /
self, vtbl_index: int, name: str, paramflags: tuple[_PF, ...] | None = ..., iid: _CData | _CDataType | None = ..., /
) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
@@ -137,30 +187,95 @@ class CFuncPtr(_PointerLike, _CData):
_GetT = TypeVar("_GetT")
_SetT = TypeVar("_SetT")
# This class is not exposed. It calls itself _ctypes.CField.
@final
@type_check_only
class _CField(Generic[_CT, _GetT, _SetT]):
offset: int
size: int
@overload
def __get__(self, instance: None, owner: type[Any] | None, /) -> Self: ...
@overload
def __get__(self, instance: Any, owner: type[Any] | None, /) -> _GetT: ...
if sys.version_info >= (3, 10):
@overload
def __get__(self, instance: None, owner: type[Any] | None = None, /) -> Self: ...
@overload
def __get__(self, instance: Any, owner: type[Any] | None = None, /) -> _GetT: ...
else:
@overload
def __get__(self, instance: None, owner: type[Any] | None, /) -> Self: ...
@overload
def __get__(self, instance: Any, owner: type[Any] | None, /) -> _GetT: ...
def __set__(self, instance: Any, value: _SetT, /) -> None: ...
class _StructUnionMeta(_CDataMeta):
_fields_: Sequence[tuple[str, type[_CData]] | tuple[str, type[_CData], int]]
_pack_: int
_anonymous_: Sequence[str]
# This class is not exposed. It calls itself _ctypes.UnionType.
@type_check_only
class _UnionType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
# At runtime, various attributes are created on a Union subclass based
# on its _fields_. This method doesn't exist, but represents those
# dynamically created attributes.
def __getattr__(self, name: str) -> _CField[Any, Any, Any]: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class Union(_CData, metaclass=_UnionType):
_fields_: ClassVar[Sequence[tuple[str, type[_CDataType]] | tuple[str, type[_CDataType], int]]]
_pack_: ClassVar[int]
_anonymous_: ClassVar[Sequence[str]]
if sys.version_info >= (3, 13):
_align_: ClassVar[int]
class _StructUnionBase(_CData, metaclass=_StructUnionMeta):
def __init__(self, *args: Any, **kw: Any) -> None: ...
def __getattr__(self, name: str) -> Any: ...
def __setattr__(self, name: str, value: Any) -> None: ...
class Union(_StructUnionBase): ...
class Structure(_StructUnionBase): ...
# This class is not exposed. It calls itself _ctypes.PyCStructType.
@type_check_only
class _PyCStructType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
# At runtime, various attributes are created on a Structure subclass based
# on its _fields_. This method doesn't exist, but represents those
# dynamically created attributes.
def __getattr__(self, name: str) -> _CField[Any, Any, Any]: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class Array(_CData, Generic[_CT]):
class Structure(_CData, metaclass=_PyCStructType):
_fields_: ClassVar[Sequence[tuple[str, type[_CDataType]] | tuple[str, type[_CDataType], int]]]
_pack_: ClassVar[int]
_anonymous_: ClassVar[Sequence[str]]
if sys.version_info >= (3, 13):
_align_: ClassVar[int]
def __init__(self, *args: Any, **kw: Any) -> None: ...
def __getattr__(self, name: str) -> Any: ...
def __setattr__(self, name: str, value: Any) -> None: ...
# This class is not exposed. It calls itself _ctypes.PyCArrayType.
@type_check_only
class _PyCArrayType(_CTypeBaseType):
def from_address(self: type[_typeshed.Self], value: int, /) -> _typeshed.Self: ...
def from_buffer(self: type[_typeshed.Self], obj: WriteableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_buffer_copy(self: type[_typeshed.Self], buffer: ReadableBuffer, offset: int = 0, /) -> _typeshed.Self: ...
def from_param(self: type[_typeshed.Self], value: Any, /) -> _typeshed.Self | _CArgObject: ...
def in_dll(self: type[_typeshed.Self], dll: CDLL, name: str, /) -> _typeshed.Self: ...
if sys.version_info < (3, 13):
# Inherited from CType_Type starting on 3.13
def __mul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __rmul__(cls: type[_CT], other: int) -> type[Array[_CT]]: ... # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
class Array(_CData, Generic[_CT], metaclass=_PyCArrayType):
@property
@abstractmethod
def _length_(self) -> int: ...
@@ -205,9 +320,15 @@ class Array(_CData, Generic[_CT]):
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def addressof(obj: _CData, /) -> int: ...
def alignment(obj_or_type: _CData | type[_CData], /) -> int: ...
def addressof(obj: _CData | _CDataType, /) -> int: ...
def alignment(obj_or_type: _CData | _CDataType | type[_CData | _CDataType], /) -> int: ...
def get_errno() -> int: ...
def resize(obj: _CData, size: int, /) -> None: ...
def resize(obj: _CData | _CDataType, size: int, /) -> None: ...
def set_errno(value: int, /) -> int: ...
def sizeof(obj_or_type: _CData | type[_CData], /) -> int: ...
def sizeof(obj_or_type: _CData | _CDataType | type[_CData | _CDataType], /) -> int: ...
def PyObj_FromPtr(address: int, /) -> Any: ...
def Py_DECREF(o: _T, /) -> _T: ...
def Py_INCREF(o: _T, /) -> _T: ...
def buffer_info(o: _CData | _CDataType | type[_CData | _CDataType], /) -> tuple[str, int, tuple[int, ...]]: ...
def call_cdeclfunction(address: int, arguments: tuple[Any, ...], /) -> Any: ...
def call_function(address: int, arguments: tuple[Any, ...], /) -> Any: ...

View File

@@ -1,6 +1,7 @@
import sys
from _typeshed import ReadOnlyBuffer, SupportsRead
from typing import IO, Any, NamedTuple, final, overload
from curses import _ncurses_version
from typing import IO, Any, final, overload
from typing_extensions import TypeAlias
# NOTE: This module is ordinarily only available on Unix, but the windows-curses
@@ -549,9 +550,4 @@ class window: # undocumented
@overload
def vline(self, y: int, x: int, ch: _ChType, n: int) -> None: ...
class _ncurses_version(NamedTuple):
major: int
minor: int
patch: int
ncurses_version: _ncurses_version

View File

@@ -0,0 +1,27 @@
from _curses import window
from typing import final
__version__: str
version: str
class error(Exception): ...
@final
class panel:
def above(self) -> panel: ...
def below(self) -> panel: ...
def bottom(self) -> None: ...
def hidden(self) -> bool: ...
def hide(self) -> None: ...
def move(self, y: int, x: int, /) -> None: ...
def replace(self, win: window, /) -> None: ...
def set_userptr(self, obj: object, /) -> None: ...
def show(self) -> None: ...
def top(self) -> None: ...
def userptr(self) -> object: ...
def window(self) -> window: ...
def bottom_panel() -> panel: ...
def new_panel(win: window, /) -> panel: ...
def top_panel() -> panel: ...
def update_panels() -> panel: ...

View File

@@ -1,7 +1,7 @@
import sys
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
from types import TracebackType
from typing import TypeVar, overload
from typing import TypeVar, final, overload, type_check_only
from typing_extensions import Self, TypeAlias
if sys.platform != "win32":
@@ -13,6 +13,8 @@ if sys.platform != "win32":
library: str
# Actual typename dbm, not exposed by the implementation
@final
@type_check_only
class _dbm:
def close(self) -> None: ...
if sys.version_info >= (3, 13):
@@ -22,18 +24,17 @@ if sys.platform != "win32":
def __setitem__(self, key: _KeyType, value: _ValueType) -> None: ...
def __delitem__(self, key: _KeyType) -> None: ...
def __len__(self) -> int: ...
def __del__(self) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
) -> None: ...
@overload
def get(self, k: _KeyType) -> bytes | None: ...
def get(self, k: _KeyType, /) -> bytes | None: ...
@overload
def get(self, k: _KeyType, default: _T) -> bytes | _T: ...
def get(self, k: _KeyType, default: _T, /) -> bytes | _T: ...
def keys(self) -> list[bytes]: ...
def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ...
# Don't exist at runtime
def setdefault(self, k: _KeyType, default: _ValueType = ..., /) -> bytes: ...
# This isn't true, but the class can't be instantiated. See #13024
__new__: None # type: ignore[assignment]
__init__: None # type: ignore[assignment]

View File

@@ -17,20 +17,13 @@ from decimal import (
Rounded as Rounded,
Subnormal as Subnormal,
Underflow as Underflow,
_ContextManager,
)
from types import TracebackType
from typing import Final
from typing_extensions import TypeAlias
_TrapType: TypeAlias = type[DecimalException]
class _ContextManager:
new_context: Context
saved_context: Context
def __init__(self, new_context: Context) -> None: ...
def __enter__(self) -> Context: ...
def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ...
__version__: Final[str]
__libmpdec_version__: Final[str]

View File

@@ -0,0 +1,80 @@
import sys
from _typeshed import ReadableBuffer
from collections.abc import Callable
from types import ModuleType
from typing import AnyStr, final, overload
from typing_extensions import Self, TypeAlias
_DigestMod: TypeAlias = str | Callable[[], HASH] | ModuleType | None
openssl_md_meth_names: frozenset[str]
class HASH:
@property
def digest_size(self) -> int: ...
@property
def block_size(self) -> int: ...
@property
def name(self) -> str: ...
def copy(self) -> Self: ...
def digest(self) -> bytes: ...
def hexdigest(self) -> str: ...
def update(self, obj: ReadableBuffer, /) -> None: ...
if sys.version_info >= (3, 10):
class UnsupportedDigestmodError(ValueError): ...
if sys.version_info >= (3, 9):
class HASHXOF(HASH):
def digest(self, length: int) -> bytes: ... # type: ignore[override]
def hexdigest(self, length: int) -> str: ... # type: ignore[override]
@final
class HMAC:
@property
def digest_size(self) -> int: ...
@property
def block_size(self) -> int: ...
@property
def name(self) -> str: ...
def copy(self) -> Self: ...
def digest(self) -> bytes: ...
def hexdigest(self) -> str: ...
def update(self, msg: ReadableBuffer) -> None: ...
@overload
def compare_digest(a: ReadableBuffer, b: ReadableBuffer, /) -> bool: ...
@overload
def compare_digest(a: AnyStr, b: AnyStr, /) -> bool: ...
def get_fips_mode() -> int: ...
def hmac_new(key: bytes | bytearray, msg: ReadableBuffer = b"", digestmod: _DigestMod = None) -> HMAC: ...
def new(name: str, string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_md5(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha1(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha3_224(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha3_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha3_384(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_sha3_512(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASH: ...
def openssl_shake_128(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ...
def openssl_shake_256(string: ReadableBuffer = b"", *, usedforsecurity: bool = True) -> HASHXOF: ...
else:
def new(name: str, string: ReadableBuffer = b"") -> HASH: ...
def openssl_md5(string: ReadableBuffer = b"") -> HASH: ...
def openssl_sha1(string: ReadableBuffer = b"") -> HASH: ...
def openssl_sha224(string: ReadableBuffer = b"") -> HASH: ...
def openssl_sha256(string: ReadableBuffer = b"") -> HASH: ...
def openssl_sha384(string: ReadableBuffer = b"") -> HASH: ...
def openssl_sha512(string: ReadableBuffer = b"") -> HASH: ...
def hmac_digest(key: bytes | bytearray, msg: ReadableBuffer, digest: str) -> bytes: ...
def pbkdf2_hmac(
hash_name: str, password: ReadableBuffer, salt: ReadableBuffer, iterations: int, dklen: int | None = None
) -> bytes: ...
def scrypt(
password: ReadableBuffer, *, salt: ReadableBuffer, n: int, r: int, p: int, maxmem: int = 0, dklen: int = 64
) -> bytes: ...

View File

@@ -45,5 +45,6 @@ class make_scanner:
def __init__(self, context: make_scanner) -> None: ...
def __call__(self, string: str, index: int) -> tuple[Any, int]: ...
def encode_basestring(s: str, /) -> str: ...
def encode_basestring_ascii(s: str, /) -> str: ...
def scanstring(string: str, end: int, strict: bool = ...) -> tuple[str, int]: ...

View File

@@ -0,0 +1,44 @@
from _typeshed import ReadableBuffer
from codecs import _ReadableStream, _WritableStream
from collections.abc import Iterable
from typing import final, type_check_only
# This class is not exposed. It calls itself _multibytecodec.MultibyteCodec.
@final
@type_check_only
class _MultibyteCodec:
def decode(self, input: ReadableBuffer, errors: str | None = None) -> str: ...
def encode(self, input: str, errors: str | None = None) -> bytes: ...
class MultibyteIncrementalDecoder:
errors: str
def __init__(self, errors: str = "strict") -> None: ...
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
def getstate(self) -> tuple[bytes, int]: ...
def reset(self) -> None: ...
def setstate(self, state: tuple[bytes, int], /) -> None: ...
class MultibyteIncrementalEncoder:
errors: str
def __init__(self, errors: str = "strict") -> None: ...
def encode(self, input: str, final: bool = False) -> bytes: ...
def getstate(self) -> int: ...
def reset(self) -> None: ...
def setstate(self, state: int, /) -> None: ...
class MultibyteStreamReader:
errors: str
stream: _ReadableStream
def __init__(self, stream: _ReadableStream, errors: str = "strict") -> None: ...
def read(self, sizeobj: int | None = None, /) -> str: ...
def readline(self, sizeobj: int | None = None, /) -> str: ...
def readlines(self, sizehintobj: int | None = None, /) -> list[str]: ...
def reset(self) -> None: ...
class MultibyteStreamWriter:
errors: str
stream: _WritableStream
def __init__(self, stream: _WritableStream, errors: str = "strict") -> None: ...
def reset(self) -> None: ...
def write(self, strobj: str, /) -> None: ...
def writelines(self, lines: Iterable[str], /) -> None: ...

View File

@@ -4,7 +4,6 @@ from collections.abc import Callable, Sequence
from typing import SupportsIndex
if sys.platform != "win32":
def cloexec_pipe() -> tuple[int, int]: ...
def fork_exec(
args: Sequence[StrOrBytesPath] | None,
executable_list: Sequence[bytes],

View File

@@ -3,7 +3,7 @@ from _typeshed import ReadableBuffer, WriteableBuffer
from collections.abc import Iterable
from socket import error as error, gaierror as gaierror, herror as herror, timeout as timeout
from typing import Any, SupportsIndex, overload
from typing_extensions import TypeAlias
from typing_extensions import CapsuleType, TypeAlias
_CMSG: TypeAlias = tuple[int, int, bytes]
_CMSGArg: TypeAlias = tuple[int, int, ReadableBuffer]
@@ -72,7 +72,8 @@ SO_SNDBUF: int
SO_SNDLOWAT: int
SO_SNDTIMEO: int
SO_TYPE: int
SO_USELOOPBACK: int
if sys.platform != "linux":
SO_USELOOPBACK: int
if sys.platform == "win32":
SO_EXCLUSIVEADDRUSE: int
if sys.platform != "win32":
@@ -87,7 +88,10 @@ if sys.platform != "win32" and sys.platform != "darwin":
SO_PEERSEC: int
SO_PRIORITY: int
SO_PROTOCOL: int
if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "linux":
SO_SETFIB: int
if sys.platform == "linux" and sys.version_info >= (3, 13):
SO_BINDTOIFINDEX: int
SOMAXCONN: int
@@ -99,27 +103,32 @@ MSG_TRUNC: int
MSG_WAITALL: int
if sys.platform != "win32":
MSG_DONTWAIT: int
MSG_EOF: int
MSG_EOR: int
MSG_NOSIGNAL: int # Sometimes this exists on darwin, sometimes not
if sys.platform != "darwin":
MSG_BCAST: int
MSG_ERRQUEUE: int
if sys.platform == "win32":
MSG_BCAST: int
MSG_MCAST: int
if sys.platform != "win32" and sys.platform != "darwin":
MSG_BTAG: int
MSG_CMSG_CLOEXEC: int
MSG_CONFIRM: int
MSG_ETAG: int
MSG_FASTOPEN: int
MSG_MORE: int
if sys.platform != "win32" and sys.platform != "linux":
MSG_EOF: int
if sys.platform != "win32" and sys.platform != "linux" and sys.platform != "darwin":
MSG_NOTIFICATION: int
MSG_BTAG: int # Not FreeBSD either
MSG_ETAG: int # Not FreeBSD either
SOL_IP: int
SOL_SOCKET: int
SOL_TCP: int
SOL_UDP: int
if sys.platform != "win32" and sys.platform != "darwin":
# Defined in socket.h for Linux, but these aren't always present for
# some reason.
SOL_ATALK: int
SOL_AX25: int
SOL_HCI: int
@@ -128,10 +137,11 @@ if sys.platform != "win32" and sys.platform != "darwin":
SOL_ROSE: int
if sys.platform != "win32":
SCM_CREDS: int
SCM_RIGHTS: int
if sys.platform != "win32" and sys.platform != "darwin":
SCM_CREDENTIALS: int
if sys.platform != "win32" and sys.platform != "linux":
SCM_CREDS: int
IPPROTO_ICMP: int
IPPROTO_IP: int
@@ -143,21 +153,22 @@ IPPROTO_DSTOPTS: int
IPPROTO_EGP: int
IPPROTO_ESP: int
IPPROTO_FRAGMENT: int
IPPROTO_GGP: int
IPPROTO_HOPOPTS: int
IPPROTO_ICMPV6: int
IPPROTO_IDP: int
IPPROTO_IGMP: int
IPPROTO_IPV4: int
IPPROTO_IPV6: int
IPPROTO_MAX: int
IPPROTO_ND: int
IPPROTO_NONE: int
IPPROTO_PIM: int
IPPROTO_PUP: int
IPPROTO_ROUTING: int
IPPROTO_SCTP: int
if sys.platform != "darwin":
if sys.platform != "linux":
IPPROTO_GGP: int
IPPROTO_IPV4: int
IPPROTO_MAX: int
IPPROTO_ND: int
if sys.platform == "win32":
IPPROTO_CBT: int
IPPROTO_ICLFXBM: int
IPPROTO_IGP: int
@@ -166,18 +177,19 @@ if sys.platform != "darwin":
IPPROTO_RDP: int
IPPROTO_ST: int
if sys.platform != "win32":
IPPROTO_EON: int
IPPROTO_GRE: int
IPPROTO_HELLO: int
IPPROTO_IPCOMP: int
IPPROTO_IPIP: int
IPPROTO_RSVP: int
IPPROTO_TP: int
if sys.platform != "win32" and sys.platform != "linux":
IPPROTO_EON: int
IPPROTO_HELLO: int
IPPROTO_IPCOMP: int
IPPROTO_XTP: int
if sys.platform != "win32" and sys.platform != "darwin":
IPPROTO_BIP: int
IPPROTO_MOBILE: int
IPPROTO_VRRP: int
if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "linux":
IPPROTO_BIP: int # Not FreeBSD either
IPPROTO_MOBILE: int # Not FreeBSD either
IPPROTO_VRRP: int # Not FreeBSD either
if sys.version_info >= (3, 9) and sys.platform == "linux":
# Availability: Linux >= 2.6.20, FreeBSD >= 10.1
IPPROTO_UDPLITE: int
@@ -202,11 +214,10 @@ IP_MULTICAST_IF: int
IP_MULTICAST_LOOP: int
IP_MULTICAST_TTL: int
IP_OPTIONS: int
IP_RECVDSTADDR: int
if sys.platform != "linux":
IP_RECVDSTADDR: int
if sys.version_info >= (3, 10):
IP_RECVTOS: int
elif sys.platform != "win32" and sys.platform != "darwin":
IP_RECVTOS: int
IP_TOS: int
IP_TTL: int
if sys.platform != "win32":
@@ -218,6 +229,7 @@ if sys.platform != "win32":
IP_RETOPTS: int
if sys.platform != "win32" and sys.platform != "darwin":
IP_TRANSPARENT: int
if sys.platform != "win32" and sys.platform != "darwin" and sys.version_info >= (3, 11):
IP_BIND_ADDRESS_NO_PORT: int
if sys.version_info >= (3, 12):
IP_ADD_SOURCE_MEMBERSHIP: int
@@ -255,6 +267,9 @@ if sys.platform != "win32":
IPV6_RECVPATHMTU: int
IPV6_RECVPKTINFO: int
IPV6_RTHDRDSTOPTS: int
if sys.platform != "win32" and sys.platform != "linux":
if sys.version_info >= (3, 9) or sys.platform != "darwin":
IPV6_USE_MIN_MTU: int
EAI_AGAIN: int
@@ -268,11 +283,12 @@ EAI_SERVICE: int
EAI_SOCKTYPE: int
if sys.platform != "win32":
EAI_ADDRFAMILY: int
EAI_OVERFLOW: int
EAI_SYSTEM: int
if sys.platform != "win32" and sys.platform != "linux":
EAI_BADHINTS: int
EAI_MAX: int
EAI_OVERFLOW: int
EAI_PROTOCOL: int
EAI_SYSTEM: int
AI_ADDRCONFIG: int
AI_ALL: int
@@ -281,7 +297,7 @@ AI_NUMERICHOST: int
AI_NUMERICSERV: int
AI_PASSIVE: int
AI_V4MAPPED: int
if sys.platform != "win32":
if sys.platform != "win32" and sys.platform != "linux":
AI_DEFAULT: int
AI_MASK: int
AI_V4MAPPED_CFG: int
@@ -293,6 +309,8 @@ NI_NAMEREQD: int
NI_NOFQDN: int
NI_NUMERICHOST: int
NI_NUMERICSERV: int
if sys.platform == "linux" and sys.version_info >= (3, 13):
NI_IDN: int
TCP_FASTOPEN: int
TCP_KEEPCNT: int
@@ -318,6 +336,27 @@ if sys.platform != "win32" and sys.platform != "darwin":
TCP_SYNCNT: int
TCP_USER_TIMEOUT: int
TCP_WINDOW_CLAMP: int
if sys.platform == "linux" and sys.version_info >= (3, 12):
TCP_CC_INFO: int
TCP_FASTOPEN_CONNECT: int
TCP_FASTOPEN_KEY: int
TCP_FASTOPEN_NO_COOKIE: int
TCP_INQ: int
TCP_MD5SIG: int
TCP_MD5SIG_EXT: int
TCP_QUEUE_SEQ: int
TCP_REPAIR: int
TCP_REPAIR_OPTIONS: int
TCP_REPAIR_QUEUE: int
TCP_REPAIR_WINDOW: int
TCP_SAVED_SYN: int
TCP_SAVE_SYN: int
TCP_THIN_DUPACK: int
TCP_THIN_LINEAR_TIMEOUTS: int
TCP_TIMESTAMP: int
TCP_TX_DELAY: int
TCP_ULP: int
TCP_ZEROCOPY_RECEIVE: int
# --------------------
# Specifically documented constants
@@ -334,12 +373,13 @@ if sys.platform == "linux":
CAN_ERR_FLAG: int
CAN_ERR_MASK: int
CAN_RAW: int
CAN_RAW_ERR_FILTER: int
CAN_RAW_FILTER: int
CAN_RAW_LOOPBACK: int
CAN_RAW_RECV_OWN_MSGS: int
CAN_RTR_FLAG: int
CAN_SFF_MASK: int
if sys.version_info < (3, 11):
CAN_RAW_ERR_FILTER: int
if sys.platform == "linux":
# Availability: Linux >= 2.6.25
@@ -437,12 +477,13 @@ if sys.platform == "linux":
AF_RDS: int
PF_RDS: int
SOL_RDS: int
# These are present in include/linux/rds.h but don't always show up
# here.
RDS_CANCEL_SENT_TO: int
RDS_CMSG_RDMA_ARGS: int
RDS_CMSG_RDMA_DEST: int
RDS_CMSG_RDMA_MAP: int
RDS_CMSG_RDMA_STATUS: int
RDS_CMSG_RDMA_UPDATE: int
RDS_CONG_MONITOR: int
RDS_FREE_MR: int
RDS_GET_MR: int
@@ -456,6 +497,10 @@ if sys.platform == "linux":
RDS_RDMA_USE_ONCE: int
RDS_RECVERR: int
# This is supported by CPython but doesn't seem to be a real thing.
# The closest existing constant in rds.h is RDS_CMSG_CONG_UPDATE
# RDS_CMSG_RDMA_UPDATE: int
if sys.platform == "win32":
SIO_RCVALL: int
SIO_KEEPALIVE_VALS: int
@@ -522,16 +567,17 @@ if sys.platform == "linux":
if sys.platform != "win32" or sys.version_info >= (3, 9):
# Documented as only available on BSD, macOS, but empirically sometimes
# available on Windows
AF_LINK: int
if sys.platform != "linux":
AF_LINK: int
has_ipv6: bool
if sys.platform != "darwin":
if sys.platform != "darwin" and sys.platform != "linux":
if sys.platform != "win32" or sys.version_info >= (3, 9):
BDADDR_ANY: str
BDADDR_LOCAL: str
if sys.platform != "win32" and sys.platform != "darwin":
if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "linux":
HCI_FILTER: int # not in NetBSD or DragonFlyBSD
HCI_TIME_STAMP: int # not in FreeBSD, NetBSD, or DragonFlyBSD
HCI_DATA_DIR: int # not in FreeBSD, NetBSD, or DragonFlyBSD
@@ -580,36 +626,37 @@ if sys.version_info >= (3, 12):
if sys.platform == "linux":
# Netlink is defined by Linux
AF_NETLINK: int
NETLINK_ARPD: int
NETLINK_CRYPTO: int
NETLINK_DNRTMSG: int
NETLINK_FIREWALL: int
NETLINK_IP6_FW: int
NETLINK_NFLOG: int
NETLINK_ROUTE6: int
NETLINK_ROUTE: int
NETLINK_SKIP: int
NETLINK_TAPBASE: int
NETLINK_TCPDIAG: int
NETLINK_USERSOCK: int
NETLINK_W1: int
NETLINK_XFRM: int
# Technically still supported by CPython
# NETLINK_ARPD: int # linux 2.0 to 2.6.12 (EOL August 2005)
# NETLINK_ROUTE6: int # linux 2.2 to 2.6.12 (EOL August 2005)
# NETLINK_SKIP: int # linux 2.0 to 2.6.12 (EOL August 2005)
# NETLINK_TAPBASE: int # linux 2.2 to 2.6.12 (EOL August 2005)
# NETLINK_TCPDIAG: int # linux 2.6.0 to 2.6.13 (EOL December 2005)
# NETLINK_W1: int # linux 2.6.13 to 2.6.17 (EOL October 2006)
if sys.platform == "darwin":
PF_SYSTEM: int
SYSPROTO_CONTROL: int
if sys.platform != "darwin":
if sys.platform != "darwin" and sys.platform != "linux":
if sys.version_info >= (3, 9) or sys.platform != "win32":
AF_BLUETOOTH: int
if sys.platform != "win32" and sys.platform != "darwin":
if sys.platform != "win32" and sys.platform != "darwin" and sys.platform != "linux":
# Linux and some BSD support is explicit in the docs
# Windows and macOS do not support in practice
BTPROTO_HCI: int
BTPROTO_L2CAP: int
BTPROTO_SCO: int # not in FreeBSD
if sys.platform != "darwin":
if sys.platform != "darwin" and sys.platform != "linux":
if sys.version_info >= (3, 9) or sys.platform != "win32":
BTPROTO_RFCOMM: int
@@ -636,13 +683,14 @@ AF_SNA: int
if sys.platform != "win32":
AF_ROUTE: int
if sys.platform == "darwin":
AF_SYSTEM: int
if sys.platform != "darwin":
AF_IRDA: int
if sys.platform != "win32" and sys.platform != "darwin":
AF_AAL5: int
AF_ASH: int
AF_ATMPVC: int
AF_ATMSVC: int
@@ -661,10 +709,12 @@ if sys.platform != "win32" and sys.platform != "darwin":
# Miscellaneous undocumented
if sys.platform != "win32":
if sys.platform != "win32" and sys.platform != "linux":
LOCAL_PEERCRED: int
if sys.platform != "win32" and sys.platform != "darwin":
# Defined in linux socket.h, but this isn't always present for
# some reason.
IPX_TYPE: int
# ===== Classes =====
@@ -792,4 +842,4 @@ def if_nameindex() -> list[tuple[int, str]]: ...
def if_nametoindex(oname: str, /) -> int: ...
def if_indextoname(index: int, /) -> str: ...
CAPI: object
CAPI: CapsuleType

View File

@@ -44,6 +44,12 @@ def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpa
@overload
def start_new_thread(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
# Obsolete synonym for start_new_thread()
@overload
def start_new(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ...
@overload
def start_new(function: Callable[..., object], args: tuple[Any, ...], kwargs: dict[str, Any], /) -> int: ...
if sys.version_info >= (3, 10):
def interrupt_main(signum: signal.Signals = ..., /) -> None: ...
@@ -51,7 +57,9 @@ else:
def interrupt_main() -> None: ...
def exit() -> NoReturn: ...
def exit_thread() -> NoReturn: ... # Obsolete synonym for exit()
def allocate_lock() -> LockType: ...
def allocate() -> LockType: ... # Obsolete synonym for allocate_lock()
def get_ident() -> int: ...
def stack_size(size: int = 0, /) -> int: ...

View File

@@ -1,3 +1,4 @@
from threading import RLock
from typing import Any
from typing_extensions import Self, TypeAlias
from weakref import ReferenceType
@@ -8,6 +9,9 @@ _LocalDict: TypeAlias = dict[Any, Any]
class _localimpl:
key: str
dicts: dict[int, tuple[ReferenceType[Any], _LocalDict]]
# Keep localargs in sync with the *args, **kwargs annotation on local.__new__
localargs: tuple[list[Any], dict[str, Any]]
locallock: RLock
def get_dict(self) -> _LocalDict: ...
def create_dict(self) -> _LocalDict: ...

View File

@@ -107,7 +107,7 @@ class object:
@property
def __class__(self) -> type[Self]: ...
@__class__.setter
def __class__(self, type: type[object], /) -> None: ...
def __class__(self, type: type[Self], /) -> None: ...
def __init__(self) -> None: ...
def __new__(cls) -> Self: ...
# N.B. `object.__setattr__` and `object.__delattr__` are heavily special-cased by type checkers.
@@ -1963,14 +1963,33 @@ class StopAsyncIteration(Exception):
class SyntaxError(Exception):
msg: str
filename: str | None
lineno: int | None
offset: int | None
text: str | None
filename: str | None
# Errors are displayed differently if this attribute exists on the exception.
# The value is always None.
print_file_and_line: None
if sys.version_info >= (3, 10):
end_lineno: int | None
end_offset: int | None
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, msg: object, /) -> None: ...
# Second argument is the tuple (filename, lineno, offset, text)
@overload
def __init__(self, msg: str, info: tuple[str | None, int | None, int | None, str | None], /) -> None: ...
if sys.version_info >= (3, 10):
# end_lineno and end_offset must both be provided if one is.
@overload
def __init__(
self, msg: str, info: tuple[str | None, int | None, int | None, str | None, int | None, int | None], /
) -> None: ...
# If you provide more than two arguments, it still creates the SyntaxError, but
# the arguments from the info tuple are not parsed. This form is omitted.
class SystemError(Exception): ...
class TypeError(Exception): ...
class ValueError(Exception): ...

View File

@@ -126,6 +126,7 @@ class BZ2File(BaseStream, IO[bytes]):
def readline(self, size: SupportsIndex = -1) -> bytes: ... # type: ignore[override]
def readinto(self, b: WriteableBuffer) -> int: ...
def readlines(self, size: SupportsIndex = -1) -> list[bytes]: ...
def peek(self, n: int = 0) -> bytes: ...
def seek(self, offset: int, whence: int = 0) -> int: ...
def write(self, data: ReadableBuffer) -> int: ...
def writelines(self, seq: Iterable[ReadableBuffer]) -> None: ...

View File

@@ -3,7 +3,7 @@ from _codecs import *
from _typeshed import ReadableBuffer
from abc import abstractmethod
from collections.abc import Callable, Generator, Iterable
from typing import Any, BinaryIO, Final, Literal, Protocol, TextIO
from typing import Any, BinaryIO, ClassVar, Final, Literal, Protocol, TextIO
from typing_extensions import Self
__all__ = [
@@ -202,6 +202,7 @@ class StreamWriter(Codec):
def write(self, object: str) -> None: ...
def writelines(self, list: Iterable[str]) -> None: ...
def reset(self) -> None: ...
def seek(self, offset: int, whence: int = 0) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, type: type[BaseException] | None, value: BaseException | None, tb: types.TracebackType | None) -> None: ...
def __getattr__(self, name: str, getattr: Callable[[Any, str], Any] = ...) -> Any: ...
@@ -209,11 +210,14 @@ class StreamWriter(Codec):
class StreamReader(Codec):
stream: _ReadableStream
errors: str
# This is set to str, but some subclasses set to bytes instead.
charbuffertype: ClassVar[type] = ...
def __init__(self, stream: _ReadableStream, errors: str = "strict") -> None: ...
def read(self, size: int = -1, chars: int = -1, firstline: bool = False) -> str: ...
def readline(self, size: int | None = None, keepends: bool = True) -> str: ...
def readlines(self, sizehint: int | None = None, keepends: bool = True) -> list[str]: ...
def reset(self) -> None: ...
def seek(self, offset: int, whence: int = 0) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, type: type[BaseException] | None, value: BaseException | None, tb: types.TracebackType | None) -> None: ...
def __iter__(self) -> Self: ...

View File

@@ -1,5 +1,5 @@
import sys
from _typeshed import StrOrBytesPath, SupportsWrite
from _typeshed import MaybeNone, StrOrBytesPath, SupportsWrite
from collections.abc import Callable, ItemsView, Iterable, Iterator, Mapping, MutableMapping, Sequence
from re import Pattern
from typing import Any, ClassVar, Final, Literal, TypeVar, overload
@@ -263,11 +263,11 @@ class RawConfigParser(_Parser):
) -> _T: ...
# This is incompatible with MutableMapping so we ignore the type
@overload # type: ignore[override]
def get(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> str | Any: ...
def get(self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None) -> str | MaybeNone: ...
@overload
def get(
self, section: str, option: str, *, raw: bool = False, vars: _Section | None = None, fallback: _T
) -> str | _T | Any: ...
) -> str | _T | MaybeNone: ...
@overload
def items(self, *, raw: bool = False, vars: _Section | None = None) -> ItemsView[str, SectionProxy]: ...
@overload
@@ -277,6 +277,8 @@ class RawConfigParser(_Parser):
def remove_option(self, section: str, option: str) -> bool: ...
def remove_section(self, section: str) -> bool: ...
def optionxform(self, optionstr: str) -> str: ...
@property
def converters(self) -> ConverterMapping: ...
class ConfigParser(RawConfigParser):
# This is incompatible with MutableMapping so we ignore the type
@@ -300,28 +302,34 @@ class SectionProxy(MutableMapping[str, str]):
def parser(self) -> RawConfigParser: ...
@property
def name(self) -> str: ...
def get( # type: ignore[override]
# This is incompatible with MutableMapping so we ignore the type
@overload # type: ignore[override]
def get(
self, option: str, *, raw: bool = False, vars: _Section | None = None, _impl: Any | None = None, **kwargs: Any
) -> str | None: ...
@overload
def get(
self,
option: str,
fallback: str | None = None,
fallback: _T,
*,
raw: bool = False,
vars: _Section | None = None,
_impl: Any | None = None,
**kwargs: Any,
) -> str | Any: ... # can be None in RawConfigParser's sections
) -> str | _T: ...
# These are partially-applied version of the methods with the same names in
# RawConfigParser; the stubs should be kept updated together
@overload
def getint(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> int: ...
def getint(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> int | None: ...
@overload
def getint(self, option: str, fallback: _T = ..., *, raw: bool = ..., vars: _Section | None = ...) -> int | _T: ...
@overload
def getfloat(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> float: ...
def getfloat(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> float | None: ...
@overload
def getfloat(self, option: str, fallback: _T = ..., *, raw: bool = ..., vars: _Section | None = ...) -> float | _T: ...
@overload
def getboolean(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> bool: ...
def getboolean(self, option: str, *, raw: bool = ..., vars: _Section | None = ...) -> bool | None: ...
@overload
def getboolean(self, option: str, fallback: _T = ..., *, raw: bool = ..., vars: _Section | None = ...) -> bool | _T: ...
# SectionProxy can have arbitrary attributes when custom converters are used

View File

@@ -1,7 +1,7 @@
import abc
import sys
from _typeshed import FileDescriptorOrPath, Unused
from abc import abstractmethod
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator
from types import TracebackType
from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable
@@ -38,16 +38,22 @@ _P = ParamSpec("_P")
_ExitFunc: TypeAlias = Callable[[type[BaseException] | None, BaseException | None, TracebackType | None], bool | None]
_CM_EF = TypeVar("_CM_EF", bound=AbstractContextManager[Any, Any] | _ExitFunc)
# mypy and pyright object to this being both ABC and Protocol.
# At runtime it inherits from ABC and is not a Protocol, but it is on the
# allowlist for use as a Protocol.
@runtime_checkable
class AbstractContextManager(Protocol[_T_co, _ExitT_co]):
class AbstractContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
def __enter__(self) -> _T_co: ...
@abstractmethod
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None, /
) -> _ExitT_co: ...
# mypy and pyright object to this being both ABC and Protocol.
# At runtime it inherits from ABC and is not a Protocol, but it is on the
# allowlist for use as a Protocol.
@runtime_checkable
class AbstractAsyncContextManager(Protocol[_T_co, _ExitT_co]):
class AbstractAsyncContextManager(ABC, Protocol[_T_co, _ExitT_co]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues]
async def __aenter__(self) -> _T_co: ...
@abstractmethod
async def __aexit__(

View File

@@ -1,8 +1,15 @@
import sys
from typing import Final
from typing import Final, NamedTuple, type_check_only
if sys.platform != "win32":
class _Method: ...
@type_check_only
class _MethodBase(NamedTuple):
name: str
ident: str | None
salt_chars: int
total_size: int
class _Method(_MethodBase): ...
METHOD_CRYPT: Final[_Method]
METHOD_MD5: Final[_Method]
METHOD_SHA256: Final[_Method]

View File

@@ -8,8 +8,6 @@ from _csv import (
__version__ as __version__,
_DialectLike,
_QuotingType,
_reader,
_writer,
field_size_limit as field_size_limit,
get_dialect as get_dialect,
list_dialects as list_dialects,
@@ -21,6 +19,10 @@ from _csv import (
if sys.version_info >= (3, 12):
from _csv import QUOTE_NOTNULL as QUOTE_NOTNULL, QUOTE_STRINGS as QUOTE_STRINGS
if sys.version_info >= (3, 10):
from _csv import Reader, Writer
else:
from _csv import _reader as Reader, _writer as Writer
from _typeshed import SupportsWrite
from collections.abc import Collection, Iterable, Mapping, Sequence
@@ -77,7 +79,7 @@ class DictReader(Generic[_T]):
fieldnames: Sequence[_T] | None
restkey: _T | None
restval: str | Any | None
reader: _reader
reader: Reader
dialect: _DialectLike
line_num: int
@overload
@@ -125,7 +127,7 @@ class DictWriter(Generic[_T]):
fieldnames: Collection[_T]
restval: Any | None
extrasaction: Literal["raise", "ignore"]
writer: _writer
writer: Writer
def __init__(
self,
f: SupportsWrite[str],

View File

@@ -10,13 +10,11 @@ from _ctypes import (
_CanCastTo as _CanCastTo,
_CArgObject as _CArgObject,
_CData as _CData,
_CDataMeta as _CDataMeta,
_CDataType as _CDataType,
_CField as _CField,
_Pointer as _Pointer,
_PointerLike as _PointerLike,
_SimpleCData as _SimpleCData,
_StructUnionBase as _StructUnionBase,
_StructUnionMeta as _StructUnionMeta,
addressof as addressof,
alignment as alignment,
byref as byref,
@@ -28,7 +26,7 @@ from _ctypes import (
)
from ctypes._endian import BigEndianStructure as BigEndianStructure, LittleEndianStructure as LittleEndianStructure
from typing import Any, ClassVar, Generic, TypeVar
from typing_extensions import TypeAlias
from typing_extensions import Self, TypeAlias, deprecated
if sys.platform == "win32":
from _ctypes import FormatError as FormatError, get_last_error as get_last_error, set_last_error as set_last_error
@@ -41,6 +39,7 @@ if sys.version_info >= (3, 9):
_T = TypeVar("_T")
_DLLT = TypeVar("_DLLT", bound=CDLL)
_CT = TypeVar("_CT", bound=_CData)
DEFAULT_MODE: int
@@ -48,7 +47,7 @@ class ArgumentError(Exception): ...
class CDLL:
_func_flags_: ClassVar[int]
_func_restype_: ClassVar[_CData]
_func_restype_: ClassVar[_CDataType]
_name: str
_handle: int
_FuncPtr: type[_FuncPointer]
@@ -91,15 +90,21 @@ class _NamedFuncPointer(_FuncPointer):
__name__: str
def CFUNCTYPE(
restype: type[_CData] | None, *argtypes: type[_CData], use_errno: bool = ..., use_last_error: bool = ...
restype: type[_CData | _CDataType] | None,
*argtypes: type[_CData | _CDataType],
use_errno: bool = ...,
use_last_error: bool = ...,
) -> type[_FuncPointer]: ...
if sys.platform == "win32":
def WINFUNCTYPE(
restype: type[_CData] | None, *argtypes: type[_CData], use_errno: bool = ..., use_last_error: bool = ...
restype: type[_CData | _CDataType] | None,
*argtypes: type[_CData | _CDataType],
use_errno: bool = ...,
use_last_error: bool = ...,
) -> type[_FuncPointer]: ...
def PYFUNCTYPE(restype: type[_CData] | None, *argtypes: type[_CData]) -> type[_FuncPointer]: ...
def PYFUNCTYPE(restype: type[_CData | _CDataType] | None, *argtypes: type[_CData | _CDataType]) -> type[_FuncPointer]: ...
# Any type that can be implicitly converted to c_void_p when passed as a C function argument.
# (bytes is not included here, see below.)
@@ -112,12 +117,17 @@ _CVoidConstPLike: TypeAlias = _CVoidPLike | bytes
_CastT = TypeVar("_CastT", bound=_CanCastTo)
def cast(obj: _CData | _CArgObject | int, typ: type[_CastT]) -> _CastT: ...
def cast(obj: _CData | _CDataType | _CArgObject | int, typ: type[_CastT]) -> _CastT: ...
def create_string_buffer(init: int | bytes, size: int | None = None) -> Array[c_char]: ...
c_buffer = create_string_buffer
def create_unicode_buffer(init: int | str, size: int | None = None) -> Array[c_wchar]: ...
@deprecated("Deprecated in Python 3.13; removal scheduled for Python 3.15")
def SetPointerType(
pointer: type[_Pointer[Any]], cls: Any # noqa: F811 # Redefinition of unused `pointer` from line 22
) -> None: ...
def ARRAY(typ: _CT, len: int) -> Array[_CT]: ... # Soft Deprecated, no plans to remove
if sys.platform == "win32":
def DllCanUnloadNow() -> int: ...
@@ -126,12 +136,12 @@ if sys.platform == "win32":
def memmove(dst: _CVoidPLike, src: _CVoidConstPLike, count: int) -> int: ...
def memset(dst: _CVoidPLike, c: int, count: int) -> int: ...
def string_at(address: _CVoidConstPLike, size: int = -1) -> bytes: ...
def string_at(ptr: _CVoidConstPLike, size: int = -1) -> bytes: ...
if sys.platform == "win32":
def WinError(code: int | None = None, descr: str | None = None) -> OSError: ...
def wstring_at(address: _CVoidConstPLike, size: int = -1) -> str: ...
def wstring_at(ptr: _CVoidConstPLike, size: int = -1) -> str: ...
class c_byte(_SimpleCData[int]): ...
@@ -140,6 +150,8 @@ class c_char(_SimpleCData[bytes]):
class c_char_p(_PointerLike, _SimpleCData[bytes | None]):
def __init__(self, value: int | bytes | None = ...) -> None: ...
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
class c_double(_SimpleCData[float]): ...
class c_longdouble(_SimpleCData[float]): ... # can be an alias for c_double
@@ -155,7 +167,13 @@ class c_uint(_SimpleCData[int]): ... # can be an alias for c_ulong
class c_ulong(_SimpleCData[int]): ...
class c_ulonglong(_SimpleCData[int]): ... # can be an alias for c_ulong
class c_ushort(_SimpleCData[int]): ...
class c_void_p(_PointerLike, _SimpleCData[int | None]): ...
class c_void_p(_PointerLike, _SimpleCData[int | None]):
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
c_voidp = c_void_p # backwards compatibility (to a bug)
class c_wchar(_SimpleCData[str]): ...
c_int8 = c_byte
@@ -174,6 +192,8 @@ class c_uint64(_SimpleCData[int]): ...
class c_wchar_p(_PointerLike, _SimpleCData[str | None]):
def __init__(self, value: int | str | None = ...) -> None: ...
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
class c_bool(_SimpleCData[bool]):
def __init__(self, value: bool = ...) -> None: ...

View File

@@ -0,0 +1 @@
__version__: str

View File

@@ -0,0 +1,8 @@
from collections.abc import Mapping
from ctypes.macholib.dylib import dylib_info as dylib_info
from ctypes.macholib.framework import framework_info as framework_info
__all__ = ["dyld_find", "framework_find", "framework_info", "dylib_info"]
def dyld_find(name: str, executable_path: str | None = None, env: Mapping[str, str] | None = None) -> str: ...
def framework_find(fn: str, executable_path: str | None = None, env: Mapping[str, str] | None = None) -> str: ...

View File

@@ -0,0 +1,14 @@
from typing import TypedDict, type_check_only
__all__ = ["dylib_info"]
# Actual result is produced by re.match.groupdict()
@type_check_only
class _DylibInfo(TypedDict):
location: str
name: str
shortname: str
version: str | None
suffix: str | None
def dylib_info(filename: str) -> _DylibInfo | None: ...

View File

@@ -0,0 +1,14 @@
from typing import TypedDict, type_check_only
__all__ = ["framework_info"]
# Actual result is produced by re.match.groupdict()
@type_check_only
class _FrameworkInfo(TypedDict):
location: str
name: str
shortname: str
version: str | None
suffix: str | None
def framework_info(filename: str) -> _FrameworkInfo | None: ...

View File

@@ -4,3 +4,5 @@ def find_library(name: str) -> str | None: ...
if sys.platform == "win32":
def find_msvcrt() -> str | None: ...
def test() -> None: ...

View File

@@ -1,7 +1,7 @@
from _ctypes import _CArgObject, _CField
from ctypes import (
Array,
Structure,
_CField,
_Pointer,
_SimpleCData,
c_byte,
@@ -21,8 +21,8 @@ from ctypes import (
c_wchar,
c_wchar_p,
)
from typing import TypeVar
from typing_extensions import TypeAlias
from typing import Any, TypeVar
from typing_extensions import Self, TypeAlias
BYTE = c_byte
WORD = c_ushort
@@ -241,10 +241,16 @@ LPBYTE = PBYTE
PBOOLEAN = PBYTE
# LP_c_char
class PCHAR(_Pointer[CHAR]): ...
class PCHAR(_Pointer[CHAR]):
# this is inherited from ctypes.c_char_p, kind of.
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
# LP_c_wchar
class PWCHAR(_Pointer[WCHAR]): ...
class PWCHAR(_Pointer[WCHAR]):
# inherited from ctypes.c_wchar_p, kind of
@classmethod
def from_param(cls, value: Any, /) -> Self | _CArgObject: ...
# LP_c_void_p
class PHANDLE(_Pointer[HANDLE]): ...

View File

@@ -1,7 +1,9 @@
import sys
from _curses import *
from _curses import window as window
from _typeshed import structseq
from collections.abc import Callable
from typing import TypeVar
from typing import Final, TypeVar, final, type_check_only
from typing_extensions import Concatenate, ParamSpec
# NOTE: The _curses module is ordinarily only available on Unix, but the
@@ -25,3 +27,19 @@ def wrapper(func: Callable[Concatenate[window, _P], _T], /, *arg: _P.args, **kwd
# it was mapped to the name 'window' in 3.8.
# Kept here as a legacy alias in case any third-party code is relying on it.
_CursesWindow = window
# At runtime this class is unexposed and calls itself curses.ncurses_version.
# That name would conflict with the actual curses.ncurses_version, which is
# an instance of this class.
@final
@type_check_only
class _ncurses_version(structseq[int], tuple[int, int, int]):
if sys.version_info >= (3, 10):
__match_args__: Final = ("major", "minor", "patch")
@property
def major(self) -> int: ...
@property
def minor(self) -> int: ...
@property
def patch(self) -> int: ...

View File

@@ -1,22 +1 @@
from _curses import window
version: str
class _Curses_Panel: # type is <class '_curses_panel.curses panel'> (note the space in the class name)
def above(self) -> _Curses_Panel: ...
def below(self) -> _Curses_Panel: ...
def bottom(self) -> None: ...
def hidden(self) -> bool: ...
def hide(self) -> None: ...
def move(self, y: int, x: int) -> None: ...
def replace(self, win: window) -> None: ...
def set_userptr(self, obj: object) -> None: ...
def show(self) -> None: ...
def top(self) -> None: ...
def userptr(self) -> object: ...
def window(self) -> window: ...
def bottom_panel() -> _Curses_Panel: ...
def new_panel(win: window, /) -> _Curses_Panel: ...
def top_panel() -> _Curses_Panel: ...
def update_panels() -> _Curses_Panel: ...
from _curses_panel import *

View File

@@ -1,8 +1,8 @@
import sys
from abc import abstractmethod
from time import struct_time
from typing import ClassVar, Final, NamedTuple, NoReturn, SupportsIndex, final, overload
from typing_extensions import Self, TypeAlias, deprecated
from typing import ClassVar, Final, NoReturn, SupportsIndex, final, overload, type_check_only
from typing_extensions import CapsuleType, Self, TypeAlias, deprecated
if sys.version_info >= (3, 11):
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", "MINYEAR", "MAXYEAR", "UTC")
@@ -40,10 +40,17 @@ if sys.version_info >= (3, 11):
UTC: timezone
if sys.version_info >= (3, 9):
class _IsoCalendarDate(NamedTuple):
year: int
week: int
weekday: int
# This class calls itself datetime.IsoCalendarDate. It's neither
# NamedTuple nor structseq.
@final
@type_check_only
class _IsoCalendarDate(tuple[int, int, int]):
@property
def year(self) -> int: ...
@property
def week(self) -> int: ...
@property
def weekday(self) -> int: ...
class date:
min: ClassVar[date]
@@ -325,3 +332,5 @@ class datetime(date):
def __sub__(self, value: Self, /) -> timedelta: ...
@overload
def __sub__(self, value: timedelta, /) -> Self: ...
datetime_CAPI: CapsuleType

View File

@@ -2,7 +2,7 @@ import sys
from _typeshed import StrOrBytesPath
from collections.abc import Iterator, MutableMapping
from types import TracebackType
from typing import Literal
from typing import Literal, type_check_only
from typing_extensions import Self, TypeAlias
__all__ = ["open", "whichdb", "error"]
@@ -89,6 +89,8 @@ class _Database(MutableMapping[_KeyType, bytes]):
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
) -> None: ...
# This class is not exposed. It calls itself dbm.error.
@type_check_only
class _error(Exception): ...
error: tuple[type[_error], type[OSError]]

View File

@@ -24,7 +24,8 @@ from _decimal import (
setcontext as setcontext,
)
from collections.abc import Container, Sequence
from typing import Any, ClassVar, Literal, NamedTuple, overload
from types import TracebackType
from typing import Any, ClassVar, Literal, NamedTuple, final, overload, type_check_only
from typing_extensions import Self, TypeAlias
_Decimal: TypeAlias = Decimal | int
@@ -35,6 +36,14 @@ _TrapType: TypeAlias = type[DecimalException]
# At runtime, these classes are implemented in C as part of "_decimal".
# However, they consider themselves to live in "decimal", so we'll put them here.
# This type isn't exposed at runtime. It calls itself decimal.ContextManager
@final
@type_check_only
class _ContextManager:
def __init__(self, new_context: Context) -> None: ...
def __enter__(self) -> Context: ...
def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ...
class DecimalTuple(NamedTuple):
sign: int
digits: tuple[int, ...]

View File

@@ -165,7 +165,7 @@ class CCompiler:
def execute(
self, func: Callable[[Unpack[_Ts]], Unused], args: tuple[Unpack[_Ts]], msg: str | None = None, level: int = 1
) -> None: ...
def spawn(self, cmd: list[str]) -> None: ...
def spawn(self, cmd: Iterable[str]) -> None: ...
def mkpath(self, name: str, mode: int = 0o777) -> None: ...
@overload
def move_file(self, src: StrPath, dst: _StrPathT) -> _StrPathT | str: ...

View File

@@ -1,6 +1,10 @@
from collections.abc import Iterable
from typing import Literal
def spawn(
cmd: list[str], search_path: bool | Literal[0, 1] = 1, verbose: bool | Literal[0, 1] = 0, dry_run: bool | Literal[0, 1] = 0
cmd: Iterable[str],
search_path: bool | Literal[0, 1] = 1,
verbose: bool | Literal[0, 1] = 0,
dry_run: bool | Literal[0, 1] = 0,
) -> None: ...
def find_executable(executable: str, path: str | None = None) -> str | None: ...

View File

@@ -3,7 +3,7 @@ import types
import unittest
from _typeshed import ExcInfo
from collections.abc import Callable
from typing import Any, ClassVar, NamedTuple
from typing import Any, NamedTuple, type_check_only
from typing_extensions import Self, TypeAlias
__all__ = [
@@ -42,17 +42,15 @@ __all__ = [
"debug",
]
# MyPy errors on conditionals within named tuples.
if sys.version_info >= (3, 13):
class TestResults(NamedTuple):
def __new__(cls, failed: int, attempted: int, *, skipped: int = 0) -> Self: ... # type: ignore[misc]
skipped: int
@type_check_only
class _TestResultsBase(NamedTuple):
failed: int
attempted: int
_fields: ClassVar = ("failed", "attempted") # type: ignore[misc]
__match_args__ = ("failed", "attempted") # type: ignore[misc]
__doc__: None # type: ignore[misc]
class TestResults(_TestResultsBase):
def __new__(cls, failed: int, attempted: int, *, skipped: int = 0) -> Self: ...
skipped: int
else:
class TestResults(NamedTuple):

View File

@@ -4,6 +4,29 @@ from email.policy import Policy
from typing import IO
from typing_extensions import TypeAlias
# At runtime, listing submodules in __all__ without them being imported is
# valid, and causes them to be included in a star import. See #6523
__all__ = [ # noqa: F822 # Undefined names in __all__
"base64mime", # pyright: ignore[reportUnsupportedDunderAll]
"charset", # pyright: ignore[reportUnsupportedDunderAll]
"encoders", # pyright: ignore[reportUnsupportedDunderAll]
"errors", # pyright: ignore[reportUnsupportedDunderAll]
"feedparser", # pyright: ignore[reportUnsupportedDunderAll]
"generator", # pyright: ignore[reportUnsupportedDunderAll]
"header", # pyright: ignore[reportUnsupportedDunderAll]
"iterators", # pyright: ignore[reportUnsupportedDunderAll]
"message", # pyright: ignore[reportUnsupportedDunderAll]
"message_from_file",
"message_from_binary_file",
"message_from_string",
"message_from_bytes",
"mime", # pyright: ignore[reportUnsupportedDunderAll]
"parser", # pyright: ignore[reportUnsupportedDunderAll]
"quoprimime", # pyright: ignore[reportUnsupportedDunderAll]
"utils", # pyright: ignore[reportUnsupportedDunderAll]
]
# Definitions imported by multiple submodules in typeshed
_ParamType: TypeAlias = str | tuple[str | None, str | None, str] # noqa: Y047
_ParamsType: TypeAlias = str | None | tuple[str, str | None, str] # noqa: Y047
@@ -12,18 +35,3 @@ def message_from_string(s: str, _class: Callable[[], Message] = ..., *, policy:
def message_from_bytes(s: bytes | bytearray, _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
def message_from_file(fp: IO[str], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
def message_from_binary_file(fp: IO[bytes], _class: Callable[[], Message] = ..., *, policy: Policy = ...) -> Message: ...
# Names in __all__ with no definition:
# base64mime
# charset
# encoders
# errors
# feedparser
# generator
# header
# iterators
# message
# mime
# parser
# quoprimime
# utils

View File

@@ -5,6 +5,8 @@ from email.message import Message
from typing import Generic, Protocol, TypeVar, type_check_only
from typing_extensions import Self
__all__ = ["Policy", "Compat32", "compat32"]
_MessageT = TypeVar("_MessageT", bound=Message, default=Message)
@type_check_only

View File

@@ -0,0 +1 @@
aliases: dict[str, str]

View File

@@ -0,0 +1,30 @@
import codecs
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
# At runtime, this is codecs.ascii_encode
@staticmethod
def encode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ...
# At runtime, this is codecs.ascii_decode
@staticmethod
def decode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
# Note: encode being a decode function and decode being an encode function is accurate to runtime.
class StreamConverter(StreamWriter, StreamReader): # type: ignore[misc] # incompatible methods in base classes
# At runtime, this is codecs.ascii_decode
@staticmethod
def encode(data: ReadableBuffer, errors: str | None = None, /) -> tuple[str, int]: ... # type: ignore[override]
# At runtime, this is codecs.ascii_encode
@staticmethod
def decode(str: str, errors: str | None = None, /) -> tuple[bytes, int]: ... # type: ignore[override]
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,26 @@
import codecs
from _typeshed import ReadableBuffer
from typing import ClassVar
# This codec is bytes to bytes.
def base64_encode(input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ...
def base64_decode(input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ...
class Codec(codecs.Codec):
def encode(self, input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ... # type: ignore[override]
def decode(self, input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ... # type: ignore[override]
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: ReadableBuffer, final: bool = False) -> bytes: ... # type: ignore[override]
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> bytes: ... # type: ignore[override]
class StreamWriter(Codec, codecs.StreamWriter):
charbuffertype: ClassVar[type] = ...
class StreamReader(Codec, codecs.StreamReader):
charbuffertype: ClassVar[type] = ...
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,23 @@
import _multibytecodec as mbc
import codecs
from typing import ClassVar
codec: mbc._MultibyteCodec
class Codec(codecs.Codec):
encode = codec.encode # type: ignore[assignment] # pyright: ignore[reportAssignmentType]
decode = codec.decode # type: ignore[assignment] # pyright: ignore[reportAssignmentType]
class IncrementalEncoder(mbc.MultibyteIncrementalEncoder, codecs.IncrementalEncoder): # type: ignore[misc]
codec: ClassVar[mbc._MultibyteCodec] = ...
class IncrementalDecoder(mbc.MultibyteIncrementalDecoder, codecs.IncrementalDecoder):
codec: ClassVar[mbc._MultibyteCodec] = ...
class StreamReader(Codec, mbc.MultibyteStreamReader, codecs.StreamReader): # type: ignore[misc]
codec: ClassVar[mbc._MultibyteCodec] = ...
class StreamWriter(Codec, mbc.MultibyteStreamWriter, codecs.StreamWriter):
codec: ClassVar[mbc._MultibyteCodec] = ...
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,23 @@
import _multibytecodec as mbc
import codecs
from typing import ClassVar
codec: mbc._MultibyteCodec
class Codec(codecs.Codec):
encode = codec.encode # type: ignore[assignment] # pyright: ignore[reportAssignmentType]
decode = codec.decode # type: ignore[assignment] # pyright: ignore[reportAssignmentType]
class IncrementalEncoder(mbc.MultibyteIncrementalEncoder, codecs.IncrementalEncoder): # type: ignore[misc]
codec: ClassVar[mbc._MultibyteCodec] = ...
class IncrementalDecoder(mbc.MultibyteIncrementalDecoder, codecs.IncrementalDecoder):
codec: ClassVar[mbc._MultibyteCodec] = ...
class StreamReader(Codec, mbc.MultibyteStreamReader, codecs.StreamReader): # type: ignore[misc]
codec: ClassVar[mbc._MultibyteCodec] = ...
class StreamWriter(Codec, mbc.MultibyteStreamWriter, codecs.StreamWriter):
codec: ClassVar[mbc._MultibyteCodec] = ...
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,26 @@
import codecs
from _typeshed import ReadableBuffer
from typing import ClassVar
# This codec is bytes to bytes.
def bz2_encode(input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ...
def bz2_decode(input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ...
class Codec(codecs.Codec):
def encode(self, input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ... # type: ignore[override]
def decode(self, input: ReadableBuffer, errors: str = "strict") -> tuple[bytes, int]: ... # type: ignore[override]
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: ReadableBuffer, final: bool = False) -> bytes: ... # type: ignore[override]
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> bytes: ... # type: ignore[override]
class StreamWriter(Codec, codecs.StreamWriter):
charbuffertype: ClassVar[type] = ...
class StreamReader(Codec, codecs.StreamReader):
charbuffertype: ClassVar[type] = ...
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,33 @@
import codecs
from _codecs import _CharMap
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
# At runtime, this is codecs.charmap_encode
@staticmethod
def encode(str: str, errors: str | None = None, mapping: _CharMap | None = None, /) -> tuple[bytes, int]: ...
# At runtime, this is codecs.charmap_decode
@staticmethod
def decode(data: ReadableBuffer, errors: str | None = None, mapping: _CharMap | None = None, /) -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
mapping: _CharMap | None
def __init__(self, errors: str = "strict", mapping: _CharMap | None = None) -> None: ...
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
mapping: _CharMap | None
def __init__(self, errors: str = "strict", mapping: _CharMap | None = None) -> None: ...
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter):
mapping: _CharMap | None
def __init__(self, stream: codecs._WritableStream, errors: str = "strict", mapping: _CharMap | None = None) -> None: ...
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ... # type: ignore[override]
class StreamReader(Codec, codecs.StreamReader):
mapping: _CharMap | None
def __init__(self, stream: codecs._ReadableStream, errors: str = "strict", mapping: _CharMap | None = None) -> None: ...
def decode(self, input: ReadableBuffer, errors: str = "strict") -> tuple[str, int]: ... # type: ignore[override]
def getregentry() -> codecs.CodecInfo: ...

View File

@@ -0,0 +1,21 @@
import codecs
from _codecs import _EncodingMap
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ...
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
def getregentry() -> codecs.CodecInfo: ...
decoding_table: str
encoding_table: _EncodingMap

View File

@@ -0,0 +1,21 @@
import codecs
from _codecs import _EncodingMap
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ...
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
def getregentry() -> codecs.CodecInfo: ...
decoding_table: str
encoding_table: _EncodingMap

View File

@@ -0,0 +1,21 @@
import codecs
from _codecs import _EncodingMap
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ...
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
def getregentry() -> codecs.CodecInfo: ...
decoding_table: str
encoding_table: _EncodingMap

View File

@@ -0,0 +1,21 @@
import codecs
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ...
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
def getregentry() -> codecs.CodecInfo: ...
decoding_map: dict[int, int | None]
decoding_table: str
encoding_map: dict[int, int]

View File

@@ -0,0 +1,21 @@
import codecs
from _codecs import _EncodingMap
from _typeshed import ReadableBuffer
class Codec(codecs.Codec):
def encode(self, input: str, errors: str = "strict") -> tuple[bytes, int]: ...
def decode(self, input: bytes, errors: str = "strict") -> tuple[str, int]: ...
class IncrementalEncoder(codecs.IncrementalEncoder):
def encode(self, input: str, final: bool = False) -> bytes: ...
class IncrementalDecoder(codecs.IncrementalDecoder):
def decode(self, input: ReadableBuffer, final: bool = False) -> str: ...
class StreamWriter(Codec, codecs.StreamWriter): ...
class StreamReader(Codec, codecs.StreamReader): ...
def getregentry() -> codecs.CodecInfo: ...
decoding_table: str
encoding_table: _EncodingMap

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