Compare commits

...

28 Commits

Author SHA1 Message Date
Alex Waygood
5e6de4e0c6 Changelog for Ruff v0.7 (#13794)
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2024-10-17 16:14:21 +00:00
Zanie Blue
70e5c4a8ba Recode TRY302 to TRY203 (#13502)
Closes https://github.com/astral-sh/ruff/issues/13492
2024-10-17 16:35:12 +01:00
Micha Reiser
9218d6bedc Remove allow-unused-imports setting from the common lint options (#13677)
Fixes https://github.com/astral-sh/ruff/issues/13668
2024-10-17 16:35:12 +01:00
Alex Waygood
1b79ae9817 [ruff-0.7] Stabilise the expansion of open-file-with-context-handler to work with other standard-library IO modules (SIM115) (#13680)
Closes #7313.
2024-10-17 16:35:12 +01:00
Alexey Preobrazhenskiy
2b87587ac2 [flake8-pytest-style] Fix defaults when lint.flake8-pytest-style config section is empty (PT001, PT023) (#13292) 2024-10-17 16:35:12 +01:00
Micha Reiser
d1e15f6246 Remove tab-size setting (#12835)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Closes https://github.com/astral-sh/ruff/issues/12041
2024-10-17 16:35:12 +01:00
Micha Reiser
89a82158a1 Remove error messages for removed CLI aliases (#12833)
Closes https://github.com/astral-sh/ruff/issues/10171
2024-10-17 16:35:12 +01:00
Micha Reiser
202c6a6d75 Remove output-format=text setting (#12836) 2024-10-17 16:35:12 +01:00
David Peter
5c3c0c4705 [red-knot] Inference for comparison of union types (#13781)
## Summary

Add type inference for comparisons involving union types. For example:
```py
one_or_two = 1 if flag else 2

reveal_type(one_or_two <= 2)  # revealed: Literal[True]
reveal_type(one_or_two <= 1)  # revealed: bool
reveal_type(one_or_two <= 0)  # revealed: Literal[False]
```

closes #13779

## Test Plan

See `resources/mdtest/comparison/unions.md`
2024-10-17 11:03:37 +02:00
Simon Brugman
6b7a738825 Add explanation of fixable in --statistics command (#13774)
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-10-17 08:02:00 +02:00
Santhosh Solomon
4ea4bbb155 [flake8-bandit] Detect patterns from multi line SQL statements (S608) (#13574)
Co-authored-by: Santhosh Solomon <santhosh@advarisk.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2024-10-17 05:42:03 +00:00
aditya pillai
ed4a0b34ba [red-knot] don't include Unknown in the type for a conditionally-defined import (#13563)
## Summary

Fixes the bug described in #13514 where an unbound public type defaulted
to the type or `Unknown`, whereas it should only be the type if unbound.

## Test Plan

Added a new test case

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-10-16 13:46:03 -07:00
Micha Reiser
2095ea8372 Add scope assertion to TypeInference.extend (#13764)
## Summary

This PR adds a debug assertion that asserts that `TypeInference::extend`
is only called on results that have the same scope.
This is critical because `expressions` uses `ScopedExpressionId` that
are local and merging expressions from different
scopes would lead to incorrect expression types.

We could consider storing `scope` only on `TypeInference` for debug
builds. Doing so has the advantage that the `TypeInference` type is
smaller of which we'll have many. However, a `ScopeId` is a `u32`... so
it shouldn't matter that much and it avoids storing the `scope` both on
`TypeInference` and `TypeInferenceBuilder`

## Test Plan

`cargo test`
2024-10-16 08:44:25 -07:00
Alex Waygood
6282402a8c [red-knot] Add control flow for try/except blocks (#13729) 2024-10-16 13:03:59 +00:00
Raphael Gaschignard
d25673f664 [red-knot] Do not panic if named expressions show up in assignment position (#13711)
Co-authored-by: Carl Meyer <carl@astral.sh>
2024-10-16 12:42:39 +00:00
Micha Reiser
a94914dc35 Enable preview mode for 'unstable' black tests (#13776) 2024-10-16 12:25:34 +00:00
cake-monotone
2ffc3fad47 [red-knot] Implement Type::Tuple Comparisons (#13712)
## Summary

This PR implements comparisons for (tuple, tuple).

It will close #13688 and complete an item in #13618 once merged.

## Test Plan

Basic tests are included for (tuple, tuple) comparisons.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-10-16 11:39:55 +00:00
Micha Reiser
8f5b2aac9a Refactor: Remove StringPart and AnyStringPart in favor of StringLikePart (#13772) 2024-10-16 12:52:06 +02:00
David Peter
b85be6297e [red knot] Minor follow-up tasks regarding singleton types (#13769)
## Summary

- Do not treat empty tuples as singletons after discussion [1]
- Improve comment regarding intersection types
- Resolve unnecessary TODO in Markdown test

[1]
https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957

## Test Plan

—
2024-10-16 11:30:03 +02:00
Alex Waygood
fb1d1e3241 [red-knot] Simplify some branches in infer_subscript_expression (#13762)
## Summary

Just a small simplification to remove some unnecessary complexity here.
Rather than using separate branches for subscript expressions involving
boolean literals, we can simply convert them to integer literals and
reuse the logic in the `IntLiteral` branches.

## Test Plan

`cargo test -p red_knot_python_semantic`
2024-10-16 07:58:24 +01:00
Dhruv Manilawala
c6b311c546 Update setup image for PyCharm External Tool (#13767)
## Summary

fixes: #13765 

## Preview

<img width="624" alt="Screenshot 2024-10-16 at 10 05 57"
src="https://github.com/user-attachments/assets/c0eccda5-3cf1-4119-a9b5-d86b01a8c64c">
2024-10-16 04:41:37 +00:00
Dhruv Manilawala
b16f665a81 [red-knot] Infer target types for unpacked tuple assignment (#13316)
## Summary

This PR adds support for unpacking tuple expression in an assignment
statement where the target expression can be a tuple or a list (the
allowed sequence targets).

The implementation introduces a new `infer_assignment_target` which can
then be used for other targets like the ones in for loops as well. This
delegates it to the `infer_definition`. The final implementation uses a
recursive function that visits the target expression in source order and
compares the variable node that corresponds to the definition. At the
same time, it keeps track of where it is on the assignment value type.

The logic also accounts for the number of elements on both sides such
that it matches even if there's a gap in between. For example, if
there's a starred expression like `(a, *b, c) = (1, 2, 3)`, then the
type of `a` will be `Literal[1]` and the type of `b` will be
`Literal[2]`.

There are a couple of follow-ups that can be done:
* Use this logic for other target positions like `for` loop
* Add diagnostics for mis-match length between LHS and RHS

## Test Plan

Add various test cases using the new markdown test framework.
Validate that existing test cases pass.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-10-15 19:07:11 +00:00
Alex
d77480768d [red-knot] Port type inference tests to new test framework (#13719)
## Summary

Porting infer tests to new markdown tests framework.

Link to the corresponding issue: #13696

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2024-10-15 11:23:46 -07:00
github-actions[bot]
5fa82fb0cd Sync vendored typeshed stubs (#13753) 2024-10-15 13:36:11 +00:00
David Peter
74bf4b0653 [red knot] Fix narrowing for '… is not …' type guards, add '… is …' type guards (#13758)
## Summary

- Fix a bug with `… is not …` type guards.
 
  Previously, in an example like
  ```py
  x = [1]
  y = [1]
  
  if x is not y:
      reveal_type(x)
  ```
  we would infer a type of `list[int] & ~list[int] == Never` for `x`
  inside the conditional (instead of `list[int]`), since we built a
  (negative) intersection with the type of the right hand side (`y`).
  However, as this example shows, this assumption can only be made for
  singleton types (types with a single inhabitant) such as `None`.
- Add support for `… is …` type guards.

closes #13715

## Test Plan

Moved existing `narrow_…` tests to Markdown-based tests and added new
ones (including a regression test for the bug described above). Note
that will create some conflicts with
https://github.com/astral-sh/ruff/pull/13719. I tried to establish the
correct organizational structure as proposed in
https://github.com/astral-sh/ruff/pull/13719#discussion_r1800188105
2024-10-15 14:49:32 +02:00
Micha Reiser
5f65e842e8 Upgrade salsa (#13757) 2024-10-15 11:06:32 +00:00
Micha Reiser
72ac6cd5a5 Fix TODO directive out of bounds acccess (#13756) 2024-10-15 10:49:53 +02:00
David Peter
04b636cba2 [red knot] Use memmem::find instead of custom version (#13750)
This is a follow-up on #13746:

- Use `memmem::find` instead of rolling our own inferior version.
- Avoid `x.as_ref()` calls using `&**x`
2024-10-14 15:17:19 +02:00
232 changed files with 10596 additions and 8732 deletions

View File

@@ -1,5 +1,52 @@
# Changelog
## 0.7.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.7.0) for a migration guide and overview of the changes!
### Breaking changes
- The pytest rules `PT001` and `PT023` now default to omitting the decorator parentheses when there are no arguments
([#12838](https://github.com/astral-sh/ruff/pull/12838), [#13292](https://github.com/astral-sh/ruff/pull/13292)).
This was a change that we attempted to make in Ruff v0.6.0, but only partially made due to an error on our part.
See the [blog post](https://astral.sh/blog/ruff-v0.7.0) for more details.
- The `useless-try-except` rule (in our `tryceratops` category) has been recoded from `TRY302` to
`TRY203` ([#13502](https://github.com/astral-sh/ruff/pull/13502)). This ensures Ruff's code is consistent with
the same rule in the [`tryceratops`](https://github.com/guilatrova/tryceratops) linter.
- The `lint.allow-unused-imports` setting has been removed ([#13677](https://github.com/astral-sh/ruff/pull/13677)). Use
[`lint.pyflakes.allow-unused-imports`](https://docs.astral.sh/ruff/settings/#lint_pyflakes_allowed-unused-imports)
instead.
### Formatter preview style
- Normalize implicit concatenated f-string quotes per part ([#13539](https://github.com/astral-sh/ruff/pull/13539))
### Preview linter features
- \[`refurb`\] implement `hardcoded-string-charset` (FURB156) ([#13530](https://github.com/astral-sh/ruff/pull/13530))
- \[`refurb`\] Count codepoints not bytes for `slice-to-remove-prefix-or-suffix (FURB188)` ([#13631](https://github.com/astral-sh/ruff/pull/13631))
### Rule changes
- \[`pylint`\] Mark `PLE1141` fix as unsafe ([#13629](https://github.com/astral-sh/ruff/pull/13629))
- \[`flake8-async`\] Consider async generators to be "checkpoints" for `cancel-scope-no-checkpoint` (`ASYNC100`) ([#13639](https://github.com/astral-sh/ruff/pull/13639))
- \[`flake8-bugbear`\] Do not suggest setting parameter `strict=` to `False` in `B905` diagnostic message ([#13656](https://github.com/astral-sh/ruff/pull/13656))
- \[`flake8-todos`\] Only flag the word "TODO", not words starting with "todo" (`TD006`) ([#13640](https://github.com/astral-sh/ruff/pull/13640))
- \[`pycodestyle`\] Fix whitespace-related false positives and false negatives inside type-parameter lists (`E231`, `E251`) ([#13704](https://github.com/astral-sh/ruff/pull/13704))
- \[`flake8-simplify`\] Stabilize preview behavior for `SIM115` so that the rule can detect files
being opened from a wider range of standard-library functions ([#12959](https://github.com/astral-sh/ruff/pull/12959)).
### CLI
- Add explanation of fixable in `--statistics` command ([#13774](https://github.com/astral-sh/ruff/pull/13774))
### Bug fixes
- \[`pyflakes`\] Allow `ipytest` cell magic (`F401`) ([#13745](https://github.com/astral-sh/ruff/pull/13745))
- \[`flake8-use-pathlib`\] Fix `PTH123` false positive when `open` is passed a file descriptor ([#13616](https://github.com/astral-sh/ruff/pull/13616))
- \[`flake8-bandit`\] Detect patterns from multi line SQL statements (`S608`) ([#13574](https://github.com/astral-sh/ruff/pull/13574))
- \[`flake8-pyi`\] - Fix dropped expressions in `PYI030` autofix ([#13727](https://github.com/astral-sh/ruff/pull/13727))
## 0.6.9
### Preview features

13
Cargo.lock generated
View File

@@ -2083,6 +2083,7 @@ dependencies = [
"hashbrown 0.15.0",
"insta",
"itertools 0.13.0",
"memchr",
"ordermap",
"red_knot_test",
"red_knot_vendored",
@@ -2319,7 +2320,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.6.9"
version = "0.7.0"
dependencies = [
"anyhow",
"argfile",
@@ -2538,7 +2539,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.6.9"
version = "0.7.0"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2858,7 +2859,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.6.9"
version = "0.7.0"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3008,7 +3009,7 @@ checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "salsa"
version = "0.18.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=4a7c955255e707e64e43f3ce5eabb771ae067768#4a7c955255e707e64e43f3ce5eabb771ae067768"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b14be5c0392f4c55eca60b92e457a35549372382#b14be5c0392f4c55eca60b92e457a35549372382"
dependencies = [
"append-only-vec",
"arc-swap",
@@ -3028,12 +3029,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.1.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=4a7c955255e707e64e43f3ce5eabb771ae067768#4a7c955255e707e64e43f3ce5eabb771ae067768"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b14be5c0392f4c55eca60b92e457a35549372382#b14be5c0392f4c55eca60b92e457a35549372382"
[[package]]
name = "salsa-macros"
version = "0.18.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=4a7c955255e707e64e43f3ce5eabb771ae067768#4a7c955255e707e64e43f3ce5eabb771ae067768"
source = "git+https://github.com/salsa-rs/salsa.git?rev=b14be5c0392f4c55eca60b92e457a35549372382#b14be5c0392f4c55eca60b92e457a35549372382"
dependencies = [
"heck",
"proc-macro2",

View File

@@ -117,7 +117,7 @@ rayon = { version = "1.10.0" }
regex = { version = "1.10.2" }
rstest = { version = "0.22.0", default-features = false }
rustc-hash = { version = "2.0.0" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "4a7c955255e707e64e43f3ce5eabb771ae067768" }
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "b14be5c0392f4c55eca60b92e457a35549372382" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }

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.6.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.6.9/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.7.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.7.0/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.6.9
rev: v0.7.0
hooks:
# Run the linter.
- id: ruff

View File

@@ -501,7 +501,10 @@ fn directory_moved_to_workspace() -> anyhow::Result<()> {
.with_context(|| "Failed to create __init__.py")?;
std::fs::write(a_original_path.as_std_path(), "").with_context(|| "Failed to create a.py")?;
let sub_a_module = resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap());
let sub_a_module = resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap(),
);
assert_eq!(sub_a_module, None);
assert_eq!(
@@ -525,7 +528,11 @@ fn directory_moved_to_workspace() -> anyhow::Result<()> {
.expect("a.py to exist");
// `import sub.a` should now resolve
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_some());
assert_eq!(
case.collect_package_files(&case.workspace_path("bar.py")),
@@ -544,7 +551,11 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
])?;
let bar = case.system_file(case.workspace_path("bar.py")).unwrap();
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_some());
let sub_path = case.workspace_path("sub");
let init_file = case
@@ -569,7 +580,11 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
case.apply_changes(changes);
// `import sub.a` should no longer resolve
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_none());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_none());
assert!(!init_file.exists(case.db()));
assert!(!a_file.exists(case.db()));
@@ -592,10 +607,14 @@ fn directory_renamed() -> anyhow::Result<()> {
let bar = case.system_file(case.workspace_path("bar.py")).unwrap();
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some());
assert!(resolve_module(
case.db().upcast(),
ModuleName::new_static("foo.baz").unwrap()
&ModuleName::new_static("sub.a").unwrap()
)
.is_some());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("foo.baz").unwrap()
)
.is_none());
@@ -623,11 +642,15 @@ fn directory_renamed() -> anyhow::Result<()> {
case.apply_changes(changes);
// `import sub.a` should no longer resolve
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_none());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_none());
// `import foo.baz` should now resolve
assert!(resolve_module(
case.db().upcast(),
ModuleName::new_static("foo.baz").unwrap()
&ModuleName::new_static("foo.baz").unwrap()
)
.is_some());
@@ -665,7 +688,11 @@ fn directory_deleted() -> anyhow::Result<()> {
let bar = case.system_file(case.workspace_path("bar.py")).unwrap();
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_some());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_some());
let sub_path = case.workspace_path("sub");
@@ -688,7 +715,11 @@ fn directory_deleted() -> anyhow::Result<()> {
case.apply_changes(changes);
// `import sub.a` should no longer resolve
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("sub.a").unwrap()).is_none());
assert!(resolve_module(
case.db().upcast(),
&ModuleName::new_static("sub.a").unwrap()
)
.is_none());
assert!(!init_file.exists(case.db()));
assert!(!a_file.exists(case.db()));
@@ -710,7 +741,7 @@ fn search_path() -> anyhow::Result<()> {
let site_packages = case.root_path().join("site_packages");
assert_eq!(
resolve_module(case.db(), ModuleName::new("a").unwrap()),
resolve_module(case.db(), &ModuleName::new("a").unwrap()),
None
);
@@ -720,7 +751,7 @@ fn search_path() -> anyhow::Result<()> {
case.apply_changes(changes);
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("a").unwrap()).is_some());
assert!(resolve_module(case.db().upcast(), &ModuleName::new_static("a").unwrap()).is_some());
assert_eq!(
case.collect_package_files(&case.workspace_path("bar.py")),
&[case.system_file(case.workspace_path("bar.py")).unwrap()]
@@ -736,7 +767,7 @@ fn add_search_path() -> anyhow::Result<()> {
let site_packages = case.workspace_path("site_packages");
std::fs::create_dir_all(site_packages.as_std_path())?;
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("a").unwrap()).is_none());
assert!(resolve_module(case.db().upcast(), &ModuleName::new_static("a").unwrap()).is_none());
// Register site-packages as a search path.
case.update_search_path_settings(SearchPathConfiguration {
@@ -751,7 +782,7 @@ fn add_search_path() -> anyhow::Result<()> {
case.apply_changes(changes);
assert!(resolve_module(case.db().upcast(), ModuleName::new_static("a").unwrap()).is_some());
assert!(resolve_module(case.db().upcast(), &ModuleName::new_static("a").unwrap()).is_some());
Ok(())
}
@@ -805,7 +836,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
// Unset the custom typeshed directory.
assert_eq!(
resolve_module(case.db(), ModuleName::new("os").unwrap()),
resolve_module(case.db(), &ModuleName::new("os").unwrap()),
None
);
@@ -820,7 +851,7 @@ fn changed_versions_file() -> anyhow::Result<()> {
case.apply_changes(changes);
assert!(resolve_module(case.db(), ModuleName::new("os").unwrap()).is_some());
assert!(resolve_module(case.db(), &ModuleName::new("os").unwrap()).is_some());
Ok(())
}
@@ -1044,7 +1075,7 @@ mod unix {
let baz = resolve_module(
case.db().upcast(),
ModuleName::new_static("bar.baz").unwrap(),
&ModuleName::new_static("bar.baz").unwrap(),
)
.expect("Expected bar.baz to exist in site-packages.");
let baz_workspace = case.workspace_path("bar/baz.py");
@@ -1125,7 +1156,7 @@ mod unix {
let baz = resolve_module(
case.db().upcast(),
ModuleName::new_static("bar.baz").unwrap(),
&ModuleName::new_static("bar.baz").unwrap(),
)
.expect("Expected bar.baz to exist in site-packages.");
let bar_baz = case.workspace_path("bar/baz.py");
@@ -1229,7 +1260,7 @@ mod unix {
let baz = resolve_module(
case.db().upcast(),
ModuleName::new_static("bar.baz").unwrap(),
&ModuleName::new_static("bar.baz").unwrap(),
)
.expect("Expected bar.baz to exist in site-packages.");
let baz_site_packages_path =

View File

@@ -34,6 +34,7 @@ hashbrown = { workspace = true }
smallvec = { workspace = true }
static_assertions = { workspace = true }
test-case = { workspace = true }
memchr = { workspace = true }
[dev-dependencies]
ruff_db = { workspace = true, features = ["os", "testing"] }

View File

@@ -0,0 +1,25 @@
# Assignment with annotations
## Annotation only transparent to local inference
```py
x = 1
x: int
y = x
reveal_type(y) # revealed: Literal[1]
```
## Violates own annotation
```py
x: int = 'foo' # error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `int`"
```
## Violates previous annotation
```py
x: int
x = 'foo' # error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `int`"
```

View File

@@ -0,0 +1,9 @@
# Multi-target assignment
## Basic
```py
x = y = 1
reveal_type(x) # revealed: Literal[1]
reveal_type(y) # revealed: Literal[1]
```

View File

@@ -0,0 +1,24 @@
# Unbound
## Unbound
```py
x = foo
foo = 1
reveal_type(x) # revealed: Unbound
```
## Unbound class variable
Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
```py
x = 1
class C:
y = x
if flag:
x = 2
reveal_type(C.x) # revealed: Literal[2]
reveal_type(C.y) # revealed: Literal[1]
```

View File

@@ -0,0 +1,17 @@
# Walrus operator
## Basic
```py
x = (y := 1) + 1
reveal_type(x) # revealed: Literal[2]
reveal_type(y) # revealed: Literal[1]
```
## Walrus self-addition
```py
x = 0
(x := x + 1)
reveal_type(x) # revealed: Literal[1]
```

View File

@@ -0,0 +1,15 @@
# Class attributes
## Union of attributes
```py
if flag:
class C:
x = 1
else:
class C:
x = 2
y = C.x
reveal_type(y) # revealed: Literal[1, 2]
```

View File

@@ -0,0 +1,36 @@
## Binary operations on integers
## Basic Arithmetic
```py
a = 2 + 1
b = a - 4
c = a * b
d = c // 3
e = c / 3
f = 5 % 3
reveal_type(a) # revealed: Literal[3]
reveal_type(b) # revealed: Literal[-1]
reveal_type(c) # revealed: Literal[-3]
reveal_type(d) # revealed: Literal[-1]
reveal_type(e) # revealed: float
reveal_type(f) # revealed: Literal[2]
```
## Division by Zero
```py
# TODO: `a` should be `int` and `e` should be `float` once we support inference.
a = 1 / 0 # error: "Cannot divide object of type `Literal[1]` by zero"
b = 2 // 0 # error: "Cannot floor divide object of type `Literal[2]` by zero"
c = 3 % 0 # error: "Cannot reduce object of type `Literal[3]` modulo zero"
d = int() / 0 # error: "Cannot divide object of type `int` by zero"
e = 1.0 / 0 # error: "Cannot divide object of type `float` by zero"
reveal_type(a) # revealed: float
reveal_type(b) # revealed: int
reveal_type(c) # revealed: int
reveal_type(d) # revealed: @Todo
reveal_type(e) # revealed: @Todo
```

View File

@@ -0,0 +1,21 @@
# Callable instance
## Dunder call
```py
class Multiplier:
def __init__(self, factor: float):
self.factor = factor
def __call__(self, number: float) -> float:
return number * self.factor
a = Multiplier(2.0)(3.0)
class Unit: ...
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
reveal_type(a) # revealed: float
reveal_type(b) # revealed: Unknown
```

View File

@@ -0,0 +1,8 @@
# Constructor
```py
class Foo: ...
x = Foo()
reveal_type(x) # revealed: Foo
```

View File

@@ -0,0 +1,51 @@
# Call expression
## Simple
```py
def get_int() -> int:
return 42
x = get_int()
reveal_type(x) # revealed: int
```
## Async
```py
async def get_int_async() -> int:
return 42
x = get_int_async()
# TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]`
reveal_type(x) # revealed: @Todo
```
## Decorated
```py
from typing import Callable
def foo() -> int:
return 42
def decorator(func) -> Callable[[], int]:
return foo
@decorator
def bar() -> str:
return 'bar'
x = bar()
# TODO: should reveal `int`, as the decorator replaces `bar` with `foo`
reveal_type(x) # revealed: @Todo
```
## Invalid callable
```py
nonsense = 123
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
```

View File

@@ -0,0 +1,74 @@
# Unions in calls
## Union of return types
```py
if flag:
def f() -> int:
return 1
else:
def f() -> str:
return 'foo'
x = f()
reveal_type(x) # revealed: int | str
```
## Calling with an unknown union
```py
from nonexistent import f # error: [unresolved-import] "Cannot resolve import `nonexistent`"
if flag:
def f() -> int:
return 1
x = f()
reveal_type(x) # revealed: Unknown | int
```
## Non-callable elements in a union
Calling a union with a non-callable element should emit a diagnostic.
```py
if flag:
f = 1
else:
def f() -> int:
return 1
x = f() # error: "Object of type `Literal[1] | Literal[f]` is not callable (due to union element `Literal[1]`)"
reveal_type(x) # revealed: Unknown | int
```
## Multiple non-callable elements in a union
Calling a union with multiple non-callable elements should mention all of them in the diagnostic.
```py
if flag:
f = 1
elif flag2:
f = 'foo'
else:
def f() -> int:
return 1
x = f() # error: "Object of type `Literal[1] | Literal["foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])"
reveal_type(x) # revealed: Unknown | int
```
## All non-callable union elements
Calling a union with no callable elements can emit a simpler diagnostic.
```py
if flag:
f = 1
else:
f = 'foo'
x = f() # error: "Object of type `Literal[1] | Literal["foo"]` is not callable"
reveal_type(x) # revealed: Unknown
```

View File

@@ -0,0 +1,41 @@
# Comparing integers
## Integer literals
```py
a = 1 == 1 == True
b = 1 == 1 == 2 == 4
c = False < True <= 2 < 3 != 6
d = 1 < 1
e = 1 > 1
f = 1 is 1
g = 1 is not 1
h = 1 is 2
i = 1 is not 7
j = 1 <= "" and 0 < 1
reveal_type(a) # revealed: Literal[True]
reveal_type(b) # revealed: Literal[False]
reveal_type(c) # revealed: Literal[True]
reveal_type(d) # revealed: Literal[False]
reveal_type(e) # revealed: Literal[False]
reveal_type(f) # revealed: bool
reveal_type(g) # revealed: bool
reveal_type(h) # revealed: Literal[False]
reveal_type(i) # revealed: Literal[True]
reveal_type(j) # revealed: @Todo | Literal[True]
```
## Integer instance
```py
# TODO: implement lookup of `__eq__` on typeshed `int` stub.
def int_instance() -> int: ...
a = 1 == int_instance()
b = 9 < int_instance()
c = int_instance() < int_instance()
reveal_type(a) # revealed: @Todo
reveal_type(b) # revealed: bool
reveal_type(c) # revealed: bool
```

View File

@@ -0,0 +1,37 @@
# Non boolean returns
Walking through examples:
- `a = A() < B() < C()`
1. `A() < B() and B() < C()` - split in N comparison
1. `A()` and `B()` - evaluate outcome types
1. `bool` and `bool` - evaluate truthiness
1. `A | B` - union of "first true" types
- `b = 0 < 1 < A() < 3`
1. `0 < 1 and 1 < A() and A() < 3` - split in N comparison
1. `True` and `bool` and `A` - evaluate outcome types
1. `True` and `bool` and `bool` - evaluate truthiness
1. `bool | A` - union of "true" types
- `c = 10 < 0 < A() < B() < C()` short-circuit to False
```py
from __future__ import annotations
class A:
def __lt__(self, other) -> A: ...
class B:
def __lt__(self, other) -> B: ...
class C:
def __lt__(self, other) -> C: ...
a = A() < B() < C()
b = 0 < 1 < A() < 3
c = 10 < 0 < A() < B() < C()
reveal_type(a) # revealed: A | B
reveal_type(b) # revealed: bool | A
reveal_type(c) # revealed: Literal[False]
```

View File

@@ -0,0 +1,29 @@
# Comparing strings
## String literals
```py
def str_instance() -> str: ...
a = "abc" == "abc"
b = "ab_cd" <= "ab_ce"
c = "abc" in "ab cd"
d = "" not in "hello"
e = "--" is "--"
f = "A" is "B"
g = "--" is not "--"
h = "A" is not "B"
i = str_instance() < "..."
# ensure we're not comparing the interned salsa symbols, which compare by order of declaration.
j = "ab" < "ab_cd"
reveal_type(a) # revealed: Literal[True]
reveal_type(b) # revealed: Literal[True]
reveal_type(c) # revealed: Literal[False]
reveal_type(d) # revealed: Literal[False]
reveal_type(e) # revealed: bool
reveal_type(f) # revealed: Literal[False]
reveal_type(g) # revealed: bool
reveal_type(h) # revealed: Literal[True]
reveal_type(i) # revealed: bool
reveal_type(j) # revealed: Literal[True]
```

View File

@@ -0,0 +1,205 @@
# Comparison - Tuples
## Heterogeneous
For tuples like `tuple[int, str, Literal[1]]`
### Value Comparisons
"Value Comparisons" refers to the operators: `==`, `!=`, `<`, `<=`, `>`, `>=`
#### Results without Ambiguity
Cases where the result can be definitively inferred as a `BooleanLiteral`.
```py
a = (1, "test", (3, 13), True)
b = (1, "test", (3, 14), False)
reveal_type(a == a) # revealed: Literal[True]
reveal_type(a != a) # revealed: Literal[False]
reveal_type(a < a) # revealed: Literal[False]
reveal_type(a <= a) # revealed: Literal[True]
reveal_type(a > a) # revealed: Literal[False]
reveal_type(a >= a) # revealed: Literal[True]
reveal_type(a == b) # revealed: Literal[False]
reveal_type(a != b) # revealed: Literal[True]
reveal_type(a < b) # revealed: Literal[True]
reveal_type(a <= b) # revealed: Literal[True]
reveal_type(a > b) # revealed: Literal[False]
reveal_type(a >= b) # revealed: Literal[False]
```
Even when tuples have different lengths, comparisons should be handled appropriately.
```py path=different_length.py
a = (1, 2, 3)
b = (1, 2, 3, 4)
reveal_type(a == b) # revealed: Literal[False]
reveal_type(a != b) # revealed: Literal[True]
reveal_type(a < b) # revealed: Literal[True]
reveal_type(a <= b) # revealed: Literal[True]
reveal_type(a > b) # revealed: Literal[False]
reveal_type(a >= b) # revealed: Literal[False]
c = ("a", "b", "c", "d")
d = ("a", "b", "c")
reveal_type(c == d) # revealed: Literal[False]
reveal_type(c != d) # revealed: Literal[True]
reveal_type(c < d) # revealed: Literal[False]
reveal_type(c <= d) # revealed: Literal[False]
reveal_type(c > d) # revealed: Literal[True]
reveal_type(c >= d) # revealed: Literal[True]
```
#### Results with Ambiguity
```py
def bool_instance() -> bool: ...
def int_instance() -> int: ...
a = (bool_instance(),)
b = (int_instance(),)
# TODO: All @Todo should be `bool`
reveal_type(a == a) # revealed: @Todo
reveal_type(a != a) # revealed: @Todo
reveal_type(a < a) # revealed: @Todo
reveal_type(a <= a) # revealed: @Todo
reveal_type(a > a) # revealed: @Todo
reveal_type(a >= a) # revealed: @Todo
reveal_type(a == b) # revealed: @Todo
reveal_type(a != b) # revealed: @Todo
reveal_type(a < b) # revealed: @Todo
reveal_type(a <= b) # revealed: @Todo
reveal_type(a > b) # revealed: @Todo
reveal_type(a >= b) # revealed: @Todo
```
#### Comparison Unsupported
If two tuples contain types that do not support comparison, the result may be `Unknown`.
However, `==` and `!=` are exceptions and can still provide definite results.
```py
a = (1, 2)
b = (1, "hello")
# TODO: should be Literal[False]
reveal_type(a == b) # revealed: @Todo
# TODO: should be Literal[True]
reveal_type(a != b) # revealed: @Todo
# TODO: should be Unknown and add more informative diagnostics
reveal_type(a < b) # revealed: @Todo
reveal_type(a <= b) # revealed: @Todo
reveal_type(a > b) # revealed: @Todo
reveal_type(a >= b) # revealed: @Todo
```
However, if the lexicographic comparison completes without reaching a point where str and int are compared,
Python will still produce a result based on the prior elements.
```py path=short_circuit.py
a = (1, 2)
b = (999999, "hello")
reveal_type(a == b) # revealed: Literal[False]
reveal_type(a != b) # revealed: Literal[True]
reveal_type(a < b) # revealed: Literal[True]
reveal_type(a <= b) # revealed: Literal[True]
reveal_type(a > b) # revealed: Literal[False]
reveal_type(a >= b) # revealed: Literal[False]
```
#### Matryoshka Tuples
```py
a = (1, True, "Hello")
b = (a, a, a)
c = (b, b, b)
reveal_type(c == c) # revealed: Literal[True]
reveal_type(c != c) # revealed: Literal[False]
reveal_type(c < c) # revealed: Literal[False]
reveal_type(c <= c) # revealed: Literal[True]
reveal_type(c > c) # revealed: Literal[False]
reveal_type(c >= c) # revealed: Literal[True]
```
#### Non Boolean Rich Comparisons
```py
class A():
def __eq__(self, o) -> str: ...
def __ne__(self, o) -> int: ...
def __lt__(self, o) -> float: ...
def __le__(self, o) -> object: ...
def __gt__(self, o) -> tuple: ...
def __ge__(self, o) -> list: ...
a = (A(), A())
# TODO: All @Todo should be bool
reveal_type(a == a) # revealed: @Todo
reveal_type(a != a) # revealed: @Todo
reveal_type(a < a) # revealed: @Todo
reveal_type(a <= a) # revealed: @Todo
reveal_type(a > a) # revealed: @Todo
reveal_type(a >= a) # revealed: @Todo
```
### Membership Test Comparisons
"Membership Test Comparisons" refers to the operators `in` and `not in`.
```py
def int_instance() -> int: ...
a = (1, 2)
b = ((3, 4), (1, 2))
c = ((1, 2, 3), (4, 5, 6))
d = ((int_instance(), int_instance()), (int_instance(), int_instance()))
reveal_type(a in b) # revealed: Literal[True]
reveal_type(a not in b) # revealed: Literal[False]
reveal_type(a in c) # revealed: Literal[False]
reveal_type(a not in c) # revealed: Literal[True]
# TODO: All @Todo should be bool
reveal_type(a in d) # revealed: @Todo
reveal_type(a not in d) # revealed: @Todo
```
### Identity Comparisons
"Identity Comparisons" refers to `is` and `is not`.
```py
a = (1, 2)
b = ("a", "b")
c = (1, 2, 3)
reveal_type(a is (1, 2)) # revealed: bool
reveal_type(a is not (1, 2)) # revealed: bool
# TODO: Update to Literal[False] once str == int comparison is implemented
reveal_type(a is b) # revealed: @Todo
# TODO: Update to Literal[True] once str == int comparison is implemented
reveal_type(a is not b) # revealed: @Todo
reveal_type(a is c) # revealed: Literal[False]
reveal_type(a is not c) # revealed: Literal[True]
```
## Homogeneous
For tuples like `tuple[int, ...]`, `tuple[Any, ...]`
// TODO

View File

@@ -0,0 +1,76 @@
# Comparison: Unions
## Union on one side of the comparison
Comparisons on union types need to consider all possible cases:
```py
one_or_two = 1 if flag else 2
reveal_type(one_or_two <= 2) # revealed: Literal[True]
reveal_type(one_or_two <= 1) # revealed: bool
reveal_type(one_or_two <= 0) # revealed: Literal[False]
reveal_type(2 >= one_or_two) # revealed: Literal[True]
reveal_type(1 >= one_or_two) # revealed: bool
reveal_type(0 >= one_or_two) # revealed: Literal[False]
reveal_type(one_or_two < 1) # revealed: Literal[False]
reveal_type(one_or_two < 2) # revealed: bool
reveal_type(one_or_two < 3) # revealed: Literal[True]
reveal_type(one_or_two > 0) # revealed: Literal[True]
reveal_type(one_or_two > 1) # revealed: bool
reveal_type(one_or_two > 2) # revealed: Literal[False]
reveal_type(one_or_two == 3) # revealed: Literal[False]
reveal_type(one_or_two == 1) # revealed: bool
reveal_type(one_or_two != 3) # revealed: Literal[True]
reveal_type(one_or_two != 1) # revealed: bool
a_or_ab = "a" if flag else "ab"
reveal_type(a_or_ab in "ab") # revealed: Literal[True]
reveal_type("a" in a_or_ab) # revealed: Literal[True]
reveal_type("c" not in a_or_ab) # revealed: Literal[True]
reveal_type("a" not in a_or_ab) # revealed: Literal[False]
reveal_type("b" in a_or_ab) # revealed: bool
reveal_type("b" not in a_or_ab) # revealed: bool
one_or_none = 1 if flag else None
reveal_type(one_or_none is None) # revealed: bool
reveal_type(one_or_none is not None) # revealed: bool
```
## Union on both sides of the comparison
With unions on both sides, we need to consider the full cross product of
options when building the resulting (union) type:
```py
small = 1 if flag_s else 2
large = 2 if flag_l else 3
reveal_type(small <= large) # revealed: Literal[True]
reveal_type(small >= large) # revealed: bool
reveal_type(small < large) # revealed: bool
reveal_type(small > large) # revealed: Literal[False]
```
## Unsupported operations
Make sure we emit a diagnostic if *any* of the possible comparisons is
unsupported. For now, we fall back to `bool` for the result type instead of
trying to infer something more precise from the other (supported) variants:
```py
x = [1, 2] if flag else 1
result = 1 in x # error: "Operator `in` is not supported"
reveal_type(result) # revealed: bool
```

View File

@@ -0,0 +1,15 @@
# Unsupported operators
```py
a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`"
b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`"
c = object() < 5 # error: "Operator `<` is not supported for types `object` and `Literal[5]`"
# TODO should error, need to check if __lt__ signature is valid for right operand
d = 5 < object()
reveal_type(a) # revealed: bool
reveal_type(b) # revealed: bool
reveal_type(c) # revealed: Unknown
# TODO: should be `Unknown`
reveal_type(d) # revealed: bool
```

View File

@@ -0,0 +1,35 @@
# If expressions
## Simple if-expression
```py
x = 1 if flag else 2
reveal_type(x) # revealed: Literal[1, 2]
```
## If-expression with walrus operator
```py
y = 0
z = 0
x = (y := 1) if flag else (z := 2)
a = y
b = z
reveal_type(x) # revealed: Literal[1, 2]
reveal_type(a) # revealed: Literal[0, 1]
reveal_type(b) # revealed: Literal[0, 2]
```
## Nested if-expression
```py
x = 1 if flag else 2 if flag2 else 3
reveal_type(x) # revealed: Literal[1, 2, 3]
```
## None
```py
x = 1 if flag else None
reveal_type(x) # revealed: Literal[1] | None
```

View File

@@ -0,0 +1,98 @@
# If statements
## Simple if
```py
y = 1
y = 2
if flag:
y = 3
x = y
reveal_type(x) # revealed: Literal[2, 3]
```
## Simple if-elif-else
```py
y = 1
y = 2
if flag:
y = 3
elif flag2:
y = 4
else:
r = y
y = 5
s = y
x = y
reveal_type(x) # revealed: Literal[3, 4, 5]
reveal_type(r) # revealed: Unbound | Literal[2]
reveal_type(s) # revealed: Unbound | Literal[5]
```
## Single symbol across if-elif-else
```py
if flag:
y = 1
elif flag2:
y = 2
else:
y = 3
reveal_type(y) # revealed: Literal[1, 2, 3]
```
## if-elif-else without else assignment
```py
y = 0
if flag:
y = 1
elif flag2:
y = 2
else:
pass
reveal_type(y) # revealed: Literal[0, 1, 2]
```
## if-elif-else with intervening assignment
```py
y = 0
if flag:
y = 1
z = 3
elif flag2:
y = 2
else:
pass
reveal_type(y) # revealed: Literal[0, 1, 2]
```
## Nested if statement
```py
y = 0
if flag:
if flag2:
y = 1
reveal_type(y) # revealed: Literal[0, 1]
```
## if-elif without else
```py
y = 1
y = 2
if flag:
y = 3
elif flag2:
y = 4
x = y
reveal_type(x) # revealed: Literal[2, 3, 4]
```

View File

@@ -0,0 +1,39 @@
# Pattern matching
## With wildcard
```py
match 0:
case 1:
y = 2
case _:
y = 3
reveal_type(y) # revealed: Literal[2, 3]
```
## Without wildcard
```py
match 0:
case 1:
y = 2
case 2:
y = 3
reveal_type(y) # revealed: Unbound | Literal[2, 3]
```
## Basic match
```py
y = 1
y = 2
match 0:
case 1:
y = 3
case 2:
y = 4
reveal_type(y) # revealed: Literal[2, 3, 4]
```

View File

@@ -0,0 +1,39 @@
# Errors while declaring
## Violates previous assignment
```py
x = 1
x: str # error: [invalid-declaration] "Cannot declare type `str` for inferred type `Literal[1]`"
```
## Incompatible declarations
```py
if flag:
x: str
else:
x: int
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
```
## Partial declarations
```py
if flag:
x: int
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int"
```
## Incompatible declarations with bad assignment
```py
if flag:
x: str
else:
x: int
# error: [conflicting-declarations]
# error: [invalid-assignment]
x = b'foo'
```

View File

@@ -0,0 +1,55 @@
# Exception Handling
## Single Exception
```py
import re
try:
x
except NameError as e:
reveal_type(e) # revealed: NameError
except re.error as f:
reveal_type(f) # revealed: error
```
## Unknown type in except handler does not cause spurious diagnostic
```py
from nonexistent_module import foo # error: [unresolved-import]
try:
x
except foo as e:
reveal_type(foo) # revealed: Unknown
reveal_type(e) # revealed: Unknown
```
## Multiple Exceptions in a Tuple
```py
EXCEPTIONS = (AttributeError, TypeError)
try:
x
except (RuntimeError, OSError) as e:
reveal_type(e) # revealed: RuntimeError | OSError
except EXCEPTIONS as f:
reveal_type(f) # revealed: AttributeError | TypeError
```
## Dynamic exception types
```py
def foo(x: type[AttributeError], y: tuple[type[OSError], type[RuntimeError]], z: tuple[type[BaseException], ...]):
try:
w
except x as e:
# TODO: should be `AttributeError`
reveal_type(e) # revealed: @Todo
except y as f:
# TODO: should be `OSError | RuntimeError`
reveal_type(f) # revealed: @Todo
except z as g:
# TODO: should be `BaseException`
reveal_type(g) # revealed: @Todo
```

View File

@@ -0,0 +1,641 @@
# Control flow for exception handlers
These tests assert that we understand the possible "definition states" (which
symbols might or might not be defined) in the various branches of a
`try`/`except`/`else`/`finally` block.
For a full writeup on the semantics of exception handlers,
see [this document][1].
The tests throughout this Markdown document use functions with names starting
with `could_raise_*` to mark definitions that might or might not succeed
(as the function could raise an exception). A type checker must assume that any
arbitrary function call could raise an exception in Python; this is just a
naming convention used in these tests for clarity, and to future-proof the
tests against possible future improvements whereby certain statements or
expressions could potentially be inferred as being incapable of causing an
exception to be raised.
## A single bare `except`
Consider the following `try`/`except` block, with a single bare `except:`.
There are different types for the variable `x` in the two branches of this
block, and we can't determine which branch might have been taken from the
perspective of code following this block. The inferred type after the block's
conclusion is therefore the union of the type at the end of the `try` suite
(`str`) and the type at the end of the `except` suite (`Literal[2]`).
*Within* the `except` suite, we must infer a union of all possible "definition
states" we could have been in at any point during the `try` suite. This is
because control flow could have jumped to the `except` suite without any of the
`try`-suite definitions successfully completing, with only *some* of the
`try`-suite definitions successfully completing, or indeed with *all* of them
successfully completing. The type of `x` at the beginning of the `except` suite
in this example is therefore `Literal[1] | str`, taking into account that we
might have jumped to the `except` suite before the
`x = could_raise_returns_str()` redefinition, but we *also* could have jumped
to the `except` suite *after* that redefinition.
```py path=union_type_inferred.py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except:
reveal_type(x) # revealed: Literal[1] | str
x = 2
reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: str | Literal[2]
```
If `x` has the same type at the end of both branches, however, the branches
unify and `x` is not inferred as having a union type following the
`try`/`except` block:
```py path=branches_unify_to_non_union_type.py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
x = could_raise_returns_str()
except:
x = could_raise_returns_str()
reveal_type(x) # revealed: str
```
## A non-bare `except`
For simple `try`/`except` blocks, an `except TypeError:` handler has the same
control flow semantics as an `except:` handler. An `except TypeError:` handler
will not catch *all* exceptions: if this is the only handler, it opens up the
possibility that an exception might occur that would not be handled. However,
as described in [the document on exception-handling semantics][1], that would
lead to termination of the scope. It's therefore irrelevant to consider this
possibility when it comes to control-flow analysis.
```py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = 2
reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: str | Literal[2]
```
## Multiple `except` branches
If the scope reaches the final `reveal_type` call in this example,
either the `try`-block suite of statements was executed in its entirety,
or exactly one `except` suite was executed in its entirety.
The inferred type of `x` at this point is the union of the types at the end of
the three suites:
- At the end of `try`, `type(x) == str`
- At the end of `except TypeError`, `x == 2`
- At the end of `except ValueError`, `x == 3`
```py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = 2
reveal_type(x) # revealed: Literal[2]
except ValueError:
reveal_type(x) # revealed: Literal[1] | str
x = 3
reveal_type(x) # revealed: Literal[3]
reveal_type(x) # revealed: str | Literal[2, 3]
```
## Exception handlers with `else` branches (but no `finally`)
If we reach the `reveal_type` call at the end of this scope,
either the `try` and `else` suites were both executed in their entireties,
or the `except` suite was executed in its entirety. The type of `x` at this
point is the union of the type at the end of the `else` suite and the type at
the end of the `except` suite:
- At the end of `else`, `x == 3`
- At the end of `except`, `x == 2`
```py path=single_except.py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = 2
reveal_type(x) # revealed: Literal[2]
else:
reveal_type(x) # revealed: str
x = 3
reveal_type(x) # revealed: Literal[3]
reveal_type(x) # revealed: Literal[2, 3]
```
For a block that has multiple `except` branches and an `else` branch, the same
principle applies. In order to reach the final `reveal_type` call,
either exactly one of the `except` suites must have been executed in its
entirety, or the `try` suite and the `else` suite must both have been executed
in their entireties:
```py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = 2
reveal_type(x) # revealed: Literal[2]
except ValueError:
reveal_type(x) # revealed: Literal[1] | str
x = 3
reveal_type(x) # revealed: Literal[3]
else:
reveal_type(x) # revealed: str
x = 4
reveal_type(x) # revealed: Literal[4]
reveal_type(x) # revealed: Literal[2, 3, 4]
```
## Exception handlers with `finally` branches (but no `except` branches)
A `finally` suite is *always* executed. As such, if we reach the `reveal_type`
call at the end of this example, we know that `x` *must* have been reassigned
to `2` during the `finally` suite. The type of `x` at the end of the example is
therefore `Literal[2]`:
```py path=redef_in_finally.py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
finally:
x = 2
reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: Literal[2]
```
If `x` was *not* redefined in the `finally` suite, however, things are somewhat
more complicated. If we reach the final `reveal_type` call,
unlike the state when we're visiting the `finally` suite,
we know that the `try`-block suite ran to completion.
This means that there are fewer possible states at this point than there were
when we were inside the `finally` block.
(Our current model does *not* correctly infer the types *inside* `finally`
suites, however; this is still a TODO item for us.)
```py path=no_redef_in_finally.py
def could_raise_returns_str() -> str:
return 'foo'
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
finally:
# TODO: should be Literal[1] | str
reveal_type(x) # revealed: str
reveal_type(x) # revealed: str
```
## Combining an `except` branch with a `finally` branch
As previously stated, we do not yet have accurate inference for types *inside*
`finally` suites. When we do, however, we will have to take account of the
following possibilities inside `finally` suites:
- The `try` suite could have run to completion
- Or we could have jumped from halfway through the `try` suite to an `except`
suite, and the `except` suite ran to completion
- Or we could have jumped from halfway through the `try` suite straight to the
`finally` suite due to an unhandled exception
- Or we could have jumped from halfway through the `try` suite to an
`except` suite, only for an exception raised in the `except` suite to cause
us to jump to the `finally` suite before the `except` suite ran to completion
```py path=redef_in_finally.py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
finally:
# TODO: should be `Literal[1] | str | bytes | bool`
reveal_type(x) # revealed: str | bool
x = 2
reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: Literal[2]
```
Now for an example without a redefinition in the `finally` suite.
As before, there *should* be fewer possibilities after completion of the
`finally` suite than there were during the `finally` suite itself.
(In some control-flow possibilities, some exceptions were merely *suspended*
during the `finally` suite; these lead to the scope's termination following the
conclusion of the `finally` suite.)
```py path=no_redef_in_finally.py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
finally:
# TODO: should be `Literal[1] | str | bytes | bool`
reveal_type(x) # revealed: str | bool
reveal_type(x) # revealed: str | bool
```
An example with multiple `except` branches and a `finally` branch:
```py path=multiple_except_branches.py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
def could_raise_returns_memoryview() -> memoryview:
return memoryview(b"")
def could_raise_returns_float() -> float:
return 3.14
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
except ValueError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_memoryview()
reveal_type(x) # revealed: memoryview
x = could_raise_returns_float()
reveal_type(x) # revealed: float
finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float`
reveal_type(x) # revealed: str | bool | float
reveal_type(x) # revealed: str | bool | float
```
## Combining `except`, `else` and `finally` branches
If the exception handler has an `else` branch, we must also take into account
the possibility that control flow could have jumped to the `finally` suite from
partway through the `else` suite due to an exception raised *there*.
```py path=single_except_branch.py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
def could_raise_returns_memoryview() -> memoryview:
return memoryview(b"")
def could_raise_returns_float() -> float:
return 3.14
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
else:
reveal_type(x) # revealed: str
x = could_raise_returns_memoryview()
reveal_type(x) # revealed: memoryview
x = could_raise_returns_float()
reveal_type(x) # revealed: float
finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float`
reveal_type(x) # revealed: bool | float
reveal_type(x) # revealed: bool | float
```
The same again, this time with multiple `except` branches:
```py path=multiple_except_branches.py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
def could_raise_returns_memoryview() -> memoryview:
return memoryview(b"")
def could_raise_returns_float() -> float:
return 3.14
def could_raise_returns_range() -> range:
return range(42)
def could_raise_returns_slice() -> slice:
return slice(None)
x = 1
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
except ValueError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_memoryview()
reveal_type(x) # revealed: memoryview
x = could_raise_returns_float()
reveal_type(x) # revealed: float
else:
reveal_type(x) # revealed: str
x = could_raise_returns_range()
reveal_type(x) # revealed: range
x = could_raise_returns_slice()
reveal_type(x) # revealed: slice
finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
reveal_type(x) # revealed: bool | float | slice
reveal_type(x) # revealed: bool | float | slice
```
## Nested `try`/`except` blocks
It would take advanced analysis, which we are not yet capable of, to be able
to determine that an exception handler always suppresses all exceptions. This
is partly because it is possible for statements in `except`, `else` and
`finally` suites to raise exceptions as well as statements in `try` suites.
This means that if an exception handler is nested inside the `try` statement of
an enclosing exception handler, it should (at least for now) be treated the
same as any other node: as a suite containing statements that could possibly
raise exceptions, which would lead to control flow jumping out of that suite
prior to the suite running to completion.
```py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_bool() -> bool:
return True
def could_raise_returns_memoryview() -> memoryview:
return memoryview(b"")
def could_raise_returns_float() -> float:
return 3.14
def could_raise_returns_range() -> range:
return range(42)
def could_raise_returns_slice() -> slice:
return slice(None)
def could_raise_returns_complex() -> complex:
return 3j
def could_raise_returns_bytearray() -> bytearray:
return bytearray()
class Foo: ...
class Bar: ...
def could_raise_returns_Foo() -> Foo:
return Foo()
def could_raise_returns_Bar() -> Bar:
return Bar()
x = 1
try:
try:
reveal_type(x) # revealed: Literal[1]
x = could_raise_returns_str()
reveal_type(x) # revealed: str
except TypeError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
x = could_raise_returns_bool()
reveal_type(x) # revealed: bool
except ValueError:
reveal_type(x) # revealed: Literal[1] | str
x = could_raise_returns_memoryview()
reveal_type(x) # revealed: memoryview
x = could_raise_returns_float()
reveal_type(x) # revealed: float
else:
reveal_type(x) # revealed: str
x = could_raise_returns_range()
reveal_type(x) # revealed: range
x = could_raise_returns_slice()
reveal_type(x) # revealed: slice
finally:
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
reveal_type(x) # revealed: bool | float | slice
x = 2
reveal_type(x) # revealed: Literal[2]
reveal_type(x) # revealed: Literal[2]
except:
reveal_type(x) # revealed: Literal[1, 2] | str | bytes | bool | memoryview | float | range | slice
x = could_raise_returns_complex()
reveal_type(x) # revealed: complex
x = could_raise_returns_bytearray()
reveal_type(x) # revealed: bytearray
else:
reveal_type(x) # revealed: Literal[2]
x = could_raise_returns_Foo()
reveal_type(x) # revealed: Foo
x = could_raise_returns_Bar()
reveal_type(x) # revealed: Bar
finally:
# TODO: should be `Literal[1, 2] | str | bytes | bool | memoryview | float | range | slice | complex | bytearray | Foo | Bar`
reveal_type(x) # revealed: bytearray | Bar
# Either one `except` branch or the `else`
# must have been taken and completed to get here:
reveal_type(x) # revealed: bytearray | Bar
```
## Nested scopes inside `try` blocks
Shadowing a variable in an inner scope has no effect on type inference of the
variable by that name in the outer scope:
```py
def could_raise_returns_str() -> str:
return 'foo'
def could_raise_returns_bytes() -> bytes:
return b'foo'
def could_raise_returns_range() -> range:
return range(42)
def could_raise_returns_bytearray() -> bytearray:
return bytearray()
def could_raise_returns_float() -> float:
return 3.14
x = 1
try:
def foo(param=could_raise_returns_str()):
x = could_raise_returns_str()
try:
reveal_type(x) # revealed: str
x = could_raise_returns_bytes()
reveal_type(x) # revealed: bytes
except:
reveal_type(x) # revealed: str | bytes
x = could_raise_returns_bytearray()
reveal_type(x) # revealed: bytearray
x = could_raise_returns_float()
reveal_type(x) # revealed: float
finally:
# TODO: should be `str | bytes | bytearray | float`
reveal_type(x) # revealed: bytes | float
reveal_type(x) # revealed: bytes | float
x = foo
reveal_type(x) # revealed: Literal[foo]
except:
reveal_type(x) # revealed: Literal[1] | Literal[foo]
class Bar:
x = could_raise_returns_range()
reveal_type(x) # revealed: range
x = Bar
reveal_type(x) # revealed: Literal[Bar]
finally:
# TODO: should be `Literal[1] | Literal[foo] | Literal[Bar]`
reveal_type(x) # revealed: Literal[foo] | Literal[Bar]
reveal_type(x) # revealed: Literal[foo] | Literal[Bar]
```
[1]: https://astral-sh.notion.site/Exception-handler-control-flow-11348797e1ca80bb8ce1e9aedbbe439d

View File

@@ -0,0 +1,30 @@
# Except star
## Except\* with BaseException
```py
try:
x
except* BaseException as e:
reveal_type(e) # revealed: BaseExceptionGroup
```
## Except\* with specific exception
```py
try:
x
except* OSError as e:
# TODO(Alex): more precise would be `ExceptionGroup[OSError]`
reveal_type(e) # revealed: BaseExceptionGroup
```
## Except\* with multiple exceptions
```py
try:
x
except* (TypeError, AttributeError) as e:
#TODO(Alex): more precise would be `ExceptionGroup[TypeError | AttributeError]`.
reveal_type(e) # revealed: BaseExceptionGroup
```

View File

@@ -0,0 +1,151 @@
# Expressions
## OR
```py
def foo() -> str:
pass
a = True or False
b = 'x' or 'y' or 'z'
c = '' or 'y' or 'z'
d = False or 'z'
e = False or True
f = False or False
g = foo() or False
h = foo() or True
reveal_type(a) # revealed: Literal[True]
reveal_type(b) # revealed: Literal["x"]
reveal_type(c) # revealed: Literal["y"]
reveal_type(d) # revealed: Literal["z"]
reveal_type(e) # revealed: Literal[True]
reveal_type(f) # revealed: Literal[False]
reveal_type(g) # revealed: str | Literal[False]
reveal_type(h) # revealed: str | Literal[True]
```
## AND
```py
def foo() -> str:
pass
a = True and False
b = False and True
c = foo() and False
d = foo() and True
e = 'x' and 'y' and 'z'
f = 'x' and 'y' and ''
g = '' and 'y'
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[False]
reveal_type(c) # revealed: str | Literal[False]
reveal_type(d) # revealed: str | Literal[True]
reveal_type(e) # revealed: Literal["z"]
reveal_type(f) # revealed: Literal[""]
reveal_type(g) # revealed: Literal[""]
```
## Simple function calls to bool
```py
def returns_bool() -> bool:
return True
if returns_bool():
x = True
else:
x = False
reveal_type(x) # revealed: bool
```
## Complex
```py
def foo() -> str:
pass
a = "x" and "y" or "z"
b = "x" or "y" and "z"
c = "" and "y" or "z"
d = "" or "y" and "z"
e = "x" and "y" or ""
f = "x" or "y" and ""
reveal_type(a) # revealed: Literal["y"]
reveal_type(b) # revealed: Literal["x"]
reveal_type(c) # revealed: Literal["z"]
reveal_type(d) # revealed: Literal["z"]
reveal_type(e) # revealed: Literal["y"]
reveal_type(f) # revealed: Literal["x"]
```
## `bool()` function
## Evaluates to builtin
```py path=a.py
redefined_builtin_bool = bool
def my_bool(x)-> bool: pass
```
```py
from a import redefined_builtin_bool, my_bool
a = redefined_builtin_bool(0)
b = my_bool(0)
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: bool
```
## Truthy values
```py
a = bool(1)
b = bool((0,))
c = bool("NON EMPTY")
d = bool(True)
def foo(): pass
e = bool(foo)
reveal_type(a) # revealed: Literal[True]
reveal_type(b) # revealed: Literal[True]
reveal_type(c) # revealed: Literal[True]
reveal_type(d) # revealed: Literal[True]
reveal_type(e) # revealed: Literal[True]
```
## Falsy values
```py
a = bool(0)
b = bool(())
c = bool(None)
d = bool("")
e = bool(False)
f = bool()
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[False]
reveal_type(c) # revealed: Literal[False]
reveal_type(d) # revealed: Literal[False]
reveal_type(e) # revealed: Literal[False]
reveal_type(f) # revealed: Literal[False]
```
## Ambiguous values
```py
a = bool([])
b = bool({})
c = bool(set())
reveal_type(a) # revealed: bool
reveal_type(b) # revealed: bool
reveal_type(c) # revealed: bool
```

View File

@@ -0,0 +1,23 @@
# Structures
## Class import following
```py
from b import C as D; E = D
reveal_type(E) # revealed: Literal[C]
```
```py path=b.py
class C: pass
```
## Module member resolution
```py
import b; D = b.C
reveal_type(D) # revealed: Literal[C]
```
```py path=b.py
class C: pass
```

View File

@@ -0,0 +1,6 @@
# Importing builtin module
```py
import builtins; x = builtins.copyright
reveal_type(x) # revealed: Literal[copyright]
```

View File

@@ -0,0 +1,75 @@
# Conditional imports
## Maybe unbound
```py path=maybe_unbound.py
if flag:
y = 3
x = y
reveal_type(x) # revealed: Unbound | Literal[3]
reveal_type(y) # revealed: Unbound | Literal[3]
```
```py
from maybe_unbound import x, y
reveal_type(x) # revealed: Literal[3]
reveal_type(y) # revealed: Literal[3]
```
## Maybe unbound annotated
```py path=maybe_unbound_annotated.py
if flag:
y: int = 3
x = y
reveal_type(x) # revealed: Unbound | Literal[3]
reveal_type(y) # revealed: Unbound | Literal[3]
```
Importing an annotated name prefers the declared type over the inferred type:
```py
from maybe_unbound_annotated import x, y
reveal_type(x) # revealed: Literal[3]
reveal_type(y) # revealed: int
```
## Reimport
```py path=c.py
def f(): ...
```
```py path=b.py
if flag:
from c import f
else:
def f(): ...
```
```py
from b import f
# TODO: We should disambiguate in such cases, showing `Literal[b.f, c.f]`.
reveal_type(f) # revealed: Literal[f, f]
```
## Reimport with stub declaration
When we have a declared type in one path and only an inferred-from-definition type in the other, we
should still be able to unify those:
```py path=c.pyi
x: int
```
```py path=b.py
if flag:
from c import x
else:
x = 1
```
```py
from b import x
reveal_type(x) # revealed: int
```

View File

@@ -0,0 +1,52 @@
# Unresolved Imports
## Unresolved import statement
```py
import bar # error: "Cannot resolve import `bar`"
reveal_type(bar) # revealed: Unknown
```
## Unresolved import from statement
```py
from bar import baz # error: "Cannot resolve import `bar`"
reveal_type(baz) # revealed: Unknown
```
## Unresolved import from resolved module
```py path=a.py
```
```py
from a import thing # error: "Module `a` has no member `thing`"
reveal_type(thing) # revealed: Unknown
```
## Resolved import of symbol from unresolved import
```py path=a.py
import foo as foo # error: "Cannot resolve import `foo`"
reveal_type(foo) # revealed: Unknown
```
Importing the unresolved import into a second file should not trigger an additional "unresolved
import" violation:
```py
from a import foo
reveal_type(foo) # revealed: Unknown
```
## No implicit shadowing
```py path=b.py
x: int
```
```py
from b import x
x = 'foo' # error: [invalid-assignment] "Object of type `Literal["foo"]"
```

View File

@@ -0,0 +1,133 @@
# Relative
## Non-existent
```py path=package/__init__.py
```
```py path=package/bar.py
from .foo import X # error: [unresolved-import]
reveal_type(X) # revealed: Unknown
```
## Simple
```py path=package/__init__.py
```
```py path=package/foo.py
X = 42
```
```py path=package/bar.py
from .foo import X
reveal_type(X) # revealed: Literal[42]
```
## Dotted
```py path=package/__init__.py
```
```py path=package/foo/bar/baz.py
X = 42
```
```py path=package/bar.py
from .foo.bar.baz import X
reveal_type(X) # revealed: Literal[42]
```
## Bare to package
```py path=package/__init__.py
X = 42
```
```py path=package/bar.py
from . import X
reveal_type(X) # revealed: Literal[42]
```
## Non-existent + bare to package
```py path=package/bar.py
from . import X # error: [unresolved-import]
reveal_type(X) # revealed: Unknown
```
## Dunder init
```py path=package/__init__.py
from .foo import X
reveal_type(X) # revealed: Literal[42]
```
```py path=package/foo.py
X = 42
```
## Non-existent + dunder init
```py path=package/__init__.py
from .foo import X # error: [unresolved-import]
reveal_type(X) # revealed: Unknown
```
## Long relative import
```py path=package/__init__.py
```
```py path=package/foo.py
X = 42
```
```py path=package/subpackage/subsubpackage/bar.py
from ...foo import X
reveal_type(X) # revealed: Literal[42]
```
## Unbound symbol
```py path=package/__init__.py
```
```py path=package/foo.py
x
```
```py path=package/bar.py
from .foo import x # error: [unresolved-import]
reveal_type(x) # revealed: Unknown
```
## Bare to module
```py path=package/__init__.py
```
```py path=package/foo.py
X = 42
```
```py path=package/bar.py
# TODO: support submodule imports
from . import foo # error: [unresolved-import]
y = foo.X
# TODO: should be `Literal[42]`
reveal_type(y) # revealed: Unknown
```
## Non-existent + bare to module
```py path=package/__init__.py
```
```py path=package/bar.py
# TODO: support submodule imports
from . import foo # error: [unresolved-import]
reveal_type(foo) # revealed: Unknown
```

View File

@@ -0,0 +1,25 @@
# Stubs
## Import from stub declaration
```py
from b import x
y = x
reveal_type(y) # revealed: int
```
```py path=b.pyi
x: int
```
## Import from non-stub with declaration and definition
```py
from b import x
y = x
reveal_type(y) # revealed: int
```
```py path=b.py
x: int = 1
```

View File

@@ -0,0 +1,8 @@
# Boolean literals
```py
x = True
y = False
reveal_type(x) # revealed: Literal[True]
reveal_type(y) # revealed: Literal[False]
```

View File

@@ -0,0 +1,8 @@
# Dictionaries
## Empty dictionary
```py
x = {}
reveal_type(x) # revealed: dict
```

View File

@@ -0,0 +1,8 @@
# Lists
## Empty list
```py
x = []
reveal_type(x) # revealed: list
```

View File

@@ -0,0 +1,8 @@
# Sets
## Basic set
```py
x = {1, 2}
reveal_type(x) # revealed: set
```

View File

@@ -0,0 +1,20 @@
# Tuples
## Empty tuple
```py
x = ()
reveal_type(x) # revealed: tuple[()]
```
## Heterogeneous tuple
```py
x = (1, 'a')
y = (1, (2, 3))
z = (x, 2)
reveal_type(x) # revealed: tuple[Literal[1], Literal["a"]]
reveal_type(y) # revealed: tuple[Literal[1], tuple[Literal[2], Literal[3]]]
reveal_type(z) # revealed: tuple[tuple[Literal[1], Literal["a"]], Literal[2]]
```

View File

@@ -0,0 +1,7 @@
# Complex literals
## Complex numbers
```py
reveal_type(2j) # revealed: complex
```

View File

@@ -0,0 +1,44 @@
# f-strings
## Expression
```py
x = 0
y = str()
z = False
a = f'hello'
b = f'h {x}'
c = 'one ' f'single ' f'literal'
d = 'first ' f'second({b})' f' third'
e = f'-{y}-'
f = f'-{y}-' f'--' '--'
g = f'{z} == {False} is {True}'
reveal_type(a) # revealed: Literal["hello"]
reveal_type(b) # revealed: Literal["h 0"]
reveal_type(c) # revealed: Literal["one single literal"]
reveal_type(d) # revealed: Literal["first second(h 0) third"]
reveal_type(e) # revealed: str
reveal_type(f) # revealed: str
reveal_type(g) # revealed: Literal["False == False is True"]
```
## Conversion Flags
```py
string = 'hello'
a = f'{string!r}'
# TODO: should be `Literal["'hello'"]`
reveal_type(a) # revealed: str
```
## Format Specifiers
```py
a = f'{1:02}'
# TODO: should be `Literal["01"]`
reveal_type(a) # revealed: str
```

View File

@@ -0,0 +1,7 @@
# Float literals
## Basic
```py
reveal_type(1.0) # revealed: float
```

View File

@@ -0,0 +1,56 @@
# Integer literals
## Literals
We can infer an integer literal type:
```py
reveal_type(1) # revealed: Literal[1]
```
## Variable
```py
x = 1
reveal_type(x) # revealed: Literal[1]
```
## Overflow
We only track integer literals within the range of an i64:
```py
reveal_type(9223372036854775808) # revealed: int
```
## Big int
We don't support big integer literals; we just infer `int` type instead:
```py
x = 10_000_000_000_000_000_000
reveal_type(x) # revealed: int
```
## Negated
```py
x = -1
y = -1234567890987654321
z = --987
reveal_type(x) # revealed: Literal[-1]
reveal_type(y) # revealed: Literal[-1234567890987654321]
reveal_type(z) # revealed: Literal[987]
```
## Floats
```py
reveal_type(1.0) # revealed: float
```
## Complex
```py
reveal_type(2j) # revealed: complex
```

View File

@@ -0,0 +1,26 @@
# String literals
## Simple
```py
w = "Hello"
x = 'world'
y = "Guten " + 'tag'
z = 'bon ' + "jour"
reveal_type(w) # revealed: Literal["Hello"]
reveal_type(x) # revealed: Literal["world"]
reveal_type(y) # revealed: Literal["Guten tag"]
reveal_type(z) # revealed: Literal["bon jour"]
```
## Nested Quotes
```py
x = 'I say "hello" to you'
y = "You say \"hey\" back"
z = 'No "closure here'
reveal_type(x) # revealed: Literal["I say \"hello\" to you"]
reveal_type(y) # revealed: Literal["You say \"hey\" back"]
reveal_type(z) # revealed: Literal["No \"closure here"]
```

View File

@@ -0,0 +1,41 @@
# Async
Async `for` loops do not work according to the synchronous iteration protocol.
## Invalid async for loop
```py
async def foo():
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
async for x in Iterator():
pass
# TODO
reveal_type(x) # revealed: Unbound | @Todo
```
## Basic async for loop
```py
async def foo():
class IntAsyncIterator:
async def __anext__(self) -> int:
return 42
class IntAsyncIterable:
def __aiter__(self) -> IntAsyncIterator:
return IntAsyncIterator()
#TODO(Alex): async iterables/iterators!
async for x in IntAsyncIterable():
pass
reveal_type(x) # revealed: Unbound | @Todo
```

View File

@@ -0,0 +1,134 @@
# For loops
## Basic `for` loop
```py
class IntIterator:
def __next__(self) -> int:
return 42
class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
for x in IntIterable():
pass
reveal_type(x) # revealed: Unbound | int
```
## With previous definition
```py
class IntIterator:
def __next__(self) -> int:
return 42
class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
x = 'foo'
for x in IntIterable():
pass
reveal_type(x) # revealed: Literal["foo"] | int
```
## With `else` (no break)
```py
class IntIterator:
def __next__(self) -> int:
return 42
class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
for x in IntIterable():
pass
else:
x = 'foo'
reveal_type(x) # revealed: Literal["foo"]
```
## May `break`
```py
class IntIterator:
def __next__(self) -> int:
return 42
class IntIterable:
def __iter__(self) -> IntIterator:
return IntIterator()
for x in IntIterable():
if x > 5:
break
else:
x = 'foo'
reveal_type(x) # revealed: int | Literal["foo"]
```
## With old-style iteration protocol
```py
class OldStyleIterable:
def __getitem__(self, key: int) -> int:
return 42
for x in OldStyleIterable():
pass
reveal_type(x) # revealed: Unbound | int
```
## With heterogeneous tuple
```py
for x in (1, 'a', b'foo'):
pass
reveal_type(x) # revealed: Unbound | Literal[1] | Literal["a"] | Literal[b"foo"]
```
## With non-callable iterator
```py
class NotIterable:
if flag:
__iter__ = 1
else:
__iter__ = None
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
pass
reveal_type(x) # revealed: Unbound | Unknown
```
## Invalid iterable
```py
nonsense = 123
for x in nonsense: # error: "Object of type `Literal[123]` is not iterable"
pass
```
## New over old style iteration protocol
```py
class NotIterable:
def __getitem__(self, key: int) -> int:
return 42
__iter__ = None
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
pass
```

View File

@@ -0,0 +1,18 @@
# Iterators
## Yield must be iterable
```py
class NotIterable: pass
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator: ...
def generator_function():
yield from Iterable()
yield from NotIterable() # error: "Object of type `NotIterable` is not iterable"
```

View File

@@ -0,0 +1,43 @@
# While loops
## Basic While Loop
```py
x = 1
while flag:
x = 2
reveal_type(x) # revealed: Literal[1, 2]
```
## While with else (no break)
```py
x = 1
while flag:
x = 2
else:
y = x
x = 3
reveal_type(x) # revealed: Literal[3]
reveal_type(y) # revealed: Literal[1, 2]
```
## While with Else (may break)
```py
x = 1
y = 0
while flag:
x = 2
if flag2:
y = 4
break
else:
y = x
x = 3
reveal_type(x) # revealed: Literal[2, 3]
reveal_type(y) # revealed: Literal[1, 2, 4]
```

View File

@@ -0,0 +1,29 @@
# Narrowing for `is` conditionals
## `is None`
```py
x = None if flag else 1
if x is None:
# TODO the following should be simplified to 'None'
reveal_type(x) # revealed: None | Literal[1] & None
reveal_type(x) # revealed: None | Literal[1]
```
## `is` for other types
```py
class A:
...
x = A()
y = x if flag else None
if y is x:
# TODO the following should be simplified to 'A'
reveal_type(y) # revealed: A | None & A
reveal_type(y) # revealed: A | None
```

View File

@@ -0,0 +1,39 @@
# Narrowing for `is not` conditionals
## `is not None`
The type guard removes `None` from the union type:
```py
x = None if flag else 1
if x is not None:
reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: None | Literal[1]
```
## `is not` for other singleton types
```py
x = True if flag else False
reveal_type(x) # revealed: bool
if x is not False:
# TODO the following should be `Literal[True]`
reveal_type(x) # revealed: bool & ~Literal[False]
```
## `is not` for non-singleton types
Non-singleton types should *not* narrow the type: two instances of a
non-singleton class may occupy different addresses in memory even if
they compare equal.
```py
x = 345
y = 345
if x is not y:
reveal_type(x) # revealed: Literal[345]
```

View File

@@ -0,0 +1,17 @@
# Narrowing for `match` statements
## Single `match` pattern
```py
x = None if flag else 1
reveal_type(x) # revealed: None | Literal[1]
y = 0
match x:
case None:
y = x
# TODO intersection simplification: should be just Literal[0] | None
reveal_type(y) # revealed: Literal[0] | None | Literal[1] & None
```

View File

@@ -0,0 +1,9 @@
# `is not None` narrowing
```py
x = None if flag else 1
if x is not None:
reveal_type(x) # revealed: Literal[1]
reveal_type(x) # revealed: None | Literal[1]
```

View File

@@ -1,35 +0,0 @@
# Numbers
## Integers
### Literals
We can infer an integer literal type:
```py
reveal_type(1) # revealed: Literal[1]
```
### Overflow
We only track integer literals within the range of an i64:
```py
reveal_type(9223372036854775808) # revealed: int
```
## Floats
There aren't literal float types, but we infer the general float type:
```py
reveal_type(1.0) # revealed: float
```
## Complex
Same for complex:
```py
reveal_type(2j) # revealed: complex
```

View File

@@ -0,0 +1,32 @@
# Builtin scope
## Conditionally global or builtin
If a builtin name is conditionally defined as a global, a name lookup should union the builtin type
with the conditionally-defined type:
```py
def returns_bool() -> bool:
return True
if returns_bool():
copyright = 1
def f():
reveal_type(copyright) # revealed: Literal[copyright] | Literal[1]
```
## Conditionally global or builtin, with annotation
Same is true if the name is annotated:
```py
def returns_bool() -> bool:
return True
if returns_bool():
copyright: int = 1
def f():
reveal_type(copyright) # revealed: Literal[copyright] | int
```

View File

@@ -0,0 +1,17 @@
# Classes shadowing
## Implicit error
```py
class C: pass
C = 1 # error: "Implicit shadowing of class `C`; annotate to make it explicit if this is intentional"
```
## Explicit
No diagnostic is raised in the case of explicit shadowing:
```py
class C: pass
C: int = 1
```

View File

@@ -0,0 +1,24 @@
# Function shadowing
## Parameter
Parameter `x` of type `str` is shadowed and reassigned with a new `int` value inside the function. No diagnostics should be generated.
```py path=a.py
def f(x: str):
x: int = int(x)
```
## Implicit error
```py path=a.py
def f(): pass
f = 1 # error: "Implicit shadowing of function `f`; annotate to make it explicit if this is intentional"
```
## Explicit shadowing
```py path=a.py
def f(): pass
f: int = 1
```

View File

@@ -0,0 +1,11 @@
# Shadwing declaration
## Shadow after incompatible declarations is OK
```py
if flag:
x: str
else:
x: int
x: bytes = b'foo'
```

View File

@@ -0,0 +1,10 @@
# Class defenitions in stubs
## Cyclical class definition
In type stubs, classes can reference themselves in their base class definitions. For example, in `typeshed`, we have `class str(Sequence[str]): ...`.
```py path=a.pyi
class C(C): ...
reveal_type(C) # revealed: Literal[C]
```

View File

@@ -0,0 +1,15 @@
# Bytes subscript
## Simple
```py
w = b'red' b'knot'
x = b'hello'
y = b'world' + b'!'
z = b'\xff\x00'
reveal_type(w) # revealed: Literal[b"redknot"]
reveal_type(x) # revealed: Literal[b"hello"]
reveal_type(y) # revealed: Literal[b"world!"]
reveal_type(z) # revealed: Literal[b"\xff\x00"]
```

View File

@@ -0,0 +1,92 @@
# Class subscript
## Class getitem unbound
```py
class NotSubscriptable: pass
a = NotSubscriptable[0] # error: "Cannot subscript object of type `Literal[NotSubscriptable]` with no `__class_getitem__` method"
```
## Class getitem
```py
class Identity:
def __class_getitem__(cls, item: int) -> str:
return item
a = Identity[0]
reveal_type(a) # revealed: str
```
## Class getitem union
```py
flag = True
class Identity:
if flag:
def __class_getitem__(cls, item: int) -> str:
return item
else:
def __class_getitem__(cls, item: int) -> int:
return item
a = Identity[0]
reveal_type(a) # revealed: str | int
```
## Class getitem with class union
```py
flag = True
class Identity1:
def __class_getitem__(cls, item: int) -> str:
return item
class Identity2:
def __class_getitem__(cls, item: int) -> int:
return item
if flag:
a = Identity1
else:
a = Identity2
b = a[0]
reveal_type(a) # revealed: Literal[Identity1, Identity2]
reveal_type(b) # revealed: str | int
```
## Class getitem with unbound method union
```py
flag = True
if flag:
class Identity:
def __class_getitem__(self, x: int) -> str:
pass
else:
class Identity: pass
a = Identity[42] # error: [call-non-callable] "Method `__class_getitem__` of type `Literal[__class_getitem__] | Unbound` is not callable on object of type `Literal[Identity, Identity]`"
reveal_type(a) # revealed: str | Unknown
```
## TODO: Class getitem non-class union
```py
flag = True
if flag:
class Identity:
def __class_getitem__(self, x: int) -> str:
pass
else:
Identity = 1
a = Identity[42] # error: "Cannot subscript object of type `Literal[Identity] | Literal[1]` with no `__getitem__` method"
# TODO: should _probably_ emit `str | Unknown`
reveal_type(a) # revealed: Unknown
```

View File

@@ -0,0 +1,45 @@
# Instance subscript
## Getitem unbound
```py
class NotSubscriptable: pass
a = NotSubscriptable()[0] # error: "Cannot subscript object of type `NotSubscriptable` with no `__getitem__` method"
```
## Getitem not callable
```py
class NotSubscriptable:
__getitem__ = None
a = NotSubscriptable()[0] # error: "Method `__getitem__` of type `None` is not callable on object of type `NotSubscriptable`"
```
## Valid getitem
```py
class Identity:
def __getitem__(self, index: int) -> int:
return index
a = Identity()[0]
reveal_type(a) # revealed: int
```
## Getitem union
```py
flag = True
class Identity:
if flag:
def __getitem__(self, index: int) -> int:
return index
else:
def __getitem__(self, index: int) -> str:
return str(index)
a = Identity()[0]
reveal_type(a) # revealed: int | str
```

View File

@@ -0,0 +1,33 @@
# List subscripts
## Indexing into lists
A list can be indexed into with:
- numbers
- slices
```py
x = [1, 2, 3]
reveal_type(x) # revealed: list
# TODO reveal int
reveal_type(x[0]) # revealed: @Todo
# TODO reveal list
reveal_type(x[0:1]) # revealed: @Todo
# TODO error
reveal_type(x["a"]) # revealed: @Todo
```
## Assignments within list assignment
In assignment, we might also have a named assignment.
This should also get type checked.
```py
x = [1, 2, 3]
x[0 if (y := 2) else 1] = 5
# TODO error? (indeterminite index type)
x["a" if (y := 2) else 1] = 6
# TODO error (can't index via string)
x["a" if (y := 2) else "b"] = 6
```

View File

@@ -0,0 +1,32 @@
# Subscript on strings
## Simple
```py
s = 'abcde'
a = s[0]
b = s[1]
c = s[-1]
d = s[-2]
e = s[8] # error: [index-out-of-bounds] "Index 8 is out of bounds for string `Literal["abcde"]` with length 5"
f = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5"
reveal_type(a) # revealed: Literal["a"]
reveal_type(b) # revealed: Literal["b"]
reveal_type(c) # revealed: Literal["e"]
reveal_type(d) # revealed: Literal["d"]
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
```
## Function return
```py
def add(x: int, y: int) -> int:
return x + y
a = 'abcde'[add(0, 1)]
# TODO: Support overloads... Should be `str`
reveal_type(a) # revealed: @Todo
```

View File

@@ -0,0 +1,21 @@
# Tuple subscripts
## Basic
```py
t = (1, 'a', 'b')
a = t[0]
b = t[1]
c = t[-1]
d = t[-2]
e = t[4] # error: [index-out-of-bounds]
f = t[-4] # error: [index-out-of-bounds]
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal["a"]
reveal_type(c) # revealed: Literal["b"]
reveal_type(d) # revealed: Literal["a"]
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
```

View File

@@ -0,0 +1,37 @@
# Unary Operations
## Unary Addition
```py
a = +0
b = +1
c = +True
reveal_type(a) # revealed: Literal[0]
reveal_type(b) # revealed: Literal[1]
reveal_type(c) # revealed: Literal[1]
```
## Unary Subtraction
```py
a = -0
b = -1
c = -True
reveal_type(a) # revealed: Literal[0]
reveal_type(b) # revealed: Literal[-1]
reveal_type(c) # revealed: Literal[-1]
```
## Unary Bitwise Inversion
```py
a = ~0
b = ~1
c = ~True
reveal_type(a) # revealed: Literal[-1]
reveal_type(b) # revealed: Literal[-2]
reveal_type(c) # revealed: Literal[-2]
```

View File

@@ -0,0 +1,149 @@
# Unary not
## None
```py
a = not None
b = not not None
reveal_type(a) # revealed: Literal[True]
reveal_type(b) # revealed: Literal[False]
```
## Function
```py
from typing import reveal_type
def f():
return 1
a = not f
b = not reveal_type
reveal_type(a) # revealed: Literal[False]
# TODO Unknown should not be part of the type of typing.reveal_type
# reveal_type(b) revealed: Literal[False]
```
## Module
```py
import b; import warnings
x = not b
z = not warnings
reveal_type(x) # revealed: Literal[False]
reveal_type(z) # revealed: Literal[False]
```
```py path=b.py
y = 1
```
## Union
```py
if flag:
p = 1
q = 3.3
r = "hello"
s = "world"
t = 0
else:
p = "hello"
q = 4
r = ""
s = 0
t = ""
a = not p
b = not q
c = not r
d = not s
e = not t
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: bool
reveal_type(c) # revealed: bool
reveal_type(d) # revealed: bool
reveal_type(e) # revealed: Literal[True]
```
## Integer literal
```py
a = not 1
b = not 1234567890987654321
e = not 0
x = not -1
y = not -1234567890987654321
z = not --987
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[False]
reveal_type(e) # revealed: Literal[True]
reveal_type(x) # revealed: Literal[False]
reveal_type(y) # revealed: Literal[False]
reveal_type(z) # revealed: Literal[False]
```
## Boolean literal
```py
w = True
x = False
y = not w
z = not x
reveal_type(w) # revealed: Literal[True]
reveal_type(x) # revealed: Literal[False]
reveal_type(y) # revealed: Literal[False]
reveal_type(z) # revealed: Literal[True]
```
## String literal
```py
a = not "hello"
b = not ""
c = not "0"
d = not "hello" + "world"
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[True]
reveal_type(c) # revealed: Literal[False]
reveal_type(d) # revealed: Literal[False]
```
## Bytes literal
```py
a = not b"hello"
b = not b""
c = not b"0"
d = not b"hello" + b"world"
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[True]
reveal_type(c) # revealed: Literal[False]
reveal_type(d) # revealed: Literal[False]
```
## Tuple
```py
a = not (1,)
b = not (1, 2)
c = not (1, 2, 3)
d = not ()
e = not ("hello",)
f = not (1, "hello")
reveal_type(a) # revealed: Literal[False]
reveal_type(b) # revealed: Literal[False]
reveal_type(c) # revealed: Literal[False]
reveal_type(d) # revealed: Literal[True]
reveal_type(e) # revealed: Literal[False]
reveal_type(f) # revealed: Literal[False]
```

View File

@@ -0,0 +1,273 @@
# Unpacking
## Tuple
### Simple tuple
```py
(a, b, c) = (1, 2, 3)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: Literal[3]
```
### Simple list
```py
[a, b, c] = (1, 2, 3)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: Literal[3]
```
### Simple mixed
```py
[a, (b, c), d] = (1, (2, 3), 4)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: Literal[3]
reveal_type(d) # revealed: Literal[4]
```
### Multiple assignment
```py
a, b = c = 1, 2
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: tuple[Literal[1], Literal[2]]
```
### Nested tuple with unpacking
```py
(a, (b, c), d) = (1, (2, 3), 4)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: Literal[3]
reveal_type(d) # revealed: Literal[4]
```
### Nested tuple without unpacking
```py
(a, b, c) = (1, (2, 3), 4)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: tuple[Literal[2], Literal[3]]
reveal_type(c) # revealed: Literal[4]
```
### Uneven unpacking (1)
```py
# TODO: Add diagnostic (there aren't enough values to unpack)
(a, b, c) = (1, 2)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
reveal_type(c) # revealed: Unknown
```
### Uneven unpacking (2)
```py
# TODO: Add diagnostic (too many values to unpack)
(a, b) = (1, 2, 3)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
```
### Starred expression (1)
```py
# TODO: Add diagnostic (need more values to unpack)
# TODO: Remove 'not-iterable' diagnostic
[a, *b, c, d] = (1, 2) # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[2]
reveal_type(d) # revealed: Unknown
```
### Starred expression (2)
```py
[a, *b, c] = (1, 2) # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[2]
```
### Starred expression (3)
```py
# TODO: Remove 'not-iterable' diagnostic
[a, *b, c] = (1, 2, 3) # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[3]
```
### Starred expression (4)
```py
# TODO: Remove 'not-iterable' diagnostic
[a, *b, c, d] = (1, 2, 3, 4, 5, 6) # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[5]
reveal_type(d) # revealed: Literal[6]
```
### Starred expression (5)
```py
# TODO: Remove 'not-iterable' diagnostic
[a, b, *c] = (1, 2, 3, 4) # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(c) # revealed: @Todo
```
### Non-iterable unpacking
TODO: Remove duplicate diagnostics. This is happening because for a sequence-like
assignment target, multiple definitions are created and the inference engine runs
on each of them which results in duplicate diagnostics.
```py
# error: "Object of type `Literal[1]` is not iterable"
# error: "Object of type `Literal[1]` is not iterable"
a, b = 1
reveal_type(a) # revealed: Unknown
reveal_type(b) # revealed: Unknown
```
### Custom iterator unpacking
```py
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
(a, b) = Iterable()
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Custom iterator unpacking nested
```py
class Iterator:
def __next__(self) -> int:
return 42
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
(a, (b, c), d) = (1, Iterable(), 2)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: int
reveal_type(c) # revealed: int
reveal_type(d) # revealed: Literal[2]
```
## String
### Simple unpacking
```py
a, b = 'ab'
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
```
### Uneven unpacking (1)
```py
# TODO: Add diagnostic (there aren't enough values to unpack)
a, b, c = 'ab'
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
reveal_type(c) # revealed: Unknown
```
### Uneven unpacking (2)
```py
# TODO: Add diagnostic (too many values to unpack)
a, b = 'abc'
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
```
### Starred expression (1)
```py
# TODO: Add diagnostic (need more values to unpack)
# TODO: Remove 'not-iterable' diagnostic
(a, *b, c, d) = "ab" # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
reveal_type(d) # revealed: Unknown
```
### Starred expression (2)
```py
(a, *b, c) = "ab" # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
```
### Starred expression (3)
```py
# TODO: Remove 'not-iterable' diagnostic
(a, *b, c) = "abc" # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
```
### Starred expression (4)
```py
# TODO: Remove 'not-iterable' diagnostic
(a, *b, c, d) = "abcdef" # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
reveal_type(d) # revealed: LiteralString
```
### Starred expression (5)
```py
# TODO: Remove 'not-iterable' diagnostic
(a, b, *c) = "abcd" # error: "Object of type `None` is not iterable"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(c) # revealed: @Todo
```

View File

@@ -17,7 +17,7 @@ use super::module::{Module, ModuleKind};
use super::path::{ModulePath, SearchPath, SearchPathValidationError};
/// Resolves a module name to a module.
pub fn resolve_module(db: &dyn Db, module_name: ModuleName) -> Option<Module> {
pub fn resolve_module(db: &dyn Db, module_name: &ModuleName) -> Option<Module> {
let interned_name = ModuleNameIngredient::new(db, module_name);
resolve_module_query(db, interned_name)
@@ -103,7 +103,7 @@ pub(crate) fn file_to_module(db: &dyn Db, file: File) -> Option<Module> {
// If it doesn't, then that means that multiple modules have the same name in different
// root paths, but that the module corresponding to `path` is in a lower priority search path,
// in which case we ignore it.
let module = resolve_module(db, module_name)?;
let module = resolve_module(db, &module_name)?;
if file == module.file() {
Some(module)
@@ -728,11 +728,11 @@ mod tests {
.build();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!(
Some(&foo_module),
resolve_module(&db, foo_module_name.clone()).as_ref()
resolve_module(&db, &foo_module_name).as_ref()
);
assert_eq!("foo", foo_module.name());
@@ -755,7 +755,7 @@ mod tests {
.build();
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
let builtins = resolve_module(&db, builtins_module_name).expect("builtins to resolve");
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
}
@@ -776,7 +776,7 @@ mod tests {
.build();
let builtins_module_name = ModuleName::new_static("builtins").unwrap();
let builtins = resolve_module(&db, builtins_module_name).expect("builtins to resolve");
let builtins = resolve_module(&db, &builtins_module_name).expect("builtins to resolve");
assert_eq!(builtins.file().path(&db), &stdlib.join("builtins.pyi"));
}
@@ -794,11 +794,11 @@ mod tests {
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(
Some(&functools_module),
resolve_module(&db, functools_module_name).as_ref()
resolve_module(&db, &functools_module_name).as_ref()
);
assert_eq!(&stdlib, functools_module.search_path());
@@ -848,7 +848,7 @@ mod tests {
let existing_modules = create_module_names(&["asyncio", "functools", "xml.etree"]);
for module_name in existing_modules {
let resolved_module = resolve_module(&db, module_name.clone()).unwrap_or_else(|| {
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
panic!("Expected module {module_name} to exist in the mock stdlib")
});
let search_path = resolved_module.search_path();
@@ -901,7 +901,7 @@ mod tests {
for module_name in nonexisting_modules {
assert!(
resolve_module(&db, module_name.clone()).is_none(),
resolve_module(&db, &module_name).is_none(),
"Unexpectedly resolved a module for {module_name}"
);
}
@@ -944,7 +944,7 @@ mod tests {
]);
for module_name in existing_modules {
let resolved_module = resolve_module(&db, module_name.clone()).unwrap_or_else(|| {
let resolved_module = resolve_module(&db, &module_name).unwrap_or_else(|| {
panic!("Expected module {module_name} to exist in the mock stdlib")
});
let search_path = resolved_module.search_path();
@@ -980,7 +980,7 @@ mod tests {
let nonexisting_modules = create_module_names(&["importlib", "xml", "xml.etree"]);
for module_name in nonexisting_modules {
assert!(
resolve_module(&db, module_name.clone()).is_none(),
resolve_module(&db, &module_name).is_none(),
"Unexpectedly resolved a module for {module_name}"
);
}
@@ -1002,11 +1002,11 @@ mod tests {
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(
Some(&functools_module),
resolve_module(&db, functools_module_name).as_ref()
resolve_module(&db, &functools_module_name).as_ref()
);
assert_eq!(&src, functools_module.search_path());
assert_eq!(ModuleKind::Module, functools_module.kind());
@@ -1026,7 +1026,7 @@ mod tests {
.build();
let pydoc_data_topics_name = ModuleName::new_static("pydoc_data.topics").unwrap();
let pydoc_data_topics = resolve_module(&db, pydoc_data_topics_name).unwrap();
let pydoc_data_topics = resolve_module(&db, &pydoc_data_topics_name).unwrap();
assert_eq!("pydoc_data.topics", pydoc_data_topics.name());
assert_eq!(pydoc_data_topics.search_path(), &stdlib);
@@ -1043,7 +1043,7 @@ mod tests {
.build();
let foo_path = src.join("foo/__init__.py");
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
assert_eq!("foo", foo_module.name());
assert_eq!(&src, foo_module.search_path());
@@ -1070,7 +1070,7 @@ mod tests {
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_init_path = src.join("foo/__init__.py");
assert_eq!(&src, foo_module.search_path());
@@ -1098,11 +1098,11 @@ mod tests {
let foo_bar_module_name = ModuleName::new_static("foo.bar").unwrap();
// `foo.py` takes priority over the `foo` namespace package
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!(foo_module.file().path(&db), &src.join("foo.py"));
// `foo.bar` isn't recognised as a module
let foo_bar_module = resolve_module(&db, foo_bar_module_name.clone());
let foo_bar_module = resolve_module(&db, &foo_bar_module_name);
assert_eq!(foo_bar_module, None);
}
@@ -1112,7 +1112,7 @@ mod tests {
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
let foo = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_stub = src.join("foo.pyi");
assert_eq!(&src, foo.search_path());
@@ -1136,7 +1136,7 @@ mod tests {
let TestCase { db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
let baz_module =
resolve_module(&db, ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
resolve_module(&db, &ModuleName::new_static("foo.bar.baz").unwrap()).unwrap();
let baz_path = src.join("foo/bar/baz.py");
assert_eq!(&src, baz_module.search_path());
@@ -1175,14 +1175,14 @@ mod tests {
let one_module_name = ModuleName::new_static("parent.child.one").unwrap();
let one_module_path = FilePath::System(src.join("parent/child/one.py"));
assert_eq!(
resolve_module(&db, one_module_name),
resolve_module(&db, &one_module_name),
path_to_module(&db, &one_module_path)
);
let two_module_name = ModuleName::new_static("parent.child.two").unwrap();
let two_module_path = FilePath::System(site_packages.join("parent/child/two.py"));
assert_eq!(
resolve_module(&db, two_module_name),
resolve_module(&db, &two_module_name),
path_to_module(&db, &two_module_path)
);
}
@@ -1215,12 +1215,12 @@ mod tests {
let one_module_path = FilePath::System(src.join("parent/child/one.py"));
let one_module_name =
resolve_module(&db, ModuleName::new_static("parent.child.one").unwrap());
resolve_module(&db, &ModuleName::new_static("parent.child.one").unwrap());
assert_eq!(one_module_name, path_to_module(&db, &one_module_path));
assert_eq!(
None,
resolve_module(&db, ModuleName::new_static("parent.child.two").unwrap())
resolve_module(&db, &ModuleName::new_static("parent.child.two").unwrap())
);
}
@@ -1236,7 +1236,7 @@ mod tests {
.with_site_packages_files(&[("foo.py", "")])
.build();
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let foo_src_path = src.join("foo.py");
assert_eq!(&src, foo_module.search_path());
@@ -1301,8 +1301,8 @@ mod tests {
)
.context("Invalid program settings")?;
let foo_module = resolve_module(&db, ModuleName::new_static("foo").unwrap()).unwrap();
let bar_module = resolve_module(&db, ModuleName::new_static("bar").unwrap()).unwrap();
let foo_module = resolve_module(&db, &ModuleName::new_static("foo").unwrap()).unwrap();
let bar_module = resolve_module(&db, &ModuleName::new_static("bar").unwrap()).unwrap();
assert_ne!(foo_module, bar_module);
@@ -1337,7 +1337,7 @@ mod tests {
.build();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let bar_path = src.join("bar.py");
let bar = system_path_to_file(&db, &bar_path).expect("bar.py to exist");
@@ -1351,7 +1351,7 @@ mod tests {
// Re-query the foo module. The foo module should still be cached because `bar.py` isn't relevant
// for resolving `foo`.
let foo_module2 = resolve_module(&db, foo_module_name);
let foo_module2 = resolve_module(&db, &foo_module_name);
assert!(!db
.take_salsa_events()
@@ -1368,14 +1368,14 @@ mod tests {
let foo_path = src.join("foo.py");
let foo_module_name = ModuleName::new_static("foo").unwrap();
assert_eq!(resolve_module(&db, foo_module_name.clone()), None);
assert_eq!(resolve_module(&db, &foo_module_name), None);
// Now write the foo file
db.write_file(&foo_path, "x = 1")?;
let foo_file = system_path_to_file(&db, &foo_path).expect("foo.py to exist");
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
assert_eq!(foo_file, foo_module.file());
Ok(())
@@ -1389,7 +1389,7 @@ mod tests {
let TestCase { mut db, src, .. } = TestCaseBuilder::new().with_src_files(SRC).build();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).expect("foo module to exist");
let foo_module = resolve_module(&db, &foo_module_name).expect("foo module to exist");
let foo_init_path = src.join("foo/__init__.py");
assert_eq!(&foo_init_path, foo_module.file().path(&db));
@@ -1401,7 +1401,7 @@ mod tests {
File::sync_path(&mut db, &foo_init_path);
File::sync_path(&mut db, foo_init_path.parent().unwrap());
let foo_module = resolve_module(&db, foo_module_name).expect("Foo module to resolve");
let foo_module = resolve_module(&db, &foo_module_name).expect("Foo module to resolve");
assert_eq!(&src.join("foo.py"), foo_module.file().path(&db));
Ok(())
@@ -1427,7 +1427,7 @@ mod tests {
let functools_module_name = ModuleName::new_static("functools").unwrap();
let stdlib_functools_path = stdlib.join("functools.pyi");
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!(
Ok(functools_module.file()),
@@ -1440,7 +1440,7 @@ mod tests {
let site_packages_functools_path = site_packages.join("functools.py");
db.write_file(&site_packages_functools_path, "f: int")
.unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
let events = db.take_salsa_events();
assert_function_query_was_not_run(
&db,
@@ -1473,7 +1473,7 @@ mod tests {
.build();
let functools_module_name = ModuleName::new_static("functools").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!(
Ok(functools_module.file()),
@@ -1484,7 +1484,7 @@ mod tests {
// since first-party files take higher priority in module resolution:
let src_functools_path = src.join("functools.py");
db.write_file(&src_functools_path, "FOO: int").unwrap();
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path(), &src);
assert_eq!(
Ok(functools_module.file()),
@@ -1515,7 +1515,7 @@ mod tests {
let functools_module_name = ModuleName::new_static("functools").unwrap();
let src_functools_path = src.join("functools.py");
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path(), &src);
assert_eq!(
Ok(functools_module.file()),
@@ -1528,7 +1528,7 @@ mod tests {
.remove_file(&src_functools_path)
.unwrap();
File::sync_path(&mut db, &src_functools_path);
let functools_module = resolve_module(&db, functools_module_name.clone()).unwrap();
let functools_module = resolve_module(&db, &functools_module_name).unwrap();
assert_eq!(functools_module.search_path(), &stdlib);
assert_eq!(
Ok(functools_module.file()),
@@ -1550,8 +1550,8 @@ mod tests {
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_bar_module_name = ModuleName::new_static("foo.bar").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_bar_module = resolve_module(&db, foo_bar_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let foo_bar_module = resolve_module(&db, &foo_bar_module_name).unwrap();
assert_eq!(
foo_module.file().path(&db),
@@ -1579,11 +1579,11 @@ mod tests {
// Lines with leading whitespace in `.pth` files do not parse:
let foo_module_name = ModuleName::new_static("foo").unwrap();
assert_eq!(resolve_module(&db, foo_module_name), None);
assert_eq!(resolve_module(&db, &foo_module_name), None);
// Lines with trailing whitespace in `.pth` files do:
let bar_module_name = ModuleName::new_static("bar").unwrap();
let bar_module = resolve_module(&db, bar_module_name.clone()).unwrap();
let bar_module = resolve_module(&db, &bar_module_name).unwrap();
assert_eq!(
bar_module.file().path(&db),
&FilePath::system("/y/src/bar.py")
@@ -1602,7 +1602,7 @@ mod tests {
.build();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!(
foo_module.file().path(&db),
@@ -1650,10 +1650,10 @@ not_a_directory
let b_module_name = ModuleName::new_static("b").unwrap();
let spam_module_name = ModuleName::new_static("spam").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let a_module = resolve_module(&db, a_module_name.clone()).unwrap();
let b_module = resolve_module(&db, b_module_name.clone()).unwrap();
let spam_module = resolve_module(&db, spam_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let a_module = resolve_module(&db, &a_module_name).unwrap();
let b_module = resolve_module(&db, &b_module_name).unwrap();
let spam_module = resolve_module(&db, &spam_module_name).unwrap();
assert_eq!(
foo_module.file().path(&db),
@@ -1681,14 +1681,14 @@ not_a_directory
let foo_module_name = ModuleName::new_static("foo").unwrap();
let bar_module_name = ModuleName::new_static("bar").unwrap();
let foo_module = resolve_module(&db, foo_module_name).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!(
foo_module.file().path(&db),
&FilePath::system("/x/src/foo.py")
);
db.clear_salsa_events();
let bar_module = resolve_module(&db, bar_module_name).unwrap();
let bar_module = resolve_module(&db, &bar_module_name).unwrap();
assert_eq!(
bar_module.file().path(&db),
&FilePath::system("/y/src/bar.py")
@@ -1713,7 +1713,7 @@ not_a_directory
db.write_files(x_directory).unwrap();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
assert_eq!(
foo_module.file().path(&db),
&FilePath::system("/x/src/foo.py")
@@ -1725,7 +1725,7 @@ not_a_directory
File::sync_path(&mut db, &site_packages.join("_foo.pth"));
assert_eq!(resolve_module(&db, foo_module_name.clone()), None);
assert_eq!(resolve_module(&db, &foo_module_name), None);
}
#[test]
@@ -1740,7 +1740,7 @@ not_a_directory
db.write_files(x_directory).unwrap();
let foo_module_name = ModuleName::new_static("foo").unwrap();
let foo_module = resolve_module(&db, foo_module_name.clone()).unwrap();
let foo_module = resolve_module(&db, &foo_module_name).unwrap();
let src_path = SystemPathBuf::from("/x/src");
assert_eq!(
foo_module.file().path(&db),
@@ -1753,7 +1753,7 @@ not_a_directory
db.memory_file_system().remove_directory(&src_path).unwrap();
File::sync_path(&mut db, &src_path.join("foo.py"));
File::sync_path(&mut db, &src_path);
assert_eq!(resolve_module(&db, foo_module_name.clone()), None);
assert_eq!(resolve_module(&db, &foo_module_name), None);
}
#[test]
@@ -1812,7 +1812,7 @@ not_a_directory
// The editable installs discovered from the `.pth` file in the first `site-packages` directory
// take precedence over the second `site-packages` directory...
let a_module_name = ModuleName::new_static("a").unwrap();
let a_module = resolve_module(&db, a_module_name.clone()).unwrap();
let a_module = resolve_module(&db, &a_module_name).unwrap();
assert_eq!(a_module.file().path(&db), &editable_install_location);
db.memory_file_system()
@@ -1823,7 +1823,7 @@ not_a_directory
// ...But now that the `.pth` file in the first `site-packages` directory has been deleted,
// the editable install no longer exists, so the module now resolves to the file in the
// second `site-packages` directory
let a_module = resolve_module(&db, a_module_name).unwrap();
let a_module = resolve_module(&db, &a_module_name).unwrap();
assert_eq!(a_module.file().path(&db), &system_site_packages_location);
}
}

View File

@@ -994,7 +994,7 @@ class C[T]:
let ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
value: ast::Number::Int(num),
..
}) = &*assignment.assignment().value
}) = assignment.value()
else {
panic!("should be a number literal")
};

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use except_handlers::TryNodeContextStackManager;
use rustc_hash::FxHashMap;
use ruff_db::files::File;
@@ -28,22 +29,27 @@ use crate::Db;
use super::constraint::{Constraint, PatternConstraint};
use super::definition::{
DefinitionCategory, ExceptHandlerDefinitionNodeRef, MatchPatternDefinitionNodeRef,
WithItemDefinitionNodeRef,
AssignmentKind, DefinitionCategory, ExceptHandlerDefinitionNodeRef,
MatchPatternDefinitionNodeRef, WithItemDefinitionNodeRef,
};
mod except_handlers;
pub(super) struct SemanticIndexBuilder<'db> {
// Builder state
db: &'db dyn Db,
file: File,
module: &'db ParsedModule,
scope_stack: Vec<FileScopeId>,
/// The assignment we're currently visiting.
current_assignment: Option<CurrentAssignment<'db>>,
/// The assignments we're currently visiting, with
/// the most recent visit at the end of the Vec
current_assignments: Vec<CurrentAssignment<'db>>,
/// The match case we're currently visiting.
current_match_case: Option<CurrentMatchCase<'db>>,
/// Flow states at each `break` in the current loop.
loop_break_states: Vec<FlowSnapshot>,
/// Per-scope contexts regarding nested `try`/`except` statements
try_node_context_stack_manager: TryNodeContextStackManager,
/// Flags about the file's global scope
has_future_annotations: bool,
@@ -67,9 +73,10 @@ impl<'db> SemanticIndexBuilder<'db> {
file,
module: parsed,
scope_stack: Vec::new(),
current_assignment: None,
current_assignments: vec![],
current_match_case: None,
loop_break_states: vec![],
try_node_context_stack_manager: TryNodeContextStackManager::default(),
has_future_annotations: false,
@@ -110,6 +117,7 @@ impl<'db> SemanticIndexBuilder<'db> {
kind: node.scope_kind(),
descendents: children_start..children_start,
};
self.try_node_context_stack_manager.enter_nested_scope();
let file_scope_id = self.scopes.push(scope);
self.symbol_tables.push(SymbolTableBuilder::new());
@@ -139,6 +147,7 @@ impl<'db> SemanticIndexBuilder<'db> {
let children_end = self.scopes.next_index();
let scope = &mut self.scopes[id];
scope.descendents = scope.descendents.start..children_end;
self.try_node_context_stack_manager.exit_scope();
id
}
@@ -227,6 +236,10 @@ impl<'db> SemanticIndexBuilder<'db> {
DefinitionCategory::Binding => use_def.record_binding(symbol, definition),
}
let mut try_node_stack_manager = std::mem::take(&mut self.try_node_context_stack_manager);
try_node_stack_manager.record_definition(self);
self.try_node_context_stack_manager = try_node_stack_manager;
definition
}
@@ -238,6 +251,19 @@ impl<'db> SemanticIndexBuilder<'db> {
expression
}
fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) {
self.current_assignments.push(assignment);
}
fn pop_assignment(&mut self) {
let popped_assignment = self.current_assignments.pop();
debug_assert!(popped_assignment.is_some());
}
fn current_assignment(&self) -> Option<&CurrentAssignment<'db>> {
self.current_assignments.last()
}
fn add_pattern_constraint(
&mut self,
subject: &ast::Expr,
@@ -359,12 +385,12 @@ impl<'db> SemanticIndexBuilder<'db> {
self.visit_expr(&generator.iter);
self.push_scope(scope);
self.current_assignment = Some(CurrentAssignment::Comprehension {
self.push_assignment(CurrentAssignment::Comprehension {
node: generator,
first: true,
});
self.visit_expr(&generator.target);
self.current_assignment = None;
self.pop_assignment();
for expr in &generator.ifs {
self.visit_expr(expr);
@@ -374,12 +400,12 @@ impl<'db> SemanticIndexBuilder<'db> {
self.add_standalone_expression(&generator.iter);
self.visit_expr(&generator.iter);
self.current_assignment = Some(CurrentAssignment::Comprehension {
self.push_assignment(CurrentAssignment::Comprehension {
node: generator,
first: false,
});
self.visit_expr(&generator.target);
self.current_assignment = None;
self.pop_assignment();
for expr in &generator.ifs {
self.visit_expr(expr);
@@ -415,7 +441,7 @@ impl<'db> SemanticIndexBuilder<'db> {
self.pop_scope();
assert!(self.scope_stack.is_empty());
assert!(self.current_assignment.is_none());
assert_eq!(&self.current_assignments, &[]);
let mut symbol_tables: IndexVec<_, _> = self
.symbol_tables
@@ -563,24 +589,38 @@ where
}
}
ast::Stmt::Assign(node) => {
debug_assert!(self.current_assignment.is_none());
debug_assert_eq!(&self.current_assignments, &[]);
self.visit_expr(&node.value);
self.add_standalone_expression(&node.value);
self.current_assignment = Some(node.into());
for target in &node.targets {
for (target_index, target) in node.targets.iter().enumerate() {
let kind = match target {
ast::Expr::List(_) | ast::Expr::Tuple(_) => Some(AssignmentKind::Sequence),
ast::Expr::Name(_) => Some(AssignmentKind::Name),
_ => None,
};
if let Some(kind) = kind {
self.push_assignment(CurrentAssignment::Assign {
assignment: node,
target_index,
kind,
});
}
self.visit_expr(target);
if kind.is_some() {
// only need to pop in the case where we pushed something
self.pop_assignment();
}
}
self.current_assignment = None;
}
ast::Stmt::AnnAssign(node) => {
debug_assert!(self.current_assignment.is_none());
debug_assert_eq!(&self.current_assignments, &[]);
self.visit_expr(&node.annotation);
if let Some(value) = &node.value {
self.visit_expr(value);
}
self.current_assignment = Some(node.into());
self.push_assignment(node.into());
self.visit_expr(&node.target);
self.current_assignment = None;
self.pop_assignment();
}
ast::Stmt::AugAssign(
aug_assign @ ast::StmtAugAssign {
@@ -590,11 +630,11 @@ where
value,
},
) => {
debug_assert!(self.current_assignment.is_none());
debug_assert_eq!(&self.current_assignments, &[]);
self.visit_expr(value);
self.current_assignment = Some(aug_assign.into());
self.push_assignment(aug_assign.into());
self.visit_expr(target);
self.current_assignment = None;
self.pop_assignment();
}
ast::Stmt::If(node) => {
self.visit_expr(&node.test);
@@ -662,9 +702,9 @@ where
self.visit_expr(&item.context_expr);
if let Some(optional_vars) = item.optional_vars.as_deref() {
self.add_standalone_expression(&item.context_expr);
self.current_assignment = Some(item.into());
self.push_assignment(item.into());
self.visit_expr(optional_vars);
self.current_assignment = None;
self.pop_assignment();
}
}
self.visit_body(body);
@@ -689,10 +729,10 @@ where
let pre_loop = self.flow_snapshot();
let saved_break_states = std::mem::take(&mut self.loop_break_states);
debug_assert!(self.current_assignment.is_none());
self.current_assignment = Some(for_stmt.into());
debug_assert_eq!(&self.current_assignments, &[]);
self.push_assignment(for_stmt.into());
self.visit_expr(target);
self.current_assignment = None;
self.pop_assignment();
// TODO: Definitions created by loop variables
// (and definitions created inside the body)
@@ -753,40 +793,104 @@ where
is_star,
range: _,
}) => {
// Save the state prior to visiting any of the `try` block.
//
// Potentially none of the `try` block could have been executed prior to executing
// the `except` block(s) and/or the `finally` block.
// 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();
self.try_node_context_stack_manager.push_context();
// Visit the `try` block!
self.visit_body(body);
for except_handler in handlers {
let ast::ExceptHandler::ExceptHandler(except_handler) = except_handler;
let ast::ExceptHandlerExceptHandler {
name: symbol_name,
type_: handled_exceptions,
body: handler_body,
range: _,
} = except_handler;
let mut post_except_states = vec![];
if let Some(handled_exceptions) = handled_exceptions {
self.visit_expr(handled_exceptions);
// Take a record also of all the intermediate states we encountered
// while visiting the `try` block
let try_block_snapshots = self.try_node_context_stack_manager.pop_context();
if !handlers.is_empty() {
// Save the state immediately *after* visiting the `try` block
// but *before* we prepare for visiting the `except` block(s).
//
// We will revert to this state prior to visiting the the `else` block,
// as there necessarily must have been 0 `except` blocks executed
// if we hit the `else` block.
let post_try_block_state = self.flow_snapshot();
// Prepare for visiting the `except` block(s)
self.flow_restore(pre_try_block_state);
for state in try_block_snapshots {
self.flow_merge(state);
}
// If `handled_exceptions` above was `None`, it's something like `except as e:`,
// which is invalid syntax. However, it's still pretty obvious here that the user
// *wanted* `e` to be bound, so we should still create a definition here nonetheless.
if let Some(symbol_name) = symbol_name {
let symbol = self.add_symbol(symbol_name.id.clone());
let pre_except_state = self.flow_snapshot();
let num_handlers = handlers.len();
self.add_definition(
symbol,
DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef {
handler: except_handler,
is_star: *is_star,
}),
);
for (i, except_handler) in handlers.iter().enumerate() {
let ast::ExceptHandler::ExceptHandler(except_handler) = except_handler;
let ast::ExceptHandlerExceptHandler {
name: symbol_name,
type_: handled_exceptions,
body: handler_body,
range: _,
} = except_handler;
if let Some(handled_exceptions) = handled_exceptions {
self.visit_expr(handled_exceptions);
}
// If `handled_exceptions` above was `None`, it's something like `except as e:`,
// which is invalid syntax. However, it's still pretty obvious here that the user
// *wanted* `e` to be bound, so we should still create a definition here nonetheless.
if let Some(symbol_name) = symbol_name {
let symbol = self.add_symbol(symbol_name.id.clone());
self.add_definition(
symbol,
DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef {
handler: except_handler,
is_star: *is_star,
}),
);
}
self.visit_body(handler_body);
// Each `except` block is mutually exclusive with all other `except` blocks.
post_except_states.push(self.flow_snapshot());
// It's unnecessary to do the `self.flow_restore()` call for the final except handler,
// 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());
}
}
self.visit_body(handler_body);
// 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);
}
self.visit_body(orelse);
for post_except_state in post_except_states {
self.flow_merge(post_except_state);
}
// TODO: there's lots of complexity here that isn't yet handled by our model.
// In order to accurately model the semantics of `finally` suites, we in fact need to visit
// the suite twice: once under the (current) assumption that either the `try + else` suite
// ran to completion or exactly one `except` branch ran to completion, and then again under
// the assumption that potentially none of the branches ran to completion and we in fact
// jumped from a `try`, `else` or `except` branch straight into the `finally` branch.
// This requires rethinking some fundamental assumptions semantic indexing makes.
// For more details, see:
// - https://astral-sh.notion.site/Exception-handler-control-flow-11348797e1ca80bb8ce1e9aedbbe439d
// - https://github.com/astral-sh/ruff/pull/13633#discussion_r1788626702
self.visit_body(finalbody);
}
_ => {
@@ -802,7 +906,7 @@ where
match expr {
ast::Expr::Name(name_node @ ast::ExprName { id, ctx, .. }) => {
let (is_use, is_definition) = match (ctx, self.current_assignment) {
let (is_use, is_definition) = match (ctx, self.current_assignment()) {
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
// For augmented assignment, the target expression is also used.
(true, true)
@@ -813,14 +917,21 @@ where
(ast::ExprContext::Invalid, _) => (false, false),
};
let symbol = self.add_symbol(id.clone());
if is_definition {
match self.current_assignment {
Some(CurrentAssignment::Assign(assignment)) => {
match self.current_assignment().copied() {
Some(CurrentAssignment::Assign {
assignment,
target_index,
kind,
}) => {
self.add_definition(
symbol,
AssignmentDefinitionNodeRef {
assignment,
target: name_node,
target_index,
name: name_node,
kind,
},
);
}
@@ -879,12 +990,11 @@ where
walk_expr(self, expr);
}
ast::Expr::Named(node) => {
debug_assert!(self.current_assignment.is_none());
// TODO walrus in comprehensions is implicitly nonlocal
self.visit_expr(&node.value);
self.current_assignment = Some(node.into());
self.push_assignment(node.into());
self.visit_expr(&node.target);
self.current_assignment = None;
self.pop_assignment();
}
ast::Expr::Lambda(lambda) => {
if let Some(parameters) = &lambda.parameters {
@@ -1043,9 +1153,13 @@ where
}
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
enum CurrentAssignment<'a> {
Assign(&'a ast::StmtAssign),
Assign {
assignment: &'a ast::StmtAssign,
target_index: usize,
kind: AssignmentKind,
},
AnnAssign(&'a ast::StmtAnnAssign),
AugAssign(&'a ast::StmtAugAssign),
For(&'a ast::StmtFor),
@@ -1057,12 +1171,6 @@ enum CurrentAssignment<'a> {
WithItem(&'a ast::WithItem),
}
impl<'a> From<&'a ast::StmtAssign> for CurrentAssignment<'a> {
fn from(value: &'a ast::StmtAssign) -> Self {
Self::Assign(value)
}
}
impl<'a> From<&'a ast::StmtAnnAssign> for CurrentAssignment<'a> {
fn from(value: &'a ast::StmtAnnAssign) -> Self {
Self::AnnAssign(value)

View File

@@ -0,0 +1,102 @@
use crate::semantic_index::use_def::FlowSnapshot;
use super::SemanticIndexBuilder;
/// An abstraction over the fact that each scope should have its own [`TryNodeContextStack`]
#[derive(Debug, Default)]
pub(super) struct TryNodeContextStackManager(Vec<TryNodeContextStack>);
impl TryNodeContextStackManager {
/// Push a new [`TryNodeContextStack`] onto the stack of stacks.
///
/// Each [`TryNodeContextStack`] is only valid for a single scope
pub(super) fn enter_nested_scope(&mut self) {
self.0.push(TryNodeContextStack::default());
}
/// Pop a new [`TryNodeContextStack`] off the stack of stacks.
///
/// Each [`TryNodeContextStack`] is only valid for a single scope
pub(super) fn exit_scope(&mut self) {
let popped_context = self.0.pop();
debug_assert!(
popped_context.is_some(),
"exit_scope() should never be called on an empty stack \
(this indicates an unbalanced `enter_nested_scope()`/`exit_scope()` pair of calls)"
);
}
/// Push a [`TryNodeContext`] onto the [`TryNodeContextStack`]
/// at the top of our stack of stacks
pub(super) fn push_context(&mut self) {
self.current_try_context_stack().push_context();
}
/// Pop a [`TryNodeContext`] off the [`TryNodeContextStack`]
/// at the top of our stack of stacks. Return the Vec of [`FlowSnapshot`]s
/// recorded while we were visiting the `try` suite.
pub(super) fn pop_context(&mut self) -> Vec<FlowSnapshot> {
self.current_try_context_stack().pop_context()
}
/// Retrieve the stack that is at the top of our stack of stacks.
/// For each `try` block on that stack, push the snapshot onto the `try` block
pub(super) fn record_definition(&mut self, builder: &SemanticIndexBuilder) {
self.current_try_context_stack().record_definition(builder);
}
/// Retrieve the [`TryNodeContextStack`] that is relevant for the current scope.
fn current_try_context_stack(&mut self) -> &mut TryNodeContextStack {
self.0
.last_mut()
.expect("There should always be at least one `TryBlockContexts` on the stack")
}
}
/// The contexts of nested `try`/`except` blocks for a single scope
#[derive(Debug, Default)]
struct TryNodeContextStack(Vec<TryNodeContext>);
impl TryNodeContextStack {
/// Push a new [`TryNodeContext`] for recording intermediate states
/// while visiting a [`ruff_python_ast::StmtTry`] node that has a `finally` branch.
fn push_context(&mut self) {
self.0.push(TryNodeContext::default());
}
/// Pop a [`TryNodeContext`] off the stack. Return the Vec of [`FlowSnapshot`]s
/// recorded while we were visiting the `try` suite.
fn pop_context(&mut self) -> Vec<FlowSnapshot> {
let TryNodeContext {
try_suite_snapshots,
} = self
.0
.pop()
.expect("Cannot pop a `try` block off an empty `TryBlockContexts` stack");
try_suite_snapshots
}
/// For each `try` block on the stack, push the snapshot onto the `try` block
fn record_definition(&mut self, builder: &SemanticIndexBuilder) {
for context in &mut self.0 {
context.record_definition(builder.flow_snapshot());
}
}
}
/// Context for tracking definitions over the course of a single
/// [`ruff_python_ast::StmtTry`] node
///
/// It will likely be necessary to add more fields to this struct in the future
/// when we add more advanced handling of `finally` branches.
#[derive(Debug, Default)]
struct TryNodeContext {
try_suite_snapshots: Vec<FlowSnapshot>,
}
impl TryNodeContext {
/// Take a record of what the internal state looked like after a definition
fn record_definition(&mut self, snapshot: FlowSnapshot) {
self.try_suite_snapshots.push(snapshot);
}
}

View File

@@ -161,7 +161,9 @@ pub(crate) struct ImportFromDefinitionNodeRef<'a> {
#[derive(Copy, Clone, Debug)]
pub(crate) struct AssignmentDefinitionNodeRef<'a> {
pub(crate) assignment: &'a ast::StmtAssign,
pub(crate) target: &'a ast::ExprName,
pub(crate) target_index: usize,
pub(crate) name: &'a ast::ExprName,
pub(crate) kind: AssignmentKind,
}
#[derive(Copy, Clone, Debug)]
@@ -224,12 +226,17 @@ impl DefinitionNodeRef<'_> {
DefinitionNodeRef::NamedExpression(named) => {
DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named))
}
DefinitionNodeRef::Assignment(AssignmentDefinitionNodeRef { assignment, target }) => {
DefinitionKind::Assignment(AssignmentDefinitionKind {
assignment: AstNodeRef::new(parsed.clone(), assignment),
target: AstNodeRef::new(parsed, target),
})
}
DefinitionNodeRef::Assignment(AssignmentDefinitionNodeRef {
assignment,
target_index,
name,
kind,
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
assignment: AstNodeRef::new(parsed.clone(), assignment),
target_index,
name: AstNodeRef::new(parsed, name),
kind,
}),
DefinitionNodeRef::AnnotatedAssignment(assign) => {
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
}
@@ -300,8 +307,10 @@ impl DefinitionNodeRef<'_> {
Self::NamedExpression(node) => node.into(),
Self::Assignment(AssignmentDefinitionNodeRef {
assignment: _,
target,
}) => target.into(),
target_index: _,
name,
kind: _,
}) => name.into(),
Self::AnnotatedAssignment(node) => node.into(),
Self::AugmentedAssignment(node) => node.into(),
Self::For(ForStmtDefinitionNodeRef {
@@ -485,17 +494,34 @@ impl ImportFromDefinitionKind {
#[derive(Clone, Debug)]
pub struct AssignmentDefinitionKind {
assignment: AstNodeRef<ast::StmtAssign>,
target: AstNodeRef<ast::ExprName>,
target_index: usize,
name: AstNodeRef<ast::ExprName>,
kind: AssignmentKind,
}
impl AssignmentDefinitionKind {
pub(crate) fn assignment(&self) -> &ast::StmtAssign {
self.assignment.node()
pub(crate) fn value(&self) -> &ast::Expr {
&self.assignment.node().value
}
pub(crate) fn target(&self) -> &ast::ExprName {
self.target.node()
pub(crate) fn target(&self) -> &ast::Expr {
&self.assignment.node().targets[self.target_index]
}
pub(crate) fn name(&self) -> &ast::ExprName {
self.name.node()
}
pub(crate) fn kind(&self) -> AssignmentKind {
self.kind
}
}
/// The kind of assignment target expression.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssignmentKind {
Sequence,
Name,
}
#[derive(Clone, Debug)]

View File

@@ -35,7 +35,7 @@ impl<'db> SemanticModel<'db> {
line_index(self.db.upcast(), self.file)
}
pub fn resolve_module(&self, module_name: ModuleName) -> Option<Module> {
pub fn resolve_module(&self, module_name: &ModuleName) -> Option<Module> {
resolve_module(self.db, module_name)
}

View File

@@ -35,8 +35,15 @@ fn core_module_symbol_ty<'db>(
core_module: CoreStdlibModule,
symbol: &str,
) -> Type<'db> {
resolve_module(db, core_module.name())
resolve_module(db, &core_module.name())
.map(|module| global_symbol_ty(db, module.file(), symbol))
.map(|ty| {
if ty.is_unbound() {
ty
} else {
ty.replace_unbound_with(db, Type::Never)
}
})
.unwrap_or(Type::Unbound)
}
@@ -76,7 +83,7 @@ pub(crate) fn typing_extensions_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) ->
///
/// Can return `None` if a custom typeshed is used that is missing the core module in question.
fn core_module_scope(db: &dyn Db, core_module: CoreStdlibModule) -> Option<ScopeId<'_>> {
resolve_module(db, core_module.name()).map(|module| global_scope(db, module.file()))
resolve_module(db, &core_module.name()).map(|module| global_scope(db, module.file()))
}
/// Get the `builtins` module scope.

View File

@@ -60,7 +60,7 @@ fn symbol_ty_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymb
use_def.public_bindings(symbol),
use_def
.public_may_be_unbound(symbol)
.then_some(Type::Unknown),
.then_some(Type::Unbound),
))
} else {
None
@@ -79,7 +79,7 @@ fn symbol_ty_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymb
}
}
/// Shorthand for `symbol_ty` that takes a symbol name instead of an ID.
/// Shorthand for `symbol_ty_by_id` that takes a symbol name instead of an ID.
fn symbol_ty<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Type<'db> {
let table = symbol_table(db, scope);
table
@@ -381,7 +381,7 @@ impl<'db> Type<'db> {
Type::Union(union) => {
union.map(db, |element| element.replace_unbound_with(db, replacement))
}
ty => *ty,
_ => *self,
}
}
@@ -415,6 +415,11 @@ impl<'db> Type<'db> {
(_, Type::Unknown | Type::Any | Type::Todo) => false,
(Type::Never, _) => true,
(_, Type::Never) => false,
(Type::BooleanLiteral(_), Type::Instance(class))
if class.is_known(db, KnownClass::Bool) =>
{
true
}
(Type::IntLiteral(_), Type::Instance(class)) if class.is_known(db, KnownClass::Int) => {
true
}
@@ -444,6 +449,9 @@ impl<'db> Type<'db> {
///
/// [assignable to]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
if self.is_equivalent_to(db, target) {
return true;
}
match (self, target) {
(Type::Unknown | Type::Any | Type::Todo, _) => true,
(_, Type::Unknown | Type::Any | Type::Todo) => true,
@@ -463,6 +471,55 @@ impl<'db> Type<'db> {
self == other
}
/// 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`
/// for more complicated types that are actually singletons.
pub(crate) fn is_singleton(self) -> bool {
match self {
Type::Any
| Type::Never
| Type::Unknown
| Type::Todo
| Type::Unbound
| Type::Instance(..) // TODO some instance types can be singleton types (EllipsisType, NotImplementedType)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::BytesLiteral(..)
| Type::LiteralString => {
// Note: The literal types included in this pattern are not true singletons.
// There can be multiple Python objects (at different memory locations) that
// are both of type Literal[345], for example.
false
}
Type::None | Type::BooleanLiteral(_) | Type::Function(..) | Type::Class(..) | Type::Module(..) => true,
Type::Tuple(..) => {
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
// should not be relied on for type narrowing, so we do not treat it as one.
// See:
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
false
}
Type::Union(..) => {
// A single-element union, where the sole element was a singleton, would itself
// be a singleton type. However, unions with length < 2 should never appear in
// our model due to [`UnionBuilder::build`].
false
}
Type::Intersection(..) => {
// Here, we assume that all intersection types that are singletons would have
// been reduced to a different form via [`IntersectionBuilder::build`] by now.
// For example:
//
// bool & ~Literal[False] = Literal[True]
// None & (None | int) = None | None & int = None
//
false
}
}
}
/// Resolve a member access of a type.
///
/// For example, if `foo` is `Type::Instance(<Bar>)`,
@@ -791,14 +848,10 @@ impl<'db> Type<'db> {
Type::IntLiteral(number) => Type::StringLiteral(StringLiteralType::new(db, {
number.to_string().into_boxed_str()
})),
Type::BooleanLiteral(true) => {
Type::StringLiteral(StringLiteralType::new(db, "True".into()))
}
Type::BooleanLiteral(false) => {
Type::StringLiteral(StringLiteralType::new(db, "False".into()))
}
Type::BooleanLiteral(true) => Type::StringLiteral(StringLiteralType::new(db, "True")),
Type::BooleanLiteral(false) => Type::StringLiteral(StringLiteralType::new(db, "False")),
Type::StringLiteral(literal) => Type::StringLiteral(StringLiteralType::new(db, {
format!("'{}'", literal.value(db).escape_default()).into()
format!("'{}'", literal.value(db).escape_default()).into_boxed_str()
})),
Type::LiteralString => Type::LiteralString,
// TODO: handle more complex types
@@ -1381,7 +1434,8 @@ impl<'db> ClassType<'db> {
pub fn class_member(self, db: &'db dyn Db, name: &str) -> Type<'db> {
let member = self.own_class_member(db, name);
if !member.is_unbound() {
return member;
// TODO diagnostic if maybe unbound?
return member.replace_unbound_with(db, Type::Never);
}
self.inherited_class_member(db, name)
@@ -1463,6 +1517,12 @@ pub struct StringLiteralType<'db> {
value: Box<str>,
}
impl<'db> StringLiteralType<'db> {
pub fn len(&self, db: &'db dyn Db) -> usize {
self.value(db).len()
}
}
#[salsa::interned]
pub struct BytesLiteralType<'db> {
#[return_ref]
@@ -1475,6 +1535,16 @@ pub struct TupleType<'db> {
elements: Box<[Type<'db>]>,
}
impl<'db> TupleType<'db> {
pub fn get(&self, db: &'db dyn Db, index: usize) -> Option<Type<'db>> {
self.elements(db).get(index).copied()
}
pub fn len(&self, db: &'db dyn Db) -> usize {
self.elements(db).len()
}
}
#[cfg(test)]
mod tests {
use super::{
@@ -1514,6 +1584,7 @@ mod tests {
enum Ty {
Never,
Unknown,
None,
Any,
IntLiteral(i64),
BoolLiteral(bool),
@@ -1530,22 +1601,19 @@ mod tests {
match self {
Ty::Never => Type::Never,
Ty::Unknown => Type::Unknown,
Ty::None => Type::None,
Ty::Any => Type::Any,
Ty::IntLiteral(n) => Type::IntLiteral(n),
Ty::StringLiteral(s) => {
Type::StringLiteral(StringLiteralType::new(db, (*s).into()))
}
Ty::StringLiteral(s) => Type::StringLiteral(StringLiteralType::new(db, s)),
Ty::BoolLiteral(b) => Type::BooleanLiteral(b),
Ty::LiteralString => Type::LiteralString,
Ty::BytesLiteral(s) => {
Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes().into()))
}
Ty::BytesLiteral(s) => Type::BytesLiteral(BytesLiteralType::new(db, s.as_bytes())),
Ty::BuiltinInstance(s) => builtins_symbol_ty(db, s).to_instance(db),
Ty::Union(tys) => {
UnionType::from_elements(db, tys.into_iter().map(|ty| ty.into_type(db)))
}
Ty::Tuple(tys) => {
let elements = tys.into_iter().map(|ty| ty.into_type(db)).collect();
let elements: Box<_> = tys.into_iter().map(|ty| ty.into_type(db)).collect();
Type::Tuple(TupleType::new(db, elements))
}
}
@@ -1566,6 +1634,7 @@ mod tests {
#[test_case(Ty::BytesLiteral("foo"), Ty::BuiltinInstance("bytes"))]
#[test_case(Ty::IntLiteral(1), Ty::Union(vec![Ty::BuiltinInstance("int"), Ty::BuiltinInstance("str")]))]
#[test_case(Ty::IntLiteral(1), Ty::Union(vec![Ty::Unknown, Ty::BuiltinInstance("str")]))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]))]
fn is_assignable_to(from: Ty, to: Ty) {
let db = setup_db();
assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
@@ -1618,6 +1687,28 @@ mod tests {
assert!(from.into_type(&db).is_equivalent_to(&db, to.into_type(&db)));
}
#[test_case(Ty::None)]
#[test_case(Ty::BoolLiteral(true))]
#[test_case(Ty::BoolLiteral(false))]
fn is_singleton(from: Ty) {
let db = setup_db();
assert!(from.into_type(&db).is_singleton());
}
#[test_case(Ty::Never)]
#[test_case(Ty::IntLiteral(345))]
#[test_case(Ty::BuiltinInstance("str"))]
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]))]
#[test_case(Ty::Tuple(vec![]))]
#[test_case(Ty::Tuple(vec![Ty::None]))]
#[test_case(Ty::Tuple(vec![Ty::None, Ty::BoolLiteral(true)]))]
fn is_not_singleton(from: Ty) {
let db = setup_db();
assert!(!from.into_type(&db).is_singleton());
}
#[test_case(Ty::IntLiteral(1); "is_int_literal_truthy")]
#[test_case(Ty::IntLiteral(-1))]
#[test_case(Ty::StringLiteral("foo"))]

View File

@@ -111,7 +111,7 @@ impl<'db> UnionBuilder<'db> {
match self.elements.len() {
0 => Type::Never,
1 => self.elements[0],
_ => Type::Union(UnionType::new(self.db, self.elements.into())),
_ => Type::Union(UnionType::new(self.db, self.elements.into_boxed_slice())),
}
}
}
@@ -374,6 +374,12 @@ mod tests {
let union = UnionType::from_elements(&db, [t0, t1, t2, t3]).expect_union();
assert_eq!(union.elements(&db), &[bool_instance_ty, t3]);
let result_ty = UnionType::from_elements(&db, [bool_instance_ty, t0]);
assert_eq!(result_ty, bool_instance_ty);
let result_ty = UnionType::from_elements(&db, [t0, bool_instance_ty]);
assert_eq!(result_ty, bool_instance_ty);
}
#[test]

View File

@@ -341,12 +341,12 @@ mod tests {
Type::Unknown,
Type::IntLiteral(-1),
global_symbol_ty(&db, mod_file, "A"),
Type::StringLiteral(StringLiteralType::new(&db, Box::from("A"))),
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([0]))),
Type::BytesLiteral(BytesLiteralType::new(&db, Box::from([7]))),
Type::StringLiteral(StringLiteralType::new(&db, "A")),
Type::BytesLiteral(BytesLiteralType::new(&db, [0u8].as_slice())),
Type::BytesLiteral(BytesLiteralType::new(&db, [7u8].as_slice())),
Type::IntLiteral(0),
Type::IntLiteral(1),
Type::StringLiteral(StringLiteralType::new(&db, Box::from("B"))),
Type::StringLiteral(StringLiteralType::new(&db, "B")),
global_symbol_ty(&db, mod_file, "foo"),
global_symbol_ty(&db, mod_file, "bar"),
global_symbol_ty(&db, mod_file, "B"),

File diff suppressed because it is too large Load Diff

View File

@@ -155,13 +155,24 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
let inference = infer_expression_types(self.db, expression);
for (op, comparator) in std::iter::zip(&**ops, &**comparators) {
let comp_ty = inference.expression_ty(comparator.scoped_ast_id(self.db, scope));
if matches!(op, ast::CmpOp::IsNot) {
let ty = IntersectionBuilder::new(self.db)
.add_negative(comp_ty)
.build();
self.constraints.insert(symbol, ty);
};
// TODO other comparison types
match op {
ast::CmpOp::IsNot => {
if comp_ty.is_singleton() {
let ty = IntersectionBuilder::new(self.db)
.add_negative(comp_ty)
.build();
self.constraints.insert(symbol, ty);
} else {
// Non-singletons cannot be safely narrowed using `is not`
}
}
ast::CmpOp::Is => {
self.constraints.insert(symbol, comp_ty);
}
_ => {
// TODO other comparison types
}
}
}
}
}

View File

@@ -141,7 +141,7 @@ static HEADER_RE: Lazy<Regex> =
/// Matches a code block fenced by triple backticks, possibly with language and `key=val`
/// configuration items following the opening backticks (in the "tag string" of the code block).
static CODE_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^```(?<lang>\w+)(?<config>( +\S+)*)\s*\n(?<code>(.|\n)*?)\n```\s*\n").unwrap()
Regex::new(r"^```(?<lang>\w+)(?<config>( +\S+)*)\s*\n(?<code>(.|\n)*?)\n?```\s*\n").unwrap()
});
#[derive(Debug)]

View File

@@ -21,7 +21,7 @@ the project the stubs are for, but instead report them here to typeshed.**
Further documentation on stub files, typeshed, and Python's typing system in
general, can also be found at https://typing.readthedocs.io/en/latest/.
Typeshed supports Python versions 3.8 and up.
Typeshed supports Python versions 3.8 to 3.13.
## Using

View File

@@ -1 +1 @@
91a58b07cdd807b1d965e04ba85af2adab8bf924
a871efd90ca2734b3341dde98cffab66f3e08cee

View File

@@ -20,6 +20,7 @@
__future__: 3.0-
__main__: 3.0-
_ast: 3.0-
_asyncio: 3.0-
_bisect: 3.0-
_bootlocale: 3.4-3.9
_codecs: 3.0-
@@ -37,6 +38,7 @@ _imp: 3.0-
_interpchannels: 3.13-
_interpqueues: 3.13-
_interpreters: 3.13-
_io: 3.0-
_json: 3.0-
_locale: 3.0-
_lsprof: 3.0-
@@ -50,6 +52,8 @@ _pydecimal: 3.5-
_random: 3.0-
_sitebuiltins: 3.4-
_socket: 3.0- # present in 3.0 at runtime, but not in typeshed
_sqlite3: 3.0-
_ssl: 3.0-
_stat: 3.4-
_thread: 3.0-
_threading_local: 3.0-

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,120 @@
import sys
from asyncio.events import AbstractEventLoop
from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable
from contextvars import Context
from types import FrameType
from typing import Any, Literal, TextIO, TypeVar
from typing_extensions import Self, TypeAlias
if sys.version_info >= (3, 9):
from types import GenericAlias
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
_TaskYieldType: TypeAlias = Future[object] | None
class Future(Awaitable[_T], Iterable[_T]):
_state: str
@property
def _exception(self) -> BaseException | None: ...
_blocking: bool
@property
def _log_traceback(self) -> bool: ...
@_log_traceback.setter
def _log_traceback(self, val: Literal[False]) -> None: ...
_asyncio_future_blocking: bool # is a part of duck-typing contract for `Future`
def __init__(self, *, loop: AbstractEventLoop | None = ...) -> None: ...
def __del__(self) -> None: ...
def get_loop(self) -> AbstractEventLoop: ...
@property
def _callbacks(self) -> list[tuple[Callable[[Self], Any], Context]]: ...
def add_done_callback(self, fn: Callable[[Self], object], /, *, context: Context | None = None) -> None: ...
if sys.version_info >= (3, 9):
def cancel(self, msg: Any | None = None) -> bool: ...
else:
def cancel(self) -> bool: ...
def cancelled(self) -> bool: ...
def done(self) -> bool: ...
def result(self) -> _T: ...
def exception(self) -> BaseException | None: ...
def remove_done_callback(self, fn: Callable[[Self], object], /) -> int: ...
def set_result(self, result: _T, /) -> None: ...
def set_exception(self, exception: type | BaseException, /) -> None: ...
def __iter__(self) -> Generator[Any, None, _T]: ...
def __await__(self) -> Generator[Any, None, _T]: ...
@property
def _loop(self) -> AbstractEventLoop: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
if sys.version_info >= (3, 12):
_TaskCompatibleCoro: TypeAlias = Coroutine[Any, Any, _T_co]
elif sys.version_info >= (3, 9):
_TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Coroutine[Any, Any, _T_co]
else:
_TaskCompatibleCoro: TypeAlias = Generator[_TaskYieldType, None, _T_co] | Awaitable[_T_co]
# mypy and pyright complain that a subclass of an invariant class shouldn't be covariant.
# While this is true in general, here it's sort-of okay to have a covariant subclass,
# since the only reason why `asyncio.Future` is invariant is the `set_result()` method,
# and `asyncio.Task.set_result()` always raises.
class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportInvalidTypeArguments]
if sys.version_info >= (3, 12):
def __init__(
self,
coro: _TaskCompatibleCoro[_T_co],
*,
loop: AbstractEventLoop = ...,
name: str | None = ...,
context: Context | None = None,
eager_start: bool = False,
) -> None: ...
elif sys.version_info >= (3, 11):
def __init__(
self,
coro: _TaskCompatibleCoro[_T_co],
*,
loop: AbstractEventLoop = ...,
name: str | None = ...,
context: Context | None = None,
) -> None: ...
else:
def __init__(
self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ...
) -> None: ...
if sys.version_info >= (3, 12):
def get_coro(self) -> _TaskCompatibleCoro[_T_co] | None: ...
else:
def get_coro(self) -> _TaskCompatibleCoro[_T_co]: ...
def get_name(self) -> str: ...
def set_name(self, value: object, /) -> None: ...
if sys.version_info >= (3, 12):
def get_context(self) -> Context: ...
def get_stack(self, *, limit: int | None = None) -> list[FrameType]: ...
def print_stack(self, *, limit: int | None = None, file: TextIO | None = None) -> None: ...
if sys.version_info >= (3, 11):
def cancelling(self) -> int: ...
def uncancel(self) -> int: ...
if sys.version_info < (3, 9):
@classmethod
def current_task(cls, loop: AbstractEventLoop | None = None) -> Task[Any] | None: ...
@classmethod
def all_tasks(cls, loop: AbstractEventLoop | None = None) -> set[Task[Any]]: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def get_event_loop() -> AbstractEventLoop: ...
def get_running_loop() -> AbstractEventLoop: ...
def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ...
def _get_running_loop() -> AbstractEventLoop: ...
def _register_task(task: Task[Any]) -> None: ...
def _unregister_task(task: Task[Any]) -> None: ...
def _enter_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ...
def _leave_task(loop: AbstractEventLoop, task: Task[Any]) -> None: ...
if sys.version_info >= (3, 12):
def current_task(loop: AbstractEventLoop | None = None) -> Task[Any] | None: ...

View File

@@ -20,6 +20,8 @@ _QuotingType: TypeAlias = int
class Error(Exception): ...
_DialectLike: TypeAlias = str | Dialect | type[Dialect]
class Dialect:
delimiter: str
quotechar: str | None
@@ -29,9 +31,18 @@ class Dialect:
lineterminator: str
quoting: _QuotingType
strict: bool
def __init__(self) -> None: ...
_DialectLike: TypeAlias = str | Dialect | type[Dialect]
def __init__(
self,
dialect: _DialectLike | None = ...,
delimiter: str = ",",
doublequote: bool = True,
escapechar: str | None = None,
lineterminator: str = "\r\n",
quotechar: str | None = '"',
quoting: _QuotingType = 0,
skipinitialspace: bool = False,
strict: bool = False,
) -> None: ...
class _reader(Iterator[list[str]]):
@property

View File

@@ -2,7 +2,7 @@ import sys
from _typeshed import ReadableBuffer, WriteableBuffer
from abc import abstractmethod
from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
from ctypes import CDLL, ArgumentError as ArgumentError
from ctypes import CDLL, ArgumentError as ArgumentError, c_void_p
from typing import Any, ClassVar, Generic, TypeVar, overload
from typing_extensions import Self, TypeAlias
@@ -99,6 +99,9 @@ class _Pointer(_PointerLike, _CData, Generic[_CT]):
def __getitem__(self, key: slice, /) -> list[Any]: ...
def __setitem__(self, key: int, value: Any, /) -> None: ...
@overload
def POINTER(type: None, /) -> type[c_void_p]: ...
@overload
def POINTER(type: type[_CT], /) -> type[_Pointer[_CT]]: ...
def pointer(obj: _CT, /) -> _Pointer[_CT]: ...

View File

@@ -298,7 +298,7 @@ if sys.version_info >= (3, 9):
def getmouse() -> tuple[int, int, int, int, int]: ...
def getsyx() -> tuple[int, int]: ...
def getwin(file: SupportsRead[bytes], /) -> _CursesWindow: ...
def getwin(file: SupportsRead[bytes], /) -> window: ...
def halfdelay(tenths: int, /) -> None: ...
def has_colors() -> bool: ...
@@ -310,7 +310,7 @@ def has_il() -> bool: ...
def has_key(key: int, /) -> bool: ...
def init_color(color_number: int, r: int, g: int, b: int, /) -> None: ...
def init_pair(pair_number: int, fg: int, bg: int, /) -> None: ...
def initscr() -> _CursesWindow: ...
def initscr() -> window: ...
def intrflush(flag: bool, /) -> None: ...
def is_term_resized(nlines: int, ncols: int, /) -> bool: ...
def isendwin() -> bool: ...
@@ -321,8 +321,8 @@ def meta(yes: bool, /) -> None: ...
def mouseinterval(interval: int, /) -> None: ...
def mousemask(newmask: int, /) -> tuple[int, int]: ...
def napms(ms: int, /) -> int: ...
def newpad(nlines: int, ncols: int, /) -> _CursesWindow: ...
def newwin(nlines: int, ncols: int, begin_y: int = ..., begin_x: int = ..., /) -> _CursesWindow: ...
def newpad(nlines: int, ncols: int, /) -> window: ...
def newwin(nlines: int, ncols: int, begin_y: int = ..., begin_x: int = ..., /) -> window: ...
def nl(flag: bool = True, /) -> None: ...
def nocbreak() -> None: ...
def noecho() -> None: ...
@@ -378,7 +378,7 @@ def use_env(flag: bool, /) -> None: ...
class error(Exception): ...
@final
class _CursesWindow:
class window: # undocumented
encoding: str
@overload
def addch(self, ch: _ChType, attr: int = ...) -> None: ...
@@ -431,9 +431,9 @@ class _CursesWindow:
def delch(self, y: int, x: int) -> None: ...
def deleteln(self) -> None: ...
@overload
def derwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ...
def derwin(self, begin_y: int, begin_x: int) -> window: ...
@overload
def derwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ...
def derwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ...
def echochar(self, ch: _ChType, attr: int = ..., /) -> None: ...
def enclose(self, y: int, x: int, /) -> bool: ...
def erase(self) -> None: ...
@@ -505,16 +505,16 @@ class _CursesWindow:
@overload
def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int, smincol: int, smaxrow: int, smaxcol: int) -> None: ...
@overload
def overlay(self, destwin: _CursesWindow) -> None: ...
def overlay(self, destwin: window) -> None: ...
@overload
def overlay(
self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int
self, destwin: window, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int
) -> None: ...
@overload
def overwrite(self, destwin: _CursesWindow) -> None: ...
def overwrite(self, destwin: window) -> None: ...
@overload
def overwrite(
self, destwin: _CursesWindow, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int
self, destwin: window, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int
) -> None: ...
def putwin(self, file: IO[Any], /) -> None: ...
def redrawln(self, beg: int, num: int, /) -> None: ...
@@ -530,13 +530,13 @@ class _CursesWindow:
def standend(self) -> None: ...
def standout(self) -> None: ...
@overload
def subpad(self, begin_y: int, begin_x: int) -> _CursesWindow: ...
def subpad(self, begin_y: int, begin_x: int) -> window: ...
@overload
def subpad(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ...
def subpad(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ...
@overload
def subwin(self, begin_y: int, begin_x: int) -> _CursesWindow: ...
def subwin(self, begin_y: int, begin_x: int) -> window: ...
@overload
def subwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> _CursesWindow: ...
def subwin(self, nlines: int, ncols: int, begin_y: int, begin_x: int) -> window: ...
def syncdown(self) -> None: ...
def syncok(self, flag: bool) -> None: ...
def syncup(self) -> None: ...
@@ -555,4 +555,3 @@ class _ncurses_version(NamedTuple):
patch: int
ncurses_version: _ncurses_version
window = _CursesWindow # undocumented

View File

@@ -1,22 +1,39 @@
import numbers
import sys
from collections.abc import Container, Sequence
from decimal import (
Clamped as Clamped,
Context as Context,
ConversionSyntax as ConversionSyntax,
Decimal as Decimal,
DecimalException as DecimalException,
DecimalTuple as DecimalTuple,
DivisionByZero as DivisionByZero,
DivisionImpossible as DivisionImpossible,
DivisionUndefined as DivisionUndefined,
FloatOperation as FloatOperation,
Inexact as Inexact,
InvalidContext as InvalidContext,
InvalidOperation as InvalidOperation,
Overflow as Overflow,
Rounded as Rounded,
Subnormal as Subnormal,
Underflow as Underflow,
)
from types import TracebackType
from typing import Any, ClassVar, Final, Literal, NamedTuple, overload
from typing_extensions import Self, TypeAlias
from typing import Final
from typing_extensions import TypeAlias
_Decimal: TypeAlias = Decimal | int
_DecimalNew: TypeAlias = Decimal | float | str | tuple[int, Sequence[int], int]
_ComparableNum: TypeAlias = Decimal | float | numbers.Rational
_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]
class DecimalTuple(NamedTuple):
sign: int
digits: tuple[int, ...]
exponent: int | Literal["n", "N", "F"]
ROUND_DOWN: Final[str]
ROUND_HALF_UP: Final[str]
ROUND_HALF_EVEN: Final[str]
@@ -32,21 +49,6 @@ MAX_PREC: Final[int]
MIN_EMIN: Final[int]
MIN_ETINY: Final[int]
class DecimalException(ArithmeticError): ...
class Clamped(DecimalException): ...
class InvalidOperation(DecimalException): ...
class ConversionSyntax(InvalidOperation): ...
class DivisionByZero(DecimalException, ZeroDivisionError): ...
class DivisionImpossible(InvalidOperation): ...
class DivisionUndefined(InvalidOperation, ZeroDivisionError): ...
class Inexact(DecimalException): ...
class InvalidContext(InvalidOperation): ...
class Rounded(DecimalException): ...
class Subnormal(DecimalException): ...
class Overflow(Inexact, Rounded): ...
class Underflow(Inexact, Rounded, Subnormal): ...
class FloatOperation(DecimalException, TypeError): ...
def setcontext(context: Context, /) -> None: ...
def getcontext() -> Context: ...
@@ -67,215 +69,6 @@ if sys.version_info >= (3, 11):
else:
def localcontext(ctx: Context | None = None) -> _ContextManager: ...
class Decimal:
def __new__(cls, value: _DecimalNew = ..., context: Context | None = ...) -> Self: ...
@classmethod
def from_float(cls, f: float, /) -> Self: ...
def __bool__(self) -> bool: ...
def compare(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def __hash__(self) -> int: ...
def as_tuple(self) -> DecimalTuple: ...
def as_integer_ratio(self) -> tuple[int, int]: ...
def to_eng_string(self, context: Context | None = None) -> str: ...
def __abs__(self) -> Decimal: ...
def __add__(self, value: _Decimal, /) -> Decimal: ...
def __divmod__(self, value: _Decimal, /) -> tuple[Decimal, Decimal]: ...
def __eq__(self, value: object, /) -> bool: ...
def __floordiv__(self, value: _Decimal, /) -> Decimal: ...
def __ge__(self, value: _ComparableNum, /) -> bool: ...
def __gt__(self, value: _ComparableNum, /) -> bool: ...
def __le__(self, value: _ComparableNum, /) -> bool: ...
def __lt__(self, value: _ComparableNum, /) -> bool: ...
def __mod__(self, value: _Decimal, /) -> Decimal: ...
def __mul__(self, value: _Decimal, /) -> Decimal: ...
def __neg__(self) -> Decimal: ...
def __pos__(self) -> Decimal: ...
def __pow__(self, value: _Decimal, mod: _Decimal | None = None, /) -> Decimal: ...
def __radd__(self, value: _Decimal, /) -> Decimal: ...
def __rdivmod__(self, value: _Decimal, /) -> tuple[Decimal, Decimal]: ...
def __rfloordiv__(self, value: _Decimal, /) -> Decimal: ...
def __rmod__(self, value: _Decimal, /) -> Decimal: ...
def __rmul__(self, value: _Decimal, /) -> Decimal: ...
def __rsub__(self, value: _Decimal, /) -> Decimal: ...
def __rtruediv__(self, value: _Decimal, /) -> Decimal: ...
def __sub__(self, value: _Decimal, /) -> Decimal: ...
def __truediv__(self, value: _Decimal, /) -> Decimal: ...
def remainder_near(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def __float__(self) -> float: ...
def __int__(self) -> int: ...
def __trunc__(self) -> int: ...
@property
def real(self) -> Decimal: ...
@property
def imag(self) -> Decimal: ...
def conjugate(self) -> Decimal: ...
def __complex__(self) -> complex: ...
@overload
def __round__(self) -> int: ...
@overload
def __round__(self, ndigits: int, /) -> Decimal: ...
def __floor__(self) -> int: ...
def __ceil__(self) -> int: ...
def fma(self, other: _Decimal, third: _Decimal, context: Context | None = None) -> Decimal: ...
def __rpow__(self, value: _Decimal, mod: Context | None = None, /) -> Decimal: ...
def normalize(self, context: Context | None = None) -> Decimal: ...
def quantize(self, exp: _Decimal, rounding: str | None = None, context: Context | None = None) -> Decimal: ...
def same_quantum(self, other: _Decimal, context: Context | None = None) -> bool: ...
def to_integral_exact(self, rounding: str | None = None, context: Context | None = None) -> Decimal: ...
def to_integral_value(self, rounding: str | None = None, context: Context | None = None) -> Decimal: ...
def to_integral(self, rounding: str | None = None, context: Context | None = None) -> Decimal: ...
def sqrt(self, context: Context | None = None) -> Decimal: ...
def max(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def min(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def adjusted(self) -> int: ...
def canonical(self) -> Decimal: ...
def compare_signal(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def compare_total(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def compare_total_mag(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def copy_abs(self) -> Decimal: ...
def copy_negate(self) -> Decimal: ...
def copy_sign(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def exp(self, context: Context | None = None) -> Decimal: ...
def is_canonical(self) -> bool: ...
def is_finite(self) -> bool: ...
def is_infinite(self) -> bool: ...
def is_nan(self) -> bool: ...
def is_normal(self, context: Context | None = None) -> bool: ...
def is_qnan(self) -> bool: ...
def is_signed(self) -> bool: ...
def is_snan(self) -> bool: ...
def is_subnormal(self, context: Context | None = None) -> bool: ...
def is_zero(self) -> bool: ...
def ln(self, context: Context | None = None) -> Decimal: ...
def log10(self, context: Context | None = None) -> Decimal: ...
def logb(self, context: Context | None = None) -> Decimal: ...
def logical_and(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def logical_invert(self, context: Context | None = None) -> Decimal: ...
def logical_or(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def logical_xor(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def max_mag(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def min_mag(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def next_minus(self, context: Context | None = None) -> Decimal: ...
def next_plus(self, context: Context | None = None) -> Decimal: ...
def next_toward(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def number_class(self, context: Context | None = None) -> str: ...
def radix(self) -> Decimal: ...
def rotate(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def scaleb(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def shift(self, other: _Decimal, context: Context | None = None) -> Decimal: ...
def __reduce__(self) -> tuple[type[Self], tuple[str]]: ...
def __copy__(self) -> Self: ...
def __deepcopy__(self, memo: Any, /) -> Self: ...
def __format__(self, specifier: str, context: Context | None = ..., /) -> str: ...
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: ...
_TrapType: TypeAlias = type[DecimalException]
class Context:
# TODO: Context doesn't allow you to delete *any* attributes from instances of the class at runtime,
# even settable attributes like `prec` and `rounding`,
# but that's inexpressable in the stub.
# Type checkers either ignore it or misinterpret it
# if you add a `def __delattr__(self, name: str, /) -> NoReturn` method to the stub
prec: int
rounding: str
Emin: int
Emax: int
capitals: int
clamp: int
traps: dict[_TrapType, bool]
flags: dict[_TrapType, bool]
def __init__(
self,
prec: int | None = ...,
rounding: str | None = ...,
Emin: int | None = ...,
Emax: int | None = ...,
capitals: int | None = ...,
clamp: int | None = ...,
flags: None | dict[_TrapType, bool] | Container[_TrapType] = ...,
traps: None | dict[_TrapType, bool] | Container[_TrapType] = ...,
_ignored_flags: list[_TrapType] | None = ...,
) -> None: ...
def __reduce__(self) -> tuple[type[Self], tuple[Any, ...]]: ...
def clear_flags(self) -> None: ...
def clear_traps(self) -> None: ...
def copy(self) -> Context: ...
def __copy__(self) -> Context: ...
# see https://github.com/python/cpython/issues/94107
__hash__: ClassVar[None] # type: ignore[assignment]
def Etiny(self) -> int: ...
def Etop(self) -> int: ...
def create_decimal(self, num: _DecimalNew = "0", /) -> Decimal: ...
def create_decimal_from_float(self, f: float, /) -> Decimal: ...
def abs(self, x: _Decimal, /) -> Decimal: ...
def add(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def canonical(self, x: Decimal, /) -> Decimal: ...
def compare(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def compare_signal(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def compare_total(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def compare_total_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def copy_abs(self, x: _Decimal, /) -> Decimal: ...
def copy_decimal(self, x: _Decimal, /) -> Decimal: ...
def copy_negate(self, x: _Decimal, /) -> Decimal: ...
def copy_sign(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def divide(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def divide_int(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def divmod(self, x: _Decimal, y: _Decimal, /) -> tuple[Decimal, Decimal]: ...
def exp(self, x: _Decimal, /) -> Decimal: ...
def fma(self, x: _Decimal, y: _Decimal, z: _Decimal, /) -> Decimal: ...
def is_canonical(self, x: _Decimal, /) -> bool: ...
def is_finite(self, x: _Decimal, /) -> bool: ...
def is_infinite(self, x: _Decimal, /) -> bool: ...
def is_nan(self, x: _Decimal, /) -> bool: ...
def is_normal(self, x: _Decimal, /) -> bool: ...
def is_qnan(self, x: _Decimal, /) -> bool: ...
def is_signed(self, x: _Decimal, /) -> bool: ...
def is_snan(self, x: _Decimal, /) -> bool: ...
def is_subnormal(self, x: _Decimal, /) -> bool: ...
def is_zero(self, x: _Decimal, /) -> bool: ...
def ln(self, x: _Decimal, /) -> Decimal: ...
def log10(self, x: _Decimal, /) -> Decimal: ...
def logb(self, x: _Decimal, /) -> Decimal: ...
def logical_and(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def logical_invert(self, x: _Decimal, /) -> Decimal: ...
def logical_or(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def logical_xor(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def max(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def max_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def min(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def min_mag(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def minus(self, x: _Decimal, /) -> Decimal: ...
def multiply(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def next_minus(self, x: _Decimal, /) -> Decimal: ...
def next_plus(self, x: _Decimal, /) -> Decimal: ...
def next_toward(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def normalize(self, x: _Decimal, /) -> Decimal: ...
def number_class(self, x: _Decimal, /) -> str: ...
def plus(self, x: _Decimal, /) -> Decimal: ...
def power(self, a: _Decimal, b: _Decimal, modulo: _Decimal | None = None) -> Decimal: ...
def quantize(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def radix(self) -> Decimal: ...
def remainder(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def remainder_near(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def rotate(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def same_quantum(self, x: _Decimal, y: _Decimal, /) -> bool: ...
def scaleb(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def shift(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def sqrt(self, x: _Decimal, /) -> Decimal: ...
def subtract(self, x: _Decimal, y: _Decimal, /) -> Decimal: ...
def to_eng_string(self, x: _Decimal, /) -> str: ...
def to_sci_string(self, x: _Decimal, /) -> str: ...
def to_integral_exact(self, x: _Decimal, /) -> Decimal: ...
def to_integral_value(self, x: _Decimal, /) -> Decimal: ...
def to_integral(self, x: _Decimal, /) -> Decimal: ...
DefaultContext: Context
BasicContext: Context
ExtendedContext: Context

View File

@@ -0,0 +1,195 @@
import builtins
import codecs
import sys
from _typeshed import FileDescriptorOrPath, MaybeNone, ReadableBuffer, WriteableBuffer
from collections.abc import Callable, Iterable, Iterator
from io import BufferedIOBase, RawIOBase, TextIOBase, UnsupportedOperation as UnsupportedOperation
from os import _Opener
from types import TracebackType
from typing import IO, Any, BinaryIO, Final, Generic, Literal, Protocol, TextIO, TypeVar, overload, type_check_only
from typing_extensions import Self
_T = TypeVar("_T")
DEFAULT_BUFFER_SIZE: Final = 8192
open = builtins.open
def open_code(path: str) -> IO[bytes]: ...
BlockingIOError = builtins.BlockingIOError
class _IOBase:
def __iter__(self) -> Iterator[bytes]: ...
def __next__(self) -> bytes: ...
def __enter__(self) -> Self: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
) -> None: ...
def close(self) -> None: ...
def fileno(self) -> int: ...
def flush(self) -> None: ...
def isatty(self) -> bool: ...
def readable(self) -> bool: ...
read: Callable[..., Any]
def readlines(self, hint: int = -1, /) -> list[bytes]: ...
def seek(self, offset: int, whence: int = ..., /) -> int: ...
def seekable(self) -> bool: ...
def tell(self) -> int: ...
def truncate(self, size: int | None = ..., /) -> int: ...
def writable(self) -> bool: ...
write: Callable[..., Any]
def writelines(self, lines: Iterable[ReadableBuffer], /) -> None: ...
def readline(self, size: int | None = -1, /) -> bytes: ...
def __del__(self) -> None: ...
@property
def closed(self) -> bool: ...
def _checkClosed(self) -> None: ... # undocumented
class _RawIOBase(_IOBase):
def readall(self) -> bytes: ...
# The following methods can return None if the file is in non-blocking mode
# and no data is available.
def readinto(self, buffer: WriteableBuffer, /) -> int | MaybeNone: ...
def write(self, b: ReadableBuffer, /) -> int | MaybeNone: ...
def read(self, size: int = -1, /) -> bytes | MaybeNone: ...
class _BufferedIOBase(_IOBase):
def detach(self) -> RawIOBase: ...
def readinto(self, buffer: WriteableBuffer, /) -> int: ...
def write(self, buffer: ReadableBuffer, /) -> int: ...
def readinto1(self, buffer: WriteableBuffer, /) -> int: ...
def read(self, size: int | None = ..., /) -> bytes: ...
def read1(self, size: int = ..., /) -> bytes: ...
class FileIO(RawIOBase, _RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
mode: str
# The type of "name" equals the argument passed in to the constructor,
# but that can make FileIO incompatible with other I/O types that assume
# "name" is a str. In the future, making FileIO generic might help.
name: Any
def __init__(
self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ...
) -> None: ...
@property
def closefd(self) -> bool: ...
class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ...
# BytesIO does not contain a "name" field. This workaround is necessary
# to allow BytesIO sub-classes to add this field, as it is defined
# as a read-only property on IO[].
name: Any
def getvalue(self) -> bytes: ...
def getbuffer(self) -> memoryview: ...
def read1(self, size: int | None = -1, /) -> bytes: ...
class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
raw: RawIOBase
def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ...
def peek(self, size: int = 0, /) -> bytes: ...
class BufferedWriter(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
raw: RawIOBase
def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ...
def write(self, buffer: ReadableBuffer, /) -> int: ...
class BufferedRandom(BufferedReader, BufferedWriter, BufferedIOBase, _BufferedIOBase): # type: ignore[misc] # incompatible definitions of methods in the base classes
def seek(self, target: int, whence: int = 0, /) -> int: ... # stubtest needs this
class BufferedRWPair(BufferedIOBase, _BufferedIOBase):
def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = ...) -> None: ...
def peek(self, size: int = ..., /) -> bytes: ...
class _TextIOBase(_IOBase):
encoding: str
errors: str | None
newlines: str | tuple[str, ...] | None
def __iter__(self) -> Iterator[str]: ... # type: ignore[override]
def __next__(self) -> str: ... # type: ignore[override]
def detach(self) -> BinaryIO: ...
def write(self, s: str, /) -> int: ...
def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override]
def readline(self, size: int = ..., /) -> str: ... # type: ignore[override]
def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override]
def read(self, size: int | None = ..., /) -> str: ...
@type_check_only
class _WrappedBuffer(Protocol):
# "name" is wrapped by TextIOWrapper. Its type is inconsistent between
# the various I/O types, see the comments on TextIOWrapper.name and
# TextIO.name.
@property
def name(self) -> Any: ...
@property
def closed(self) -> bool: ...
def read(self, size: int = ..., /) -> ReadableBuffer: ...
# Optional: def read1(self, size: int, /) -> ReadableBuffer: ...
def write(self, b: bytes, /) -> object: ...
def flush(self) -> object: ...
def close(self) -> object: ...
def seekable(self) -> bool: ...
def readable(self) -> bool: ...
def writable(self) -> bool: ...
def truncate(self, size: int, /) -> int: ...
def fileno(self) -> int: ...
def isatty(self) -> bool: ...
# Optional: Only needs to be present if seekable() returns True.
# def seek(self, offset: Literal[0], whence: Literal[2]) -> int: ...
# def tell(self) -> int: ...
_BufferT_co = TypeVar("_BufferT_co", bound=_WrappedBuffer, default=_WrappedBuffer, covariant=True)
class TextIOWrapper(TextIOBase, _TextIOBase, TextIO, Generic[_BufferT_co]): # type: ignore[misc] # incompatible definitions of write in the base classes
def __init__(
self,
buffer: _BufferT_co,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
line_buffering: bool = False,
write_through: bool = False,
) -> None: ...
# Equals the "buffer" argument passed in to the constructor.
@property
def buffer(self) -> _BufferT_co: ... # type: ignore[override]
@property
def line_buffering(self) -> bool: ...
@property
def write_through(self) -> bool: ...
def reconfigure(
self,
*,
encoding: str | None = None,
errors: str | None = None,
newline: str | None = None,
line_buffering: bool | None = None,
write_through: bool | None = None,
) -> None: ...
def readline(self, size: int = -1, /) -> str: ... # type: ignore[override]
# Equals the "buffer" argument passed in to the constructor.
def detach(self) -> _BufferT_co: ... # type: ignore[override]
# TextIOWrapper's version of seek only supports a limited subset of
# operations.
def seek(self, cookie: int, whence: int = 0, /) -> int: ...
class StringIO(TextIOWrapper, TextIOBase, _TextIOBase): # type: ignore[misc] # incompatible definitions of write in the base classes
def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ...
# StringIO does not contain a "name" field. This workaround is necessary
# to allow StringIO sub-classes to add this field, as it is defined
# as a read-only property on IO[].
name: Any
def getvalue(self) -> str: ...
class IncrementalNewlineDecoder(codecs.IncrementalDecoder):
def __init__(self, decoder: codecs.IncrementalDecoder | None, translate: bool, errors: str = ...) -> None: ...
def decode(self, input: ReadableBuffer | str, final: bool = False) -> str: ...
@property
def newlines(self) -> str | tuple[str, ...] | None: ...
def setstate(self, state: tuple[bytes, int], /) -> None: ...
if sys.version_info >= (3, 10):
@overload
def text_encoding(encoding: None, stacklevel: int = 2, /) -> Literal["locale", "utf-8"]: ...
@overload
def text_encoding(encoding: _T, stacklevel: int = 2, /) -> _T: ...

View File

@@ -1,18 +1,16 @@
import sys
from _typeshed import SupportsGetItem
from collections.abc import Callable, Container, Iterable, MutableMapping, MutableSequence, Sequence
from typing import Any, AnyStr, Generic, Protocol, SupportsAbs, SupportsIndex, TypeVar, final, overload
from typing_extensions import ParamSpec, TypeAlias, TypeIs, TypeVarTuple, Unpack
from operator import attrgetter as attrgetter, itemgetter as itemgetter, methodcaller as methodcaller
from typing import Any, AnyStr, Protocol, SupportsAbs, SupportsIndex, TypeVar, overload
from typing_extensions import ParamSpec, TypeAlias, TypeIs
_R = TypeVar("_R")
_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
_T1 = TypeVar("_T1")
_T2 = TypeVar("_T2")
_K = TypeVar("_K")
_V = TypeVar("_V")
_P = ParamSpec("_P")
_Ts = TypeVarTuple("_Ts")
# The following protocols return "Any" instead of bool, since the comparison
# operators can be overloaded to return an arbitrary object. For example,
@@ -92,40 +90,6 @@ def setitem(a: MutableSequence[_T], b: slice, c: Sequence[_T], /) -> None: ...
@overload
def setitem(a: MutableMapping[_K, _V], b: _K, c: _V, /) -> None: ...
def length_hint(obj: object, default: int = 0, /) -> int: ...
@final
class attrgetter(Generic[_T_co]):
@overload
def __new__(cls, attr: str, /) -> attrgetter[Any]: ...
@overload
def __new__(cls, attr: str, attr2: str, /) -> attrgetter[tuple[Any, Any]]: ...
@overload
def __new__(cls, attr: str, attr2: str, attr3: str, /) -> attrgetter[tuple[Any, Any, Any]]: ...
@overload
def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[Any, Any, Any, Any]]: ...
@overload
def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[Any, ...]]: ...
def __call__(self, obj: Any, /) -> _T_co: ...
@final
class itemgetter(Generic[_T_co]):
@overload
def __new__(cls, item: _T, /) -> itemgetter[_T]: ...
@overload
def __new__(cls, item1: _T1, item2: _T2, /, *items: Unpack[_Ts]) -> itemgetter[tuple[_T1, _T2, Unpack[_Ts]]]: ...
# __key: _KT_contra in SupportsGetItem seems to be causing variance issues, ie:
# TypeVar "_KT_contra@SupportsGetItem" is contravariant
# "tuple[int, int]" is incompatible with protocol "SupportsIndex"
# preventing [_T_co, ...] instead of [Any, ...]
#
# A suspected mypy issue prevents using [..., _T] instead of [..., Any] here.
# https://github.com/python/mypy/issues/14032
def __call__(self, obj: SupportsGetItem[Any, Any]) -> Any: ...
@final
class methodcaller:
def __init__(self, name: str, /, *args: Any, **kwargs: Any) -> None: ...
def __call__(self, obj: Any) -> Any: ...
def iadd(a: Any, b: Any, /) -> Any: ...
def iand(a: Any, b: Any, /) -> Any: ...
def iconcat(a: Any, b: Any, /) -> Any: ...

View File

@@ -1,6 +1,7 @@
import sys
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
@@ -666,18 +667,6 @@ if sys.platform != "win32":
if sys.platform != "win32" and sys.platform != "darwin":
IPX_TYPE: int
# ===== Exceptions =====
error = OSError
class herror(error): ...
class gaierror(error): ...
if sys.version_info >= (3, 10):
timeout = TimeoutError
else:
class timeout(error): ...
# ===== Classes =====
class socket:
@@ -687,8 +676,9 @@ class socket:
def type(self) -> int: ...
@property
def proto(self) -> int: ...
# F811: "Redefinition of unused `timeout`"
@property
def timeout(self) -> float | None: ...
def timeout(self) -> float | None: ... # noqa: F811
if sys.platform == "win32":
def __init__(
self, family: int = ..., type: int = ..., proto: int = ..., fileno: SupportsIndex | bytes | None = ...
@@ -788,7 +778,9 @@ def inet_ntoa(packed_ip: ReadableBuffer, /) -> str: ...
def inet_pton(address_family: int, ip_string: str, /) -> bytes: ...
def inet_ntop(address_family: int, packed_ip: ReadableBuffer, /) -> str: ...
def getdefaulttimeout() -> float | None: ...
def setdefaulttimeout(timeout: float | None, /) -> None: ...
# F811: "Redefinition of unused `timeout`"
def setdefaulttimeout(timeout: float | None, /) -> None: ... # noqa: F811
if sys.platform != "win32":
def sethostname(name: str, /) -> None: ...

View File

@@ -0,0 +1,312 @@
import sys
from _typeshed import ReadableBuffer, StrOrBytesPath
from collections.abc import Callable
from sqlite3 import (
Connection as Connection,
Cursor as Cursor,
DatabaseError as DatabaseError,
DataError as DataError,
Error as Error,
IntegrityError as IntegrityError,
InterfaceError as InterfaceError,
InternalError as InternalError,
NotSupportedError as NotSupportedError,
OperationalError as OperationalError,
PrepareProtocol as PrepareProtocol,
ProgrammingError as ProgrammingError,
Row as Row,
Warning as Warning,
)
from typing import Any, Final, Literal, TypeVar, overload
from typing_extensions import TypeAlias
if sys.version_info >= (3, 11):
from sqlite3 import Blob as Blob
_T = TypeVar("_T")
_ConnectionT = TypeVar("_ConnectionT", bound=Connection)
_SqliteData: TypeAlias = str | ReadableBuffer | int | float | None
_Adapter: TypeAlias = Callable[[_T], _SqliteData]
_Converter: TypeAlias = Callable[[bytes], Any]
PARSE_COLNAMES: Final[int]
PARSE_DECLTYPES: Final[int]
SQLITE_ALTER_TABLE: Final[int]
SQLITE_ANALYZE: Final[int]
SQLITE_ATTACH: Final[int]
SQLITE_CREATE_INDEX: Final[int]
SQLITE_CREATE_TABLE: Final[int]
SQLITE_CREATE_TEMP_INDEX: Final[int]
SQLITE_CREATE_TEMP_TABLE: Final[int]
SQLITE_CREATE_TEMP_TRIGGER: Final[int]
SQLITE_CREATE_TEMP_VIEW: Final[int]
SQLITE_CREATE_TRIGGER: Final[int]
SQLITE_CREATE_VIEW: Final[int]
SQLITE_CREATE_VTABLE: Final[int]
SQLITE_DELETE: Final[int]
SQLITE_DENY: Final[int]
SQLITE_DETACH: Final[int]
SQLITE_DONE: Final[int]
SQLITE_DROP_INDEX: Final[int]
SQLITE_DROP_TABLE: Final[int]
SQLITE_DROP_TEMP_INDEX: Final[int]
SQLITE_DROP_TEMP_TABLE: Final[int]
SQLITE_DROP_TEMP_TRIGGER: Final[int]
SQLITE_DROP_TEMP_VIEW: Final[int]
SQLITE_DROP_TRIGGER: Final[int]
SQLITE_DROP_VIEW: Final[int]
SQLITE_DROP_VTABLE: Final[int]
SQLITE_FUNCTION: Final[int]
SQLITE_IGNORE: Final[int]
SQLITE_INSERT: Final[int]
SQLITE_OK: Final[int]
SQLITE_PRAGMA: Final[int]
SQLITE_READ: Final[int]
SQLITE_RECURSIVE: Final[int]
SQLITE_REINDEX: Final[int]
SQLITE_SAVEPOINT: Final[int]
SQLITE_SELECT: Final[int]
SQLITE_TRANSACTION: Final[int]
SQLITE_UPDATE: Final[int]
adapters: dict[tuple[type[Any], type[Any]], _Adapter[Any]]
converters: dict[str, _Converter]
sqlite_version: str
if sys.version_info < (3, 12):
version: str
if sys.version_info >= (3, 12):
LEGACY_TRANSACTION_CONTROL: Final[int]
SQLITE_DBCONFIG_DEFENSIVE: Final[int]
SQLITE_DBCONFIG_DQS_DDL: Final[int]
SQLITE_DBCONFIG_DQS_DML: Final[int]
SQLITE_DBCONFIG_ENABLE_FKEY: Final[int]
SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: Final[int]
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION: Final[int]
SQLITE_DBCONFIG_ENABLE_QPSG: Final[int]
SQLITE_DBCONFIG_ENABLE_TRIGGER: Final[int]
SQLITE_DBCONFIG_ENABLE_VIEW: Final[int]
SQLITE_DBCONFIG_LEGACY_ALTER_TABLE: Final[int]
SQLITE_DBCONFIG_LEGACY_FILE_FORMAT: Final[int]
SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: Final[int]
SQLITE_DBCONFIG_RESET_DATABASE: Final[int]
SQLITE_DBCONFIG_TRIGGER_EQP: Final[int]
SQLITE_DBCONFIG_TRUSTED_SCHEMA: Final[int]
SQLITE_DBCONFIG_WRITABLE_SCHEMA: Final[int]
if sys.version_info >= (3, 11):
SQLITE_ABORT: Final[int]
SQLITE_ABORT_ROLLBACK: Final[int]
SQLITE_AUTH: Final[int]
SQLITE_AUTH_USER: Final[int]
SQLITE_BUSY: Final[int]
SQLITE_BUSY_RECOVERY: Final[int]
SQLITE_BUSY_SNAPSHOT: Final[int]
SQLITE_BUSY_TIMEOUT: Final[int]
SQLITE_CANTOPEN: Final[int]
SQLITE_CANTOPEN_CONVPATH: Final[int]
SQLITE_CANTOPEN_DIRTYWAL: Final[int]
SQLITE_CANTOPEN_FULLPATH: Final[int]
SQLITE_CANTOPEN_ISDIR: Final[int]
SQLITE_CANTOPEN_NOTEMPDIR: Final[int]
SQLITE_CANTOPEN_SYMLINK: Final[int]
SQLITE_CONSTRAINT: Final[int]
SQLITE_CONSTRAINT_CHECK: Final[int]
SQLITE_CONSTRAINT_COMMITHOOK: Final[int]
SQLITE_CONSTRAINT_FOREIGNKEY: Final[int]
SQLITE_CONSTRAINT_FUNCTION: Final[int]
SQLITE_CONSTRAINT_NOTNULL: Final[int]
SQLITE_CONSTRAINT_PINNED: Final[int]
SQLITE_CONSTRAINT_PRIMARYKEY: Final[int]
SQLITE_CONSTRAINT_ROWID: Final[int]
SQLITE_CONSTRAINT_TRIGGER: Final[int]
SQLITE_CONSTRAINT_UNIQUE: Final[int]
SQLITE_CONSTRAINT_VTAB: Final[int]
SQLITE_CORRUPT: Final[int]
SQLITE_CORRUPT_INDEX: Final[int]
SQLITE_CORRUPT_SEQUENCE: Final[int]
SQLITE_CORRUPT_VTAB: Final[int]
SQLITE_EMPTY: Final[int]
SQLITE_ERROR: Final[int]
SQLITE_ERROR_MISSING_COLLSEQ: Final[int]
SQLITE_ERROR_RETRY: Final[int]
SQLITE_ERROR_SNAPSHOT: Final[int]
SQLITE_FORMAT: Final[int]
SQLITE_FULL: Final[int]
SQLITE_INTERNAL: Final[int]
SQLITE_INTERRUPT: Final[int]
SQLITE_IOERR: Final[int]
SQLITE_IOERR_ACCESS: Final[int]
SQLITE_IOERR_AUTH: Final[int]
SQLITE_IOERR_BEGIN_ATOMIC: Final[int]
SQLITE_IOERR_BLOCKED: Final[int]
SQLITE_IOERR_CHECKRESERVEDLOCK: Final[int]
SQLITE_IOERR_CLOSE: Final[int]
SQLITE_IOERR_COMMIT_ATOMIC: Final[int]
SQLITE_IOERR_CONVPATH: Final[int]
SQLITE_IOERR_CORRUPTFS: Final[int]
SQLITE_IOERR_DATA: Final[int]
SQLITE_IOERR_DELETE: Final[int]
SQLITE_IOERR_DELETE_NOENT: Final[int]
SQLITE_IOERR_DIR_CLOSE: Final[int]
SQLITE_IOERR_DIR_FSYNC: Final[int]
SQLITE_IOERR_FSTAT: Final[int]
SQLITE_IOERR_FSYNC: Final[int]
SQLITE_IOERR_GETTEMPPATH: Final[int]
SQLITE_IOERR_LOCK: Final[int]
SQLITE_IOERR_MMAP: Final[int]
SQLITE_IOERR_NOMEM: Final[int]
SQLITE_IOERR_RDLOCK: Final[int]
SQLITE_IOERR_READ: Final[int]
SQLITE_IOERR_ROLLBACK_ATOMIC: Final[int]
SQLITE_IOERR_SEEK: Final[int]
SQLITE_IOERR_SHMLOCK: Final[int]
SQLITE_IOERR_SHMMAP: Final[int]
SQLITE_IOERR_SHMOPEN: Final[int]
SQLITE_IOERR_SHMSIZE: Final[int]
SQLITE_IOERR_SHORT_READ: Final[int]
SQLITE_IOERR_TRUNCATE: Final[int]
SQLITE_IOERR_UNLOCK: Final[int]
SQLITE_IOERR_VNODE: Final[int]
SQLITE_IOERR_WRITE: Final[int]
SQLITE_LIMIT_ATTACHED: Final[int]
SQLITE_LIMIT_COLUMN: Final[int]
SQLITE_LIMIT_COMPOUND_SELECT: Final[int]
SQLITE_LIMIT_EXPR_DEPTH: Final[int]
SQLITE_LIMIT_FUNCTION_ARG: Final[int]
SQLITE_LIMIT_LENGTH: Final[int]
SQLITE_LIMIT_LIKE_PATTERN_LENGTH: Final[int]
SQLITE_LIMIT_SQL_LENGTH: Final[int]
SQLITE_LIMIT_TRIGGER_DEPTH: Final[int]
SQLITE_LIMIT_VARIABLE_NUMBER: Final[int]
SQLITE_LIMIT_VDBE_OP: Final[int]
SQLITE_LIMIT_WORKER_THREADS: Final[int]
SQLITE_LOCKED: Final[int]
SQLITE_LOCKED_SHAREDCACHE: Final[int]
SQLITE_LOCKED_VTAB: Final[int]
SQLITE_MISMATCH: Final[int]
SQLITE_MISUSE: Final[int]
SQLITE_NOLFS: Final[int]
SQLITE_NOMEM: Final[int]
SQLITE_NOTADB: Final[int]
SQLITE_NOTFOUND: Final[int]
SQLITE_NOTICE: Final[int]
SQLITE_NOTICE_RECOVER_ROLLBACK: Final[int]
SQLITE_NOTICE_RECOVER_WAL: Final[int]
SQLITE_OK_LOAD_PERMANENTLY: Final[int]
SQLITE_OK_SYMLINK: Final[int]
SQLITE_PERM: Final[int]
SQLITE_PROTOCOL: Final[int]
SQLITE_RANGE: Final[int]
SQLITE_READONLY: Final[int]
SQLITE_READONLY_CANTINIT: Final[int]
SQLITE_READONLY_CANTLOCK: Final[int]
SQLITE_READONLY_DBMOVED: Final[int]
SQLITE_READONLY_DIRECTORY: Final[int]
SQLITE_READONLY_RECOVERY: Final[int]
SQLITE_READONLY_ROLLBACK: Final[int]
SQLITE_ROW: Final[int]
SQLITE_SCHEMA: Final[int]
SQLITE_TOOBIG: Final[int]
SQLITE_WARNING: Final[int]
SQLITE_WARNING_AUTOINDEX: Final[int]
threadsafety: Final[int]
# Can take or return anything depending on what's in the registry.
@overload
def adapt(obj: Any, proto: Any, /) -> Any: ...
@overload
def adapt(obj: Any, proto: Any, alt: _T, /) -> Any | _T: ...
def complete_statement(statement: str) -> bool: ...
if sys.version_info >= (3, 12):
@overload
def connect(
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
check_same_thread: bool = True,
cached_statements: int = 128,
uri: bool = False,
*,
autocommit: bool = ...,
) -> Connection: ...
@overload
def connect(
database: StrOrBytesPath,
timeout: float,
detect_types: int,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
check_same_thread: bool,
factory: type[_ConnectionT],
cached_statements: int = 128,
uri: bool = False,
*,
autocommit: bool = ...,
) -> _ConnectionT: ...
@overload
def connect(
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
check_same_thread: bool = True,
*,
factory: type[_ConnectionT],
cached_statements: int = 128,
uri: bool = False,
autocommit: bool = ...,
) -> _ConnectionT: ...
else:
@overload
def connect(
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
check_same_thread: bool = True,
cached_statements: int = 128,
uri: bool = False,
) -> Connection: ...
@overload
def connect(
database: StrOrBytesPath,
timeout: float,
detect_types: int,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None,
check_same_thread: bool,
factory: type[_ConnectionT],
cached_statements: int = 128,
uri: bool = False,
) -> _ConnectionT: ...
@overload
def connect(
database: StrOrBytesPath,
timeout: float = 5.0,
detect_types: int = 0,
isolation_level: Literal["DEFERRED", "EXCLUSIVE", "IMMEDIATE"] | None = "DEFERRED",
check_same_thread: bool = True,
*,
factory: type[_ConnectionT],
cached_statements: int = 128,
uri: bool = False,
) -> _ConnectionT: ...
def enable_callback_tracebacks(enable: bool, /) -> None: ...
if sys.version_info < (3, 12):
# takes a pos-or-keyword argument because there is a C wrapper
def enable_shared_cache(do_enable: int) -> None: ...
if sys.version_info >= (3, 10):
def register_adapter(type: type[_T], adapter: _Adapter[_T], /) -> None: ...
def register_converter(typename: str, converter: _Converter, /) -> None: ...
else:
def register_adapter(type: type[_T], caster: _Adapter[_T], /) -> None: ...
def register_converter(name: str, converter: _Converter, /) -> None: ...
if sys.version_info < (3, 10):
OptimizedUnicode = str

View File

@@ -0,0 +1,292 @@
import sys
from _typeshed import ReadableBuffer, StrOrBytesPath
from collections.abc import Callable
from ssl import (
SSLCertVerificationError as SSLCertVerificationError,
SSLContext,
SSLEOFError as SSLEOFError,
SSLError as SSLError,
SSLObject,
SSLSyscallError as SSLSyscallError,
SSLWantReadError as SSLWantReadError,
SSLWantWriteError as SSLWantWriteError,
SSLZeroReturnError as SSLZeroReturnError,
)
from typing import Any, Literal, TypedDict, final, overload
from typing_extensions import NotRequired, Self, TypeAlias
_PasswordType: TypeAlias = Callable[[], str | bytes | bytearray] | str | bytes | bytearray
_PCTRTT: TypeAlias = tuple[tuple[str, str], ...]
_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...]
_PeerCertRetDictType: TypeAlias = dict[str, str | _PCTRTTT | _PCTRTT]
class _Cipher(TypedDict):
aead: bool
alg_bits: int
auth: str
description: str
digest: str | None
id: int
kea: str
name: str
protocol: str
strength_bits: int
symmetric: str
class _CertInfo(TypedDict):
subject: tuple[tuple[tuple[str, str], ...], ...]
issuer: tuple[tuple[tuple[str, str], ...], ...]
version: int
serialNumber: str
notBefore: str
notAfter: str
subjectAltName: NotRequired[tuple[tuple[str, str], ...] | None]
OCSP: NotRequired[tuple[str, ...] | None]
caIssuers: NotRequired[tuple[str, ...] | None]
crlDistributionPoints: NotRequired[tuple[str, ...] | None]
def RAND_add(string: str | ReadableBuffer, entropy: float, /) -> None: ...
def RAND_bytes(n: int, /) -> bytes: ...
if sys.version_info < (3, 12):
def RAND_pseudo_bytes(n: int, /) -> tuple[bytes, bool]: ...
if sys.version_info < (3, 10):
def RAND_egd(path: str) -> None: ...
def RAND_status() -> bool: ...
def get_default_verify_paths() -> tuple[str, str, str, str]: ...
if sys.platform == "win32":
_EnumRetType: TypeAlias = list[tuple[bytes, str, set[str] | bool]]
def enum_certificates(store_name: str) -> _EnumRetType: ...
def enum_crls(store_name: str) -> _EnumRetType: ...
def txt2obj(txt: str, name: bool = False) -> tuple[int, str, str, str]: ...
def nid2obj(nid: int, /) -> tuple[int, str, str, str]: ...
class _SSLContext:
check_hostname: bool
keylog_filename: str | None
maximum_version: int
minimum_version: int
num_tickets: int
options: int
post_handshake_auth: bool
protocol: int
if sys.version_info >= (3, 10):
security_level: int
sni_callback: Callable[[SSLObject, str, SSLContext], None | int] | None
verify_flags: int
verify_mode: int
def __new__(cls, protocol: int, /) -> Self: ...
def cert_store_stats(self) -> dict[str, int]: ...
@overload
def get_ca_certs(self, binary_form: Literal[False] = False) -> list[_PeerCertRetDictType]: ...
@overload
def get_ca_certs(self, binary_form: Literal[True]) -> list[bytes]: ...
@overload
def get_ca_certs(self, binary_form: bool = False) -> Any: ...
def get_ciphers(self) -> list[_Cipher]: ...
def load_cert_chain(
self, certfile: StrOrBytesPath, keyfile: StrOrBytesPath | None = None, password: _PasswordType | None = None
) -> None: ...
def load_dh_params(self, path: str, /) -> None: ...
def load_verify_locations(
self,
cafile: StrOrBytesPath | None = None,
capath: StrOrBytesPath | None = None,
cadata: str | ReadableBuffer | None = None,
) -> None: ...
def session_stats(self) -> dict[str, int]: ...
def set_ciphers(self, cipherlist: str, /) -> None: ...
def set_default_verify_paths(self) -> None: ...
def set_ecdh_curve(self, name: str, /) -> None: ...
if sys.version_info >= (3, 13):
def set_psk_client_callback(self, callback: Callable[[str | None], tuple[str | None, bytes]] | None) -> None: ...
def set_psk_server_callback(
self, callback: Callable[[str | None], tuple[str | None, bytes]] | None, identity_hint: str | None = None
) -> None: ...
@final
class MemoryBIO:
eof: bool
pending: int
def __new__(self) -> Self: ...
def read(self, size: int = -1, /) -> bytes: ...
def write(self, b: ReadableBuffer, /) -> int: ...
def write_eof(self) -> None: ...
@final
class SSLSession:
@property
def has_ticket(self) -> bool: ...
@property
def id(self) -> bytes: ...
@property
def ticket_lifetime_hint(self) -> int: ...
@property
def time(self) -> int: ...
@property
def timeout(self) -> int: ...
# _ssl.Certificate is weird: it can't be instantiated or subclassed.
# Instances can only be created via methods of the private _ssl._SSLSocket class,
# for which the relevant method signatures are:
#
# class _SSLSocket:
# def get_unverified_chain(self) -> list[Certificate] | None: ...
# def get_verified_chain(self) -> list[Certificate] | None: ...
#
# You can find a _ssl._SSLSocket object as the _sslobj attribute of a ssl.SSLSocket object
if sys.version_info >= (3, 10):
@final
class Certificate:
def get_info(self) -> _CertInfo: ...
@overload
def public_bytes(self) -> str: ...
@overload
def public_bytes(self, format: Literal[1] = 1, /) -> str: ... # ENCODING_PEM
@overload
def public_bytes(self, format: Literal[2], /) -> bytes: ... # ENCODING_DER
@overload
def public_bytes(self, format: int, /) -> str | bytes: ...
if sys.version_info < (3, 12):
err_codes_to_names: dict[tuple[int, int], str]
err_names_to_codes: dict[str, tuple[int, int]]
lib_codes_to_names: dict[int, str]
_DEFAULT_CIPHERS: str
# SSL error numbers
SSL_ERROR_ZERO_RETURN: int
SSL_ERROR_WANT_READ: int
SSL_ERROR_WANT_WRITE: int
SSL_ERROR_WANT_X509_LOOKUP: int
SSL_ERROR_SYSCALL: int
SSL_ERROR_SSL: int
SSL_ERROR_WANT_CONNECT: int
SSL_ERROR_EOF: int
SSL_ERROR_INVALID_ERROR_CODE: int
# verify modes
CERT_NONE: int
CERT_OPTIONAL: int
CERT_REQUIRED: int
# verify flags
VERIFY_DEFAULT: int
VERIFY_CRL_CHECK_LEAF: int
VERIFY_CRL_CHECK_CHAIN: int
VERIFY_X509_STRICT: int
VERIFY_X509_TRUSTED_FIRST: int
if sys.version_info >= (3, 10):
VERIFY_ALLOW_PROXY_CERTS: int
VERIFY_X509_PARTIAL_CHAIN: int
# alert descriptions
ALERT_DESCRIPTION_CLOSE_NOTIFY: int
ALERT_DESCRIPTION_UNEXPECTED_MESSAGE: int
ALERT_DESCRIPTION_BAD_RECORD_MAC: int
ALERT_DESCRIPTION_RECORD_OVERFLOW: int
ALERT_DESCRIPTION_DECOMPRESSION_FAILURE: int
ALERT_DESCRIPTION_HANDSHAKE_FAILURE: int
ALERT_DESCRIPTION_BAD_CERTIFICATE: int
ALERT_DESCRIPTION_UNSUPPORTED_CERTIFICATE: int
ALERT_DESCRIPTION_CERTIFICATE_REVOKED: int
ALERT_DESCRIPTION_CERTIFICATE_EXPIRED: int
ALERT_DESCRIPTION_CERTIFICATE_UNKNOWN: int
ALERT_DESCRIPTION_ILLEGAL_PARAMETER: int
ALERT_DESCRIPTION_UNKNOWN_CA: int
ALERT_DESCRIPTION_ACCESS_DENIED: int
ALERT_DESCRIPTION_DECODE_ERROR: int
ALERT_DESCRIPTION_DECRYPT_ERROR: int
ALERT_DESCRIPTION_PROTOCOL_VERSION: int
ALERT_DESCRIPTION_INSUFFICIENT_SECURITY: int
ALERT_DESCRIPTION_INTERNAL_ERROR: int
ALERT_DESCRIPTION_USER_CANCELLED: int
ALERT_DESCRIPTION_NO_RENEGOTIATION: int
ALERT_DESCRIPTION_UNSUPPORTED_EXTENSION: int
ALERT_DESCRIPTION_CERTIFICATE_UNOBTAINABLE: int
ALERT_DESCRIPTION_UNRECOGNIZED_NAME: int
ALERT_DESCRIPTION_BAD_CERTIFICATE_STATUS_RESPONSE: int
ALERT_DESCRIPTION_BAD_CERTIFICATE_HASH_VALUE: int
ALERT_DESCRIPTION_UNKNOWN_PSK_IDENTITY: int
# protocol versions
PROTOCOL_SSLv23: int
PROTOCOL_TLS: int
PROTOCOL_TLS_CLIENT: int
PROTOCOL_TLS_SERVER: int
PROTOCOL_TLSv1: int
PROTOCOL_TLSv1_1: int
PROTOCOL_TLSv1_2: int
# protocol options
OP_ALL: int
OP_NO_SSLv2: int
OP_NO_SSLv3: int
OP_NO_TLSv1: int
OP_NO_TLSv1_1: int
OP_NO_TLSv1_2: int
OP_NO_TLSv1_3: int
OP_CIPHER_SERVER_PREFERENCE: int
OP_SINGLE_DH_USE: int
OP_NO_TICKET: int
OP_SINGLE_ECDH_USE: int
OP_NO_COMPRESSION: int
OP_ENABLE_MIDDLEBOX_COMPAT: int
OP_NO_RENEGOTIATION: int
if sys.version_info >= (3, 11):
OP_IGNORE_UNEXPECTED_EOF: int
elif sys.version_info >= (3, 8) and sys.platform == "linux":
OP_IGNORE_UNEXPECTED_EOF: int
if sys.version_info >= (3, 12):
OP_LEGACY_SERVER_CONNECT: int
OP_ENABLE_KTLS: int
# host flags
HOSTFLAG_ALWAYS_CHECK_SUBJECT: int
HOSTFLAG_NEVER_CHECK_SUBJECT: int
HOSTFLAG_NO_WILDCARDS: int
HOSTFLAG_NO_PARTIAL_WILDCARDS: int
HOSTFLAG_MULTI_LABEL_WILDCARDS: int
HOSTFLAG_SINGLE_LABEL_SUBDOMAINS: int
if sys.version_info >= (3, 10):
# certificate file types
# Typed as Literal so the overload on Certificate.public_bytes can work properly.
ENCODING_PEM: Literal[1]
ENCODING_DER: Literal[2]
# protocol versions
PROTO_MINIMUM_SUPPORTED: int
PROTO_MAXIMUM_SUPPORTED: int
PROTO_SSLv3: int
PROTO_TLSv1: int
PROTO_TLSv1_1: int
PROTO_TLSv1_2: int
PROTO_TLSv1_3: int
# feature support
HAS_SNI: bool
HAS_TLS_UNIQUE: bool
HAS_ECDH: bool
HAS_NPN: bool
if sys.version_info >= (3, 13):
HAS_PSK: bool
HAS_ALPN: bool
HAS_SSLv2: bool
HAS_SSLv3: bool
HAS_TLSv1: bool
HAS_TLSv1_1: bool
HAS_TLSv1_2: bool
HAS_TLSv1_3: bool
# version info
OPENSSL_VERSION_NUMBER: int
OPENSSL_VERSION_INFO: tuple[int, int, int, int, int]
OPENSSL_VERSION: str
_OPENSSL_API_VERSION: tuple[int, int, int, int, int]

View File

@@ -1,37 +1,10 @@
import sys
from collections.abc import Callable
from typing import Any, Generic, TypeVar, final, overload
from typing_extensions import Self
if sys.version_info >= (3, 9):
from types import GenericAlias
from typing import Any, TypeVar, overload
from weakref import CallableProxyType as CallableProxyType, ProxyType as ProxyType, ReferenceType as ReferenceType, ref as ref
_C = TypeVar("_C", bound=Callable[..., Any])
_T = TypeVar("_T")
@final
class CallableProxyType(Generic[_C]): # "weakcallableproxy"
def __eq__(self, value: object, /) -> bool: ...
def __getattr__(self, attr: str) -> Any: ...
__call__: _C
@final
class ProxyType(Generic[_T]): # "weakproxy"
def __eq__(self, value: object, /) -> bool: ...
def __getattr__(self, attr: str) -> Any: ...
class ReferenceType(Generic[_T]):
__callback__: Callable[[Self], Any]
def __new__(cls, o: _T, callback: Callable[[Self], Any] | None = ..., /) -> Self: ...
def __init__(self, o: _T, callback: Callable[[Self], Any] | None = ..., /) -> None: ...
def __call__(self) -> _T | None: ...
def __eq__(self, value: object, /) -> bool: ...
def __hash__(self) -> int: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
ref = ReferenceType
def getweakrefcount(object: Any, /) -> int: ...
def getweakrefs(object: Any, /) -> list[Any]: ...

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
import ssl
import sys
from _asyncio import (
_get_running_loop as _get_running_loop,
_set_running_loop as _set_running_loop,
get_event_loop as get_event_loop,
get_running_loop as get_running_loop,
)
from _typeshed import FileDescriptorLike, ReadableBuffer, StrPath, Unused, WriteableBuffer
from abc import ABCMeta, abstractmethod
from collections.abc import Callable, Sequence
@@ -632,7 +638,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy, metaclass=ABCMeta):
def get_event_loop_policy() -> AbstractEventLoopPolicy: ...
def set_event_loop_policy(policy: AbstractEventLoopPolicy | None) -> None: ...
def get_event_loop() -> AbstractEventLoop: ...
def set_event_loop(loop: AbstractEventLoop | None) -> None: ...
def new_event_loop() -> AbstractEventLoop: ...
@@ -646,7 +651,3 @@ if sys.version_info < (3, 14):
else:
def get_child_watcher() -> AbstractChildWatcher: ...
def set_child_watcher(watcher: AbstractChildWatcher) -> None: ...
def _set_running_loop(loop: AbstractEventLoop | None, /) -> None: ...
def _get_running_loop() -> AbstractEventLoop: ...
def get_running_loop() -> AbstractEventLoop: ...

View File

@@ -1,15 +1,10 @@
import sys
from collections.abc import Awaitable, Callable, Generator, Iterable
from _asyncio import Future as Future
from concurrent.futures._base import Future as _ConcurrentFuture
from contextvars import Context
from typing import Any, Literal, TypeVar
from typing_extensions import Self, TypeIs
from typing import Any, TypeVar
from typing_extensions import TypeIs
from .events import AbstractEventLoop
if sys.version_info >= (3, 9):
from types import GenericAlias
__all__ = ("Future", "wrap_future", "isfuture")
_T = TypeVar("_T")
@@ -18,40 +13,4 @@ _T = TypeVar("_T")
# but it leads to circular import error in pytype tool.
# That's why the import order is reversed.
def isfuture(obj: object) -> TypeIs[Future[Any]]: ...
class Future(Awaitable[_T], Iterable[_T]):
_state: str
@property
def _exception(self) -> BaseException | None: ...
_blocking: bool
@property
def _log_traceback(self) -> bool: ...
@_log_traceback.setter
def _log_traceback(self, val: Literal[False]) -> None: ...
_asyncio_future_blocking: bool # is a part of duck-typing contract for `Future`
def __init__(self, *, loop: AbstractEventLoop | None = ...) -> None: ...
def __del__(self) -> None: ...
def get_loop(self) -> AbstractEventLoop: ...
@property
def _callbacks(self) -> list[tuple[Callable[[Self], Any], Context]]: ...
def add_done_callback(self, fn: Callable[[Self], object], /, *, context: Context | None = None) -> None: ...
if sys.version_info >= (3, 9):
def cancel(self, msg: Any | None = None) -> bool: ...
else:
def cancel(self) -> bool: ...
def cancelled(self) -> bool: ...
def done(self) -> bool: ...
def result(self) -> _T: ...
def exception(self) -> BaseException | None: ...
def remove_done_callback(self, fn: Callable[[Self], object], /) -> int: ...
def set_result(self, result: _T, /) -> None: ...
def set_exception(self, exception: type | BaseException, /) -> None: ...
def __iter__(self) -> Generator[Any, None, _T]: ...
def __await__(self) -> Generator[Any, None, _T]: ...
@property
def _loop(self) -> AbstractEventLoop: ...
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
def wrap_future(future: _ConcurrentFuture[_T] | Future[_T], *, loop: AbstractEventLoop | None = None) -> Future[_T]: ...

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