Compare commits
64 Commits
micha/remo
...
dhruv/temp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11c3b52fd5 | ||
|
|
a388e49f38 | ||
|
|
099f077311 | ||
|
|
8574751911 | ||
|
|
ddae741b72 | ||
|
|
5053d2c127 | ||
|
|
ef72fd79a7 | ||
|
|
658a51ea10 | ||
|
|
7c2da4f06e | ||
|
|
48fa839c80 | ||
|
|
cf0f5e1318 | ||
|
|
20b8a43017 | ||
|
|
b8acadd6a2 | ||
|
|
b372fe7198 | ||
|
|
53fa32a389 | ||
|
|
d1189c20df | ||
|
|
9a6b08b557 | ||
|
|
76e4277696 | ||
|
|
2d917d72f6 | ||
|
|
2629527559 | ||
|
|
bf20061268 | ||
|
|
eddc8d7644 | ||
|
|
b1ce8a3949 | ||
|
|
262c04f297 | ||
|
|
71536a43db | ||
|
|
e6dcdf3e49 | ||
|
|
f426349051 | ||
|
|
42c70697d8 | ||
|
|
1607d88c22 | ||
|
|
c6b82151dd | ||
|
|
39cf46ecd6 | ||
|
|
96b3c400fe | ||
|
|
60a2dc53e7 | ||
|
|
8d98aea6c4 | ||
|
|
d2c9f5e43c | ||
|
|
7dd0c7f4bd | ||
|
|
56c796acee | ||
|
|
2fe203292a | ||
|
|
b6847b371e | ||
|
|
b19862c64a | ||
|
|
9a0dade925 | ||
|
|
ec6208e51b | ||
|
|
74cf66e4c2 | ||
|
|
1f19aca632 | ||
|
|
6f52d573ef | ||
|
|
c593ccb529 | ||
|
|
9f3a38d408 | ||
|
|
f8eb547fb4 | ||
|
|
b77de359bc | ||
|
|
41f74512df | ||
|
|
387dc664bd | ||
|
|
41c9bdbd37 | ||
|
|
222a646437 | ||
|
|
5b411fe606 | ||
|
|
47dd83e56f | ||
|
|
08e23d78aa | ||
|
|
5af0966057 | ||
|
|
faf9dfaa9d | ||
|
|
9d131c8c45 | ||
|
|
5a56886414 | ||
|
|
66c3aaa307 | ||
|
|
b6ffa51c16 | ||
|
|
35f007f17f | ||
|
|
3006d6da23 |
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -17,5 +17,5 @@
|
||||
/scripts/fuzz-parser/ @AlexWaygood
|
||||
|
||||
# red-knot
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
|
||||
8
.github/workflows/publish-pypi.yml
vendored
8
.github/workflows/publish-pypi.yml
vendored
@@ -21,14 +21,12 @@ jobs:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: wheels-*
|
||||
path: wheels
|
||||
merge-multiple: true
|
||||
- name: Publish to PyPi
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
skip-existing: true
|
||||
packages-dir: wheels
|
||||
verbose: true
|
||||
run: uv publish -v wheels/*
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -202,46 +202,15 @@ jobs:
|
||||
name: artifacts-dist-manifest
|
||||
path: dist-manifest.json
|
||||
|
||||
custom-publish-pypi:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
|
||||
uses: ./.github/workflows/publish-pypi.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
# publish jobs get escalated permissions
|
||||
permissions:
|
||||
"id-token": "write"
|
||||
"packages": "write"
|
||||
|
||||
custom-publish-wasm:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }}
|
||||
uses: ./.github/workflows/publish-wasm.yml
|
||||
with:
|
||||
plan: ${{ needs.plan.outputs.val }}
|
||||
secrets: inherit
|
||||
# publish jobs get escalated permissions
|
||||
permissions:
|
||||
"contents": "read"
|
||||
"id-token": "write"
|
||||
"packages": "write"
|
||||
|
||||
# Create a GitHub Release while uploading all files to it
|
||||
announce:
|
||||
needs:
|
||||
- plan
|
||||
- host
|
||||
- custom-publish-pypi
|
||||
- custom-publish-wasm
|
||||
# use "always() && ..." to allow us to wait for all publish jobs while
|
||||
# still allowing individual publish jobs to skip themselves (for prereleases).
|
||||
# "host" however must run to completion, no skipping allowed!
|
||||
if: ${{ always() && needs.host.result == 'success' && (needs.custom-publish-pypi.result == 'skipped' || needs.custom-publish-pypi.result == 'success') && (needs.custom-publish-wasm.result == 'skipped' || needs.custom-publish-wasm.result == 'success') }}
|
||||
if: ${{ always() && needs.host.result == 'success' }}
|
||||
runs-on: "ubuntu-20.04"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,5 +1,31 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- Fix formatting of single with-item with trailing comment ([#14005](https://github.com/astral-sh/ruff/pull/14005))
|
||||
- \[`pyupgrade`\] Add PEP 646 `Unpack` conversion to `*` with fix (`UP044`) ([#13988](https://github.com/astral-sh/ruff/pull/13988))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Regenerate `known_stdlibs.rs` with stdlibs 2024.10.25 ([#13963](https://github.com/astral-sh/ruff/pull/13963))
|
||||
- \[`flake8-no-pep420`\] Skip namespace package enforcement for PEP 723 scripts (`INP001`) ([#13974](https://github.com/astral-sh/ruff/pull/13974))
|
||||
|
||||
### Server
|
||||
|
||||
- Fix server panic when undoing an edit ([#14010](https://github.com/astral-sh/ruff/pull/14010))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix issues in discovering ruff in pip build environments ([#13881](https://github.com/astral-sh/ruff/pull/13881))
|
||||
- \[`flake8-type-checking`\] Fix false positive for `singledispatchmethod` (`TCH003`) ([#13941](https://github.com/astral-sh/ruff/pull/13941))
|
||||
- \[`flake8-type-checking`\] Treat return type of `singledispatch` as runtime-required (`TCH003`) ([#13957](https://github.com/astral-sh/ruff/pull/13957))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-simplify`\] Include caveats of enabling `if-else-block-instead-of-if-exp` (`SIM108`) ([#14019](https://github.com/astral-sh/ruff/pull/14019))
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Preview features
|
||||
|
||||
93
Cargo.lock
generated
93
Cargo.lock
generated
@@ -123,9 +123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.90"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95"
|
||||
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||
|
||||
[[package]]
|
||||
name = "append-only-vec"
|
||||
@@ -407,7 +407,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -687,7 +687,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -698,7 +698,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1213,9 +1213,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.40.0"
|
||||
version = "1.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60"
|
||||
checksum = "a1f72d3e19488cf7d8ea52d2fc0f8754fc933398b337cd3cbdb28aaeb35159ef"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1267,7 +1267,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1393,7 +1393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1848,7 +1848,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1963,9 +1963,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2247,9 +2247,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2306,7 +2306,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2523,7 +2523,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2590,7 +2590,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2620,6 +2620,7 @@ dependencies = [
|
||||
"compact_str",
|
||||
"is-macro",
|
||||
"itertools 0.13.0",
|
||||
"memchr",
|
||||
"ruff_cache",
|
||||
"ruff_macros",
|
||||
"ruff_python_trivia",
|
||||
@@ -2748,7 +2749,6 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
@@ -2779,7 +2779,6 @@ dependencies = [
|
||||
"insta",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
]
|
||||
|
||||
@@ -2838,7 +2837,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3009,7 +3008,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3043,7 +3042,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3066,9 +3065,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3086,13 +3085,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3103,7 +3102,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3126,7 +3125,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3167,7 +3166,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3269,7 +3268,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3291,9 +3290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.82"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3308,7 +3307,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3371,7 +3370,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3382,28 +3381,28 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3515,7 +3514,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3773,7 +3772,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3859,7 +3858,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3893,7 +3892,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -3927,7 +3926,7 @@ checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4215,7 +4214,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.82",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -188,8 +188,9 @@ missing_panics_doc = "allow"
|
||||
module_name_repetitions = "allow"
|
||||
must_use_candidate = "allow"
|
||||
similar_names = "allow"
|
||||
single_match_else = "allow"
|
||||
too_many_lines = "allow"
|
||||
# To allow `#[allow(clippy::all)]` in `crates/ruff_python_parser/src/python.rs`.
|
||||
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
|
||||
needless_raw_string_hashes = "allow"
|
||||
# Disallowed restriction lints
|
||||
print_stdout = "warn"
|
||||
@@ -292,7 +293,7 @@ build-local-artifacts = false
|
||||
# Local artifacts jobs to run in CI
|
||||
local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
# publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
# Post-announce jobs to run in CI
|
||||
post-announce-jobs = [
|
||||
"./notify-dependents",
|
||||
|
||||
@@ -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.7.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.1/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.7.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.2/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.7.1
|
||||
rev: v0.7.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -417,6 +417,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Babel](https://github.com/python-babel/babel)
|
||||
- Benchling ([Refac](https://github.com/benchling/refac))
|
||||
- [Bokeh](https://github.com/bokeh/bokeh)
|
||||
- CrowdCent ([NumerBlox](https://github.com/crowdcent/numerblox)) <!-- typos: ignore -->
|
||||
- [Cryptography (PyCA)](https://github.com/pyca/cryptography)
|
||||
- CERN ([Indico](https://getindico.io/))
|
||||
- [DVC](https://github.com/iterative/dvc)
|
||||
|
||||
@@ -12,6 +12,7 @@ pn = "pn" # `import panel as pn` is a thing
|
||||
poit = "poit"
|
||||
BA = "BA" # acronym for "Bad Allowed", used in testing.
|
||||
jod = "jod" # e.g., `jod-thread`
|
||||
Numer = "Numer" # Library name 'NumerBlox' in "Who's Using Ruff?"
|
||||
|
||||
[default]
|
||||
extend-ignore-re = [
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
wrap = 100
|
||||
@@ -23,12 +23,90 @@ x: int
|
||||
x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `int`"
|
||||
```
|
||||
|
||||
## PEP-604 annotations not yet supported
|
||||
## Tuple annotations are understood
|
||||
|
||||
```py path=module.py
|
||||
from typing_extensions import Unpack
|
||||
|
||||
a: tuple[()] = ()
|
||||
b: tuple[int] = (42,)
|
||||
c: tuple[str, int] = ("42", 42)
|
||||
d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42))
|
||||
e: tuple[str, ...] = ()
|
||||
# TODO: we should not emit this error
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
|
||||
f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42")
|
||||
g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42")
|
||||
h: tuple[list[int], list[int]] = ([], [])
|
||||
i: tuple[str | int, str | int] = (42, 42)
|
||||
j: tuple[str | int] = (42,)
|
||||
```
|
||||
|
||||
```py path=script.py
|
||||
from module import a, b, c, d, e, f, g, h, i, j
|
||||
|
||||
reveal_type(a) # revealed: tuple[()]
|
||||
reveal_type(b) # revealed: tuple[int]
|
||||
reveal_type(c) # revealed: tuple[str, int]
|
||||
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||
|
||||
# TODO: homogenous tuples, PEP-646 tuples
|
||||
reveal_type(e) # revealed: @Todo
|
||||
reveal_type(f) # revealed: @Todo
|
||||
reveal_type(g) # revealed: @Todo
|
||||
|
||||
# TODO: support more kinds of type expressions in annotations
|
||||
reveal_type(h) # revealed: @Todo
|
||||
|
||||
reveal_type(i) # revealed: tuple[str | int, str | int]
|
||||
reveal_type(j) # revealed: tuple[str | int]
|
||||
```
|
||||
|
||||
## Incorrect tuple assignments are complained about
|
||||
|
||||
```py
|
||||
def f() -> str | None:
|
||||
# error: [invalid-assignment] "Object of type `tuple[Literal[1], Literal[2]]` is not assignable to `tuple[()]`"
|
||||
a: tuple[()] = (1, 2)
|
||||
|
||||
# error: [invalid-assignment] "Object of type `tuple[Literal["foo"]]` is not assignable to `tuple[int]`"
|
||||
b: tuple[int] = ("foo",)
|
||||
|
||||
# error: [invalid-assignment] "Object of type `tuple[list, Literal["foo"]]` is not assignable to `tuple[str | int, str]`"
|
||||
c: tuple[str | int, str] = ([], "foo")
|
||||
```
|
||||
|
||||
## PEP-604 annotations are supported
|
||||
|
||||
```py
|
||||
def foo() -> str | int | None:
|
||||
return None
|
||||
|
||||
# TODO: should be `str | None` (but Todo is better than `Unknown`)
|
||||
reveal_type(f()) # revealed: @Todo
|
||||
reveal_type(foo()) # revealed: str | int | None
|
||||
|
||||
def bar() -> str | str | None:
|
||||
return None
|
||||
|
||||
reveal_type(bar()) # revealed: str | None
|
||||
|
||||
def baz() -> str | str:
|
||||
return "Hello, world!"
|
||||
|
||||
reveal_type(baz()) # revealed: str
|
||||
```
|
||||
|
||||
## Attribute expressions in type annotations are understood
|
||||
|
||||
```py
|
||||
import builtins
|
||||
|
||||
int = "foo"
|
||||
a: builtins.int = 42
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal["bar"]` is not assignable to `int`"
|
||||
b: builtins.int = "bar"
|
||||
|
||||
c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = ((42, 42), 42)
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `tuple[tuple[int, int], int]`"
|
||||
c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = "foo"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Augmented assignment
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
x = 3
|
||||
x -= 1
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
|
||||
x = 1.0
|
||||
x /= 2
|
||||
reveal_type(x) # revealed: float
|
||||
```
|
||||
|
||||
## Dunder methods
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __isub__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
x = C()
|
||||
x -= 1
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
class C:
|
||||
def __iadd__(self, other: str) -> float:
|
||||
return 1.0
|
||||
|
||||
x = C()
|
||||
x += "Hello"
|
||||
reveal_type(x) # revealed: float
|
||||
```
|
||||
|
||||
## Unsupported types
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __isub__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
x = C()
|
||||
x -= 1
|
||||
|
||||
# TODO: should error, once operand type check is implemented
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Method union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
class Foo:
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
else:
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
|
||||
f = Foo()
|
||||
f += 12
|
||||
|
||||
reveal_type(f) # revealed: str | int
|
||||
```
|
||||
|
||||
## Partially bound `__iadd__`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class Foo:
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
f = Foo()
|
||||
|
||||
# TODO: We should emit an `unsupported-operator` error here, possibly with the information
|
||||
# that `Foo.__iadd__` may be unbound as additional context.
|
||||
f += "Hello, world!"
|
||||
|
||||
reveal_type(f) # revealed: int
|
||||
```
|
||||
|
||||
## Partially bound with `__add__`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class Foo:
|
||||
def __add__(self, other: str) -> str:
|
||||
return "Hello, world!"
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
f = Foo()
|
||||
f += "Hello, world!"
|
||||
|
||||
# TODO(charlie): This should be `int | str`, since `__iadd__` may be unbound.
|
||||
reveal_type(f) # revealed: int
|
||||
```
|
||||
|
||||
## Partially bound target union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class Foo:
|
||||
def __add__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
|
||||
if bool_instance():
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
|
||||
# TODO(charlie): This should be `str | int | float`
|
||||
reveal_type(f) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Target union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
class Foo:
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
if flag:
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
|
||||
# TODO(charlie): This should be `str | float`.
|
||||
reveal_type(f) # revealed: @Todo
|
||||
```
|
||||
@@ -6,11 +6,19 @@
|
||||
x = foo # error: [unresolved-reference] "Name `foo` used when not defined"
|
||||
foo = 1
|
||||
|
||||
# error: [unresolved-reference]
|
||||
# revealed: Unbound
|
||||
# No error `unresolved-reference` diagnostic is reported for `x`. This is
|
||||
# desirable because we would get a lot of cascading errors even though there
|
||||
# is only one root cause (the unbound variable `foo`).
|
||||
|
||||
# revealed: Unknown
|
||||
reveal_type(x)
|
||||
```
|
||||
|
||||
Note: in this particular example, one could argue that the most likely error would be a wrong order
|
||||
of the `x`/`foo` definitions, and so it could be desirable to infer `Literal[1]` for the type of
|
||||
`x`. On the other hand, there might be a variable `fob` a little higher up in this file, and the
|
||||
actual error might have been just a typo. Inferring `Unknown` thus seems like the safest option.
|
||||
|
||||
## Unbound class variable
|
||||
|
||||
Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
|
||||
@@ -30,3 +38,22 @@ class C:
|
||||
reveal_type(C.x) # revealed: Literal[2]
|
||||
reveal_type(C.y) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Possibly unbound in class and global scope
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if bool_instance():
|
||||
x = "abc"
|
||||
|
||||
class C:
|
||||
if bool_instance():
|
||||
x = 1
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
y = x
|
||||
|
||||
reveal_type(C.y) # revealed: Literal[1] | Literal["abc"]
|
||||
```
|
||||
|
||||
@@ -9,8 +9,8 @@ For references, see:
|
||||
|
||||
## Operations
|
||||
|
||||
We support inference for all Python's binary operators:
|
||||
`+`, `-`, `*`, `@`, `/`, `//`, `%`, `**`, `<<`, `>>`, `&`, `^`, and `|`.
|
||||
We support inference for all Python's binary operators: `+`, `-`, `*`, `@`, `/`, `//`, `%`, `**`,
|
||||
`<<`, `>>`, `&`, `^`, and `|`.
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -152,9 +152,8 @@ reveal_type(B() - A()) # revealed: int
|
||||
|
||||
## Non-reflected precedence in general
|
||||
|
||||
In general, if the left-hand side defines `__add__` and the right-hand side
|
||||
defines `__radd__` and the right-hand side is not a subtype of the left-hand
|
||||
side, `lhs.__add__` will take precedence:
|
||||
In general, if the left-hand side defines `__add__` and the right-hand side defines `__radd__` and
|
||||
the right-hand side is not a subtype of the left-hand side, `lhs.__add__` will take precedence:
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -181,9 +180,8 @@ reveal_type(C() + C()) # revealed: int
|
||||
|
||||
## Reflected precedence for subtypes (in some cases)
|
||||
|
||||
If the right-hand operand is a subtype of the left-hand operand and has a
|
||||
different implementation of the reflected method, the reflected method on the
|
||||
right-hand operand takes precedence.
|
||||
If the right-hand operand is a subtype of the left-hand operand and has a different implementation
|
||||
of the reflected method, the reflected method on the right-hand operand takes precedence.
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -213,9 +211,8 @@ reveal_type(A() + C()) # revealed: str
|
||||
|
||||
## Reflected precedence 2
|
||||
|
||||
If the right-hand operand is a subtype of the left-hand operand, but does not
|
||||
override the reflected method, the left-hand operand's non-reflected method
|
||||
still takes precedence:
|
||||
If the right-hand operand is a subtype of the left-hand operand, but does not override the reflected
|
||||
method, the left-hand operand's non-reflected method still takes precedence:
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -232,17 +229,15 @@ reveal_type(A() + B()) # revealed: str
|
||||
|
||||
## Only reflected supported
|
||||
|
||||
For example, at runtime, `(1).__add__(1.2)` is `NotImplemented`, but
|
||||
`(1.2).__radd__(1) == 2.2`, meaning that `1 + 1.2` succeeds at runtime
|
||||
(producing `2.2`). The runtime tries the second one only if the first one
|
||||
returns `NotImplemented` to signal failure.
|
||||
For example, at runtime, `(1).__add__(1.2)` is `NotImplemented`, but `(1.2).__radd__(1) == 2.2`,
|
||||
meaning that `1 + 1.2` succeeds at runtime (producing `2.2`). The runtime tries the second one only
|
||||
if the first one returns `NotImplemented` to signal failure.
|
||||
|
||||
Typeshed and other stubs annotate dunder-method calls that would return
|
||||
`NotImplemented` as being "illegal" calls. `int.__add__` is annotated as only
|
||||
"accepting" `int`s, even though it strictly-speaking "accepts" any other object
|
||||
without raising an exception -- it will simply return `NotImplemented`,
|
||||
allowing the runtime to try the `__radd__` method of the right-hand operand
|
||||
as well.
|
||||
Typeshed and other stubs annotate dunder-method calls that would return `NotImplemented` as being
|
||||
"illegal" calls. `int.__add__` is annotated as only "accepting" `int`s, even though it
|
||||
strictly-speaking "accepts" any other object without raising an exception -- it will simply return
|
||||
`NotImplemented`, allowing the runtime to try the `__radd__` method of the right-hand operand as
|
||||
well.
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -308,8 +303,8 @@ reveal_type(y + 4.12) # revealed: int
|
||||
|
||||
## With literal types
|
||||
|
||||
When we have a literal type for one operand, we're able to fall back to the
|
||||
instance handling for its instance super-type.
|
||||
When we have a literal type for one operand, we're able to fall back to the instance handling for
|
||||
its instance super-type.
|
||||
|
||||
```py
|
||||
class A:
|
||||
@@ -348,15 +343,13 @@ reveal_type(literal_string_instance + A()) # revealed: @Todo
|
||||
|
||||
## Operations involving instances of classes inheriting from `Any`
|
||||
|
||||
`Any` and `Unknown` represent a set of possible runtime objects, wherein the
|
||||
bounds of the set are unknown. Whether the left-hand operand's dunder or the
|
||||
right-hand operand's reflected dunder depends on whether the right-hand operand
|
||||
is an instance of a class that is a subclass of the left-hand operand's class
|
||||
and overrides the reflected dunder. In the following example, because of the
|
||||
unknowable nature of `Any`/`Unknown`, we must consider both possibilities:
|
||||
`Any`/`Unknown` might resolve to an unknown third class that inherits from `X`
|
||||
and overrides `__radd__`; but it also might not. Thus, the correct answer here
|
||||
for the `reveal_type` is `int | Unknown`.
|
||||
`Any` and `Unknown` represent a set of possible runtime objects, wherein the bounds of the set are
|
||||
unknown. Whether the left-hand operand's dunder or the right-hand operand's reflected dunder depends
|
||||
on whether the right-hand operand is an instance of a class that is a subclass of the left-hand
|
||||
operand's class and overrides the reflected dunder. In the following example, because of the
|
||||
unknowable nature of `Any`/`Unknown`, we must consider both possibilities: `Any`/`Unknown` might
|
||||
resolve to an unknown third class that inherits from `X` and overrides `__radd__`; but it also might
|
||||
not. Thus, the correct answer here for the `reveal_type` is `int | Unknown`.
|
||||
|
||||
```py
|
||||
from does_not_exist import Foo # error: [unresolved-import]
|
||||
@@ -426,10 +419,9 @@ reveal_type(B() + C())
|
||||
|
||||
### Reflected dunder is not tried between two objects of the same type
|
||||
|
||||
For the specific case where the left-hand operand is the exact same type as the
|
||||
right-hand operand, the reflected dunder of the right-hand operand is not
|
||||
tried; the runtime short-circuits after trying the unreflected dunder of the
|
||||
left-hand operand. For context, see
|
||||
For the specific case where the left-hand operand is the exact same type as the right-hand operand,
|
||||
the reflected dunder of the right-hand operand is not tried; the runtime short-circuits after trying
|
||||
the unreflected dunder of the left-hand operand. For context, see
|
||||
[this mailing list discussion](https://mail.python.org/archives/list/python-dev@python.org/thread/7NZUCODEAPQFMRFXYRMGJXDSIS3WJYIV/).
|
||||
|
||||
```py
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# Short-Circuit Evaluation
|
||||
|
||||
## Not all boolean expressions must be evaluated
|
||||
|
||||
In `or` expressions, if the left-hand side is truthy, the right-hand side is not evaluated.
|
||||
Similarly, in `and` expressions, if the left-hand side is falsy, the right-hand side is not
|
||||
evaluated.
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if bool_instance() or (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if bool_instance() and (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## First expression is always evaluated
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if (x := 1) or bool_instance():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if (x := 1) and bool_instance():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Statically known truthiness
|
||||
|
||||
```py
|
||||
if True or (x := 1):
|
||||
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if True and (x := 1):
|
||||
# TODO: infer that the second arm is always executed, do not raise a diagnostic
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Later expressions can always use variables from earlier expressions
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
bool_instance() or (x := 1) or reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
# error: [unresolved-reference]
|
||||
bool_instance() or reveal_type(y) or (y := 1) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Nested expressions
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if bool_instance() or ((x := 1) and bool_instance()):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if ((y := 1) and bool_instance()) or bool_instance():
|
||||
reveal_type(y) # revealed: Literal[1]
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
if (bool_instance() and (z := 1)) or reveal_type(z): # revealed: Literal[1]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(z) # revealed: Literal[1]
|
||||
```
|
||||
@@ -1,7 +1,7 @@
|
||||
# Comparison: Byte literals
|
||||
|
||||
These tests assert that we infer precise `Literal` types for comparisons between objects
|
||||
inferred as having `Literal` bytes types:
|
||||
These tests assert that we infer precise `Literal` types for comparisons between objects inferred as
|
||||
having `Literal` bytes types:
|
||||
|
||||
```py
|
||||
reveal_type(b"abc" == b"abc") # revealed: Literal[True]
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
# Comparison: Membership Test
|
||||
|
||||
In Python, the term "membership test operators" refers to the operators `in` and `not in`. To
|
||||
customize their behavior, classes can implement one of the special methods `__contains__`,
|
||||
`__iter__`, or `__getitem__`.
|
||||
|
||||
For references, see:
|
||||
|
||||
- <https://docs.python.org/3/reference/expressions.html#membership-test-details>
|
||||
- <https://docs.python.org/3/reference/datamodel.html#object.__contains__>
|
||||
- <https://snarky.ca/unravelling-membership-testing/>
|
||||
|
||||
## Implements `__contains__`
|
||||
|
||||
Classes can support membership tests by implementing the `__contains__` method:
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __contains__(self, item: str) -> bool:
|
||||
return True
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
# TODO: should emit diagnostic, need to check arg type, will fail
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Implements `__iter__`
|
||||
|
||||
Classes that don't implement `__contains__`, but do implement `__iter__`, also support containment
|
||||
checks; the needle will be sought in their iterated items:
|
||||
|
||||
```py
|
||||
class StringIterator:
|
||||
def __next__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
class A:
|
||||
def __iter__(self) -> StringIterator:
|
||||
return StringIterator()
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Implements `__getitems__`
|
||||
|
||||
The final fallback is to implement `__getitem__` for integer keys. Python will call `__getitem__`
|
||||
with `0`, `1`, `2`... until either the needle is found (leading the membership test to evaluate to
|
||||
`True`) or `__getitem__` raises `IndexError` (the raised exception is swallowed, but results in the
|
||||
membership test evaluating to `False`).
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __getitem__(self, key: int) -> str:
|
||||
return "foo"
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type(42 not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Wrong Return Type
|
||||
|
||||
Python coerces the results of containment checks to `bool`, even if `__contains__` returns a
|
||||
non-bool:
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __contains__(self, item: str) -> str:
|
||||
return "foo"
|
||||
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
reveal_type("hello" not in A()) # revealed: bool
|
||||
```
|
||||
|
||||
## Literal Result for `in` and `not in`
|
||||
|
||||
`__contains__` with a literal return type may result in a `BooleanLiteral` outcome.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class AlwaysTrue:
|
||||
def __contains__(self, item: int) -> Literal[1]:
|
||||
return 1
|
||||
|
||||
class AlwaysFalse:
|
||||
def __contains__(self, item: int) -> Literal[""]:
|
||||
return ""
|
||||
|
||||
# TODO: it should be Literal[True] and Literal[False]
|
||||
reveal_type(42 in AlwaysTrue()) # revealed: @Todo
|
||||
reveal_type(42 not in AlwaysTrue()) # revealed: @Todo
|
||||
|
||||
# TODO: it should be Literal[False] and Literal[True]
|
||||
reveal_type(42 in AlwaysFalse()) # revealed: @Todo
|
||||
reveal_type(42 not in AlwaysFalse()) # revealed: @Todo
|
||||
```
|
||||
|
||||
## No Fallback for `__contains__`
|
||||
|
||||
If `__contains__` is implemented, checking membership of a type it doesn't accept is an error; it
|
||||
doesn't result in a fallback to `__iter__` or `__getitem__`:
|
||||
|
||||
```py
|
||||
class CheckContains: ...
|
||||
class CheckIter: ...
|
||||
class CheckGetItem: ...
|
||||
|
||||
class CheckIterIterator:
|
||||
def __next__(self) -> CheckIter:
|
||||
return CheckIter()
|
||||
|
||||
class A:
|
||||
def __contains__(self, item: CheckContains) -> bool:
|
||||
return True
|
||||
|
||||
def __iter__(self) -> CheckIterIterator:
|
||||
return CheckIterIterator()
|
||||
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
reveal_type(CheckContains() in A()) # revealed: bool
|
||||
|
||||
# TODO: should emit diagnostic, need to check arg type,
|
||||
# should not fall back to __iter__ or __getitem__
|
||||
reveal_type(CheckIter() in A()) # revealed: bool
|
||||
reveal_type(CheckGetItem() in A()) # revealed: bool
|
||||
|
||||
class B:
|
||||
def __iter__(self) -> CheckIterIterator:
|
||||
return CheckIterIterator()
|
||||
|
||||
def __getitem__(self, key: int) -> CheckGetItem:
|
||||
return CheckGetItem()
|
||||
|
||||
reveal_type(CheckIter() in B()) # revealed: bool
|
||||
# Always use `__iter__`, regardless of iterated type; there's no NotImplemented
|
||||
# in this case, so there's no fallback to `__getitem__`
|
||||
reveal_type(CheckGetItem() in B()) # revealed: bool
|
||||
```
|
||||
|
||||
## Invalid Old-Style Iteration
|
||||
|
||||
If `__getitem__` is implemented but does not accept integer arguments, then the membership test is
|
||||
not supported and should trigger a diagnostic.
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __getitem__(self, key: str) -> str:
|
||||
return "foo"
|
||||
|
||||
# TODO should emit a diagnostic
|
||||
reveal_type(42 in A()) # revealed: bool
|
||||
reveal_type("hello" in A()) # revealed: bool
|
||||
```
|
||||
@@ -0,0 +1,328 @@
|
||||
# Comparison: Rich Comparison
|
||||
|
||||
Rich comparison operations (`==`, `!=`, `<`, `<=`, `>`, `>=`) in Python are implemented through
|
||||
double-underscore methods that allow customization of comparison behavior.
|
||||
|
||||
For references, see:
|
||||
|
||||
- <https://docs.python.org/3/reference/datamodel.html#object.__lt__>
|
||||
- <https://snarky.ca/unravelling-rich-comparison-operators/>
|
||||
|
||||
## Rich Comparison Dunder Implementations For Same Class
|
||||
|
||||
Classes can support rich comparison by implementing dunder methods like `__eq__`, `__ne__`, etc. The
|
||||
most common case involves implementing these methods for the same type:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> int:
|
||||
return 42
|
||||
|
||||
def __ne__(self, other: A) -> float:
|
||||
return 42.0
|
||||
|
||||
def __lt__(self, other: A) -> str:
|
||||
return "42"
|
||||
|
||||
def __le__(self, other: A) -> bytes:
|
||||
return b"42"
|
||||
|
||||
def __gt__(self, other: A) -> list:
|
||||
return [42]
|
||||
|
||||
def __ge__(self, other: A) -> set:
|
||||
return {42}
|
||||
|
||||
reveal_type(A() == A()) # revealed: int
|
||||
reveal_type(A() != A()) # revealed: float
|
||||
reveal_type(A() < A()) # revealed: str
|
||||
reveal_type(A() <= A()) # revealed: bytes
|
||||
reveal_type(A() > A()) # revealed: list
|
||||
reveal_type(A() >= A()) # revealed: set
|
||||
```
|
||||
|
||||
## Rich Comparison Dunder Implementations for Other Class
|
||||
|
||||
In some cases, classes may implement rich comparison dunder methods for comparisons with a different
|
||||
type:
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> int:
|
||||
return 42
|
||||
|
||||
def __ne__(self, other: B) -> float:
|
||||
return 42.0
|
||||
|
||||
def __lt__(self, other: B) -> str:
|
||||
return "42"
|
||||
|
||||
def __le__(self, other: B) -> bytes:
|
||||
return b"42"
|
||||
|
||||
def __gt__(self, other: B) -> list:
|
||||
return [42]
|
||||
|
||||
def __ge__(self, other: B) -> set:
|
||||
return {42}
|
||||
|
||||
class B: ...
|
||||
|
||||
reveal_type(A() == B()) # revealed: int
|
||||
reveal_type(A() != B()) # revealed: float
|
||||
reveal_type(A() < B()) # revealed: str
|
||||
reveal_type(A() <= B()) # revealed: bytes
|
||||
reveal_type(A() > B()) # revealed: list
|
||||
reveal_type(A() >= B()) # revealed: set
|
||||
```
|
||||
|
||||
## Reflected Comparisons
|
||||
|
||||
Fallback to the right-hand side’s comparison methods occurs when the left-hand side does not define
|
||||
them. Note: class `B` has its own `__eq__` and `__ne__` methods to override those of `object`, but
|
||||
these methods will be ignored here because they require a mismatched operand type.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> int:
|
||||
return 42
|
||||
|
||||
def __ne__(self, other: B) -> float:
|
||||
return 42.0
|
||||
|
||||
def __lt__(self, other: B) -> str:
|
||||
return "42"
|
||||
|
||||
def __le__(self, other: B) -> bytes:
|
||||
return b"42"
|
||||
|
||||
def __gt__(self, other: B) -> list:
|
||||
return [42]
|
||||
|
||||
def __ge__(self, other: B) -> set:
|
||||
return {42}
|
||||
|
||||
class B:
|
||||
# To override builtins.object.__eq__ and builtins.object.__ne__
|
||||
# TODO these should emit an invalid override diagnostic
|
||||
def __eq__(self, other: str) -> B:
|
||||
return B()
|
||||
|
||||
def __ne__(self, other: str) -> B:
|
||||
return B()
|
||||
|
||||
# TODO: should be `int` and `float`.
|
||||
# Need to check arg type and fall back to `rhs.__eq__` and `rhs.__ne__`.
|
||||
#
|
||||
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
|
||||
# this can only happen with an invalid override of these methods,
|
||||
# but we still support it.
|
||||
reveal_type(B() == A()) # revealed: B
|
||||
reveal_type(B() != A()) # revealed: B
|
||||
|
||||
reveal_type(B() < A()) # revealed: list
|
||||
reveal_type(B() <= A()) # revealed: set
|
||||
|
||||
reveal_type(B() > A()) # revealed: str
|
||||
reveal_type(B() >= A()) # revealed: bytes
|
||||
|
||||
class C:
|
||||
def __gt__(self, other: C) -> int:
|
||||
return 42
|
||||
|
||||
def __ge__(self, other: C) -> float:
|
||||
return 42.0
|
||||
|
||||
reveal_type(C() < C()) # revealed: int
|
||||
reveal_type(C() <= C()) # revealed: float
|
||||
```
|
||||
|
||||
## Reflected Comparisons with Subclasses
|
||||
|
||||
When subclasses override comparison methods, these overridden methods take precedence over those in
|
||||
the parent class. Class `B` inherits from `A` and redefines comparison methods to return types other
|
||||
than `A`.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __lt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __le__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __gt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __ge__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __eq__(self, other: A) -> int:
|
||||
return 42
|
||||
|
||||
def __ne__(self, other: A) -> float:
|
||||
return 42.0
|
||||
|
||||
def __lt__(self, other: A) -> str:
|
||||
return "42"
|
||||
|
||||
def __le__(self, other: A) -> bytes:
|
||||
return b"42"
|
||||
|
||||
def __gt__(self, other: A) -> list:
|
||||
return [42]
|
||||
|
||||
def __ge__(self, other: A) -> set:
|
||||
return {42}
|
||||
|
||||
reveal_type(A() == B()) # revealed: int
|
||||
reveal_type(A() != B()) # revealed: float
|
||||
|
||||
reveal_type(A() < B()) # revealed: list
|
||||
reveal_type(A() <= B()) # revealed: set
|
||||
|
||||
reveal_type(A() > B()) # revealed: str
|
||||
reveal_type(A() >= B()) # revealed: bytes
|
||||
```
|
||||
|
||||
## Reflected Comparisons with Subclass But Falls Back to LHS
|
||||
|
||||
In the case of a subclass, the right-hand side has priority. However, if the overridden dunder
|
||||
method has an mismatched type to operand, the comparison will fall back to the left-hand side.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __lt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
def __gt__(self, other: A) -> A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __lt__(self, other: int) -> B:
|
||||
return B()
|
||||
|
||||
def __gt__(self, other: int) -> B:
|
||||
return B()
|
||||
|
||||
# TODO: should be `A`, need to check argument type and fall back to LHS method
|
||||
reveal_type(A() < B()) # revealed: B
|
||||
reveal_type(A() > B()) # revealed: B
|
||||
```
|
||||
|
||||
## Operations involving instances of classes inheriting from `Any`
|
||||
|
||||
`Any` and `Unknown` represent a set of possible runtime objects, wherein the bounds of the set are
|
||||
unknown. Whether the left-hand operand's dunder or the right-hand operand's reflected dunder depends
|
||||
on whether the right-hand operand is an instance of a class that is a subclass of the left-hand
|
||||
operand's class and overrides the reflected dunder. In the following example, because of the
|
||||
unknowable nature of `Any`/`Unknown`, we must consider both possibilities: `Any`/`Unknown` might
|
||||
resolve to an unknown third class that inherits from `X` and overrides `__gt__`; but it also might
|
||||
not. Thus, the correct answer here for the `reveal_type` is `int | Unknown`.
|
||||
|
||||
(This test is referenced from `mdtest/binary/instances.md`)
|
||||
|
||||
```py
|
||||
from does_not_exist import Foo # error: [unresolved-import]
|
||||
|
||||
reveal_type(Foo) # revealed: Unknown
|
||||
|
||||
class X:
|
||||
def __lt__(self, other: object) -> int:
|
||||
return 42
|
||||
|
||||
class Y(Foo): ...
|
||||
|
||||
# TODO: Should be `int | Unknown`; see above discussion.
|
||||
reveal_type(X() < Y()) # revealed: int
|
||||
```
|
||||
|
||||
## Equality and Inequality Fallback
|
||||
|
||||
This test confirms that `==` and `!=` comparisons default to identity comparisons (`is`, `is not`)
|
||||
when argument types do not match the method signature.
|
||||
|
||||
Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#object.__eq__)
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
# TODO both these overrides should emit invalid-override diagnostic
|
||||
def __eq__(self, other: int) -> A:
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: int) -> A:
|
||||
return A()
|
||||
|
||||
# TODO: it should be `bool`, need to check arg type and fall back to `is` and `is not`
|
||||
reveal_type(A() == A()) # revealed: A
|
||||
reveal_type(A() != A()) # revealed: A
|
||||
```
|
||||
|
||||
## Object Comparisons with Typeshed
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
reveal_type(A() == object()) # revealed: bool
|
||||
reveal_type(A() != object()) # revealed: bool
|
||||
reveal_type(object() == A()) # revealed: bool
|
||||
reveal_type(object() != A()) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `object`"
|
||||
# revealed: Unknown
|
||||
reveal_type(A() < object())
|
||||
```
|
||||
|
||||
## Numbers Comparison with typeshed
|
||||
|
||||
```py
|
||||
reveal_type(1 == 1.0) # revealed: bool
|
||||
reveal_type(1 != 1.0) # revealed: bool
|
||||
reveal_type(1 < 1.0) # revealed: bool
|
||||
reveal_type(1 <= 1.0) # revealed: bool
|
||||
reveal_type(1 > 1.0) # revealed: bool
|
||||
reveal_type(1 >= 1.0) # revealed: bool
|
||||
|
||||
reveal_type(1 == 2j) # revealed: bool
|
||||
reveal_type(1 != 2j) # revealed: bool
|
||||
|
||||
# TODO: should be Unknown and emit diagnostic,
|
||||
# need to check arg type and should be failed
|
||||
reveal_type(1 < 2j) # revealed: bool
|
||||
reveal_type(1 <= 2j) # revealed: bool
|
||||
reveal_type(1 > 2j) # revealed: bool
|
||||
reveal_type(1 >= 2j) # revealed: bool
|
||||
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = bool_instance()
|
||||
y = int_instance()
|
||||
|
||||
reveal_type(x < y) # revealed: bool
|
||||
reveal_type(y < x) # revealed: bool
|
||||
reveal_type(4.2 < x) # revealed: bool
|
||||
reveal_type(x < 4.2) # revealed: bool
|
||||
```
|
||||
@@ -12,16 +12,18 @@ reveal_type(1 is 1) # revealed: bool
|
||||
reveal_type(1 is not 1) # revealed: bool
|
||||
reveal_type(1 is 2) # revealed: Literal[False]
|
||||
reveal_type(1 is not 7) # revealed: Literal[True]
|
||||
reveal_type(1 <= "" and 0 < 1) # revealed: @Todo | Literal[True]
|
||||
# TODO: should be Unknown, and emit diagnostic, once we check call argument types
|
||||
reveal_type(1 <= "" and 0 < 1) # revealed: bool
|
||||
```
|
||||
|
||||
## Integer instance
|
||||
|
||||
```py
|
||||
# TODO: implement lookup of `__eq__` on typeshed `int` stub.
|
||||
def int_instance() -> int: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(1 == int_instance()) # revealed: @Todo
|
||||
reveal_type(1 == int_instance()) # revealed: bool
|
||||
reveal_type(9 < int_instance()) # revealed: bool
|
||||
reveal_type(int_instance() < int_instance()) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -5,9 +5,9 @@ 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
|
||||
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`
|
||||
|
||||
|
||||
@@ -59,51 +59,51 @@ reveal_type(c >= d) # revealed: Literal[True]
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool: ...
|
||||
def int_instance() -> int: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
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 == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
|
||||
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
|
||||
reveal_type(a == b) # revealed: bool
|
||||
reveal_type(a != b) # revealed: bool
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
```
|
||||
|
||||
#### 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.
|
||||
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[False], once we implement (in)equality for mismatched literals
|
||||
reveal_type(a == b) # revealed: bool
|
||||
|
||||
# TODO: should be Literal[True]
|
||||
reveal_type(a != b) # revealed: @Todo
|
||||
# TODO: should be Literal[True], once we implement (in)equality for mismatched literals
|
||||
reveal_type(a != b) # revealed: bool
|
||||
|
||||
# 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
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
```
|
||||
|
||||
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.
|
||||
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)
|
||||
@@ -145,13 +145,12 @@ class A:
|
||||
|
||||
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
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
```
|
||||
|
||||
### Membership Test Comparisons
|
||||
@@ -159,7 +158,8 @@ reveal_type(a >= a) # revealed: @Todo
|
||||
"Membership Test Comparisons" refers to the operators `in` and `not in`.
|
||||
|
||||
```py
|
||||
def int_instance() -> int: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
a = (1, 2)
|
||||
b = ((3, 4), (1, 2))
|
||||
@@ -172,9 +172,8 @@ 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
|
||||
reveal_type(a in d) # revealed: bool
|
||||
reveal_type(a not in d) # revealed: bool
|
||||
```
|
||||
|
||||
### Identity Comparisons
|
||||
@@ -189,10 +188,10 @@ 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
|
||||
# TODO should be Literal[False] once we implement comparison of mismatched literal types
|
||||
reveal_type(a is b) # revealed: bool
|
||||
# TODO should be Literal[True] once we implement comparison of mismatched literal types
|
||||
reveal_type(a is not b) # revealed: bool
|
||||
|
||||
reveal_type(a is c) # revealed: Literal[False]
|
||||
reveal_type(a is not c) # revealed: Literal[True]
|
||||
|
||||
@@ -52,8 +52,8 @@ 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:
|
||||
With unions on both sides, we need to consider the full cross product of options when building the
|
||||
resulting (union) type:
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
@@ -72,9 +72,9 @@ 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:
|
||||
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
|
||||
def bool_instance() -> bool:
|
||||
|
||||
@@ -10,12 +10,16 @@ reveal_type(a) # revealed: bool
|
||||
b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`"
|
||||
reveal_type(b) # revealed: bool
|
||||
|
||||
c = object() < 5 # error: "Operator `<` is not supported for types `object` and `int`"
|
||||
reveal_type(c) # revealed: Unknown
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `object` and `int`")
|
||||
c = object() < 5
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(c) # revealed: bool
|
||||
|
||||
# TODO should error, need to check if __lt__ signature is valid for right operand
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `int` and `object`")
|
||||
d = 5 < object()
|
||||
# TODO: should be `Unknown`
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(d) # revealed: bool
|
||||
|
||||
flag = bool_instance()
|
||||
@@ -27,5 +31,6 @@ reveal_type(e) # revealed: bool
|
||||
# TODO: should error, need to check if __lt__ signature is valid for right operand
|
||||
# error may be "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`
|
||||
f = (1, 2) < (1, "hello")
|
||||
reveal_type(f) # revealed: @Todo
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(f) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -37,11 +37,11 @@ x = y
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4, 5]
|
||||
|
||||
# revealed: Unbound | Literal[2]
|
||||
# revealed: Literal[2]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(r)
|
||||
|
||||
# revealed: Unbound | Literal[5]
|
||||
# revealed: Literal[5]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(s)
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ match 0:
|
||||
case 2:
|
||||
y = 3
|
||||
|
||||
# revealed: Unbound | Literal[2, 3]
|
||||
# revealed: Literal[2, 3]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(y)
|
||||
```
|
||||
|
||||
@@ -41,7 +41,12 @@ except EXCEPTIONS as f:
|
||||
## Dynamic exception types
|
||||
|
||||
```py
|
||||
def foo(x: type[AttributeError], y: tuple[type[OSError], type[RuntimeError]], z: tuple[type[BaseException], ...]):
|
||||
# TODO: we should not emit these `call-possibly-unbound-method` errors for `tuple.__class_getitem__`
|
||||
def foo(
|
||||
x: type[AttributeError],
|
||||
y: tuple[type[OSError], type[RuntimeError]], # error: [call-possibly-unbound-method]
|
||||
z: tuple[type[BaseException], ...], # error: [call-possibly-unbound-method]
|
||||
):
|
||||
try:
|
||||
help()
|
||||
except x as e:
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
# 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.
|
||||
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].
|
||||
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.
|
||||
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]`).
|
||||
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.
|
||||
*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:
|
||||
@@ -54,9 +47,8 @@ except:
|
||||
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:
|
||||
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:
|
||||
@@ -74,13 +66,12 @@ 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.
|
||||
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:
|
||||
@@ -102,11 +93,9 @@ 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:
|
||||
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`
|
||||
@@ -136,11 +125,10 @@ 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:
|
||||
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`
|
||||
@@ -167,10 +155,9 @@ else:
|
||||
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
|
||||
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
|
||||
@@ -201,10 +188,9 @@ 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]`:
|
||||
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:
|
||||
@@ -223,15 +209,13 @@ finally:
|
||||
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.
|
||||
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.)
|
||||
(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:
|
||||
@@ -252,18 +236,18 @@ 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:
|
||||
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
|
||||
- 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:
|
||||
@@ -296,12 +280,11 @@ finally:
|
||||
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.)
|
||||
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:
|
||||
@@ -377,9 +360,9 @@ 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*.
|
||||
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:
|
||||
@@ -479,15 +462,13 @@ 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.
|
||||
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:
|
||||
@@ -580,8 +561,8 @@ 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:
|
||||
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:
|
||||
|
||||
@@ -16,10 +16,11 @@ class MyBox[T]:
|
||||
def __init__(self, data: T):
|
||||
self.data = data
|
||||
|
||||
# TODO not error (should be subscriptable)
|
||||
box: MyBox[int] = MyBox(5) # error: [non-subscriptable]
|
||||
# TODO error differently (str and int don't unify)
|
||||
wrong_innards: MyBox[int] = MyBox("five") # error: [non-subscriptable]
|
||||
box: MyBox[int] = MyBox(5)
|
||||
|
||||
# TODO should emit a diagnostic here (str is not assignable to int)
|
||||
wrong_innards: MyBox[int] = MyBox("five")
|
||||
|
||||
# TODO reveal int
|
||||
reveal_type(box.data) # revealed: @Todo
|
||||
|
||||
@@ -52,7 +53,8 @@ reveal_type(secure_box.data) # revealed: @Todo
|
||||
|
||||
## 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]): ...`.
|
||||
In type stubs, classes can reference themselves in their base class definitions. For example, in
|
||||
`typeshed`, we have `class str(Sequence[str]): ...`.
|
||||
|
||||
This should hold true even with generics at play.
|
||||
|
||||
|
||||
@@ -12,11 +12,10 @@ if flag:
|
||||
|
||||
x = y # error: [possibly-unresolved-reference]
|
||||
|
||||
# revealed: Unbound | Literal[3]
|
||||
# error: [possibly-unresolved-reference]
|
||||
# revealed: Literal[3]
|
||||
reveal_type(x)
|
||||
|
||||
# revealed: Unbound | Literal[3]
|
||||
# revealed: Literal[3]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(y)
|
||||
```
|
||||
@@ -40,11 +39,10 @@ if flag:
|
||||
y: int = 3
|
||||
x = y # error: [possibly-unresolved-reference]
|
||||
|
||||
# revealed: Unbound | Literal[3]
|
||||
# error: [possibly-unresolved-reference]
|
||||
# revealed: Literal[3]
|
||||
reveal_type(x)
|
||||
|
||||
# revealed: Unbound | Literal[3]
|
||||
# revealed: Literal[3]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(y)
|
||||
```
|
||||
@@ -58,6 +56,24 @@ reveal_type(x) # revealed: Literal[3]
|
||||
reveal_type(y) # revealed: int
|
||||
```
|
||||
|
||||
## Maybe undeclared
|
||||
|
||||
Importing a possibly undeclared name still gives us its declared type:
|
||||
|
||||
```py path=maybe_undeclared.py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if bool_instance():
|
||||
x: int
|
||||
```
|
||||
|
||||
```py
|
||||
from maybe_undeclared import x
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Reimport
|
||||
|
||||
```py path=c.py
|
||||
|
||||
@@ -17,8 +17,8 @@ async def foo():
|
||||
async for x in Iterator():
|
||||
pass
|
||||
|
||||
# TODO: should reveal `Unbound | Unknown` because `__aiter__` is not defined
|
||||
# revealed: Unbound | @Todo
|
||||
# TODO: should reveal `Unknown` because `__aiter__` is not defined
|
||||
# revealed: @Todo
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -40,6 +40,6 @@ async def foo():
|
||||
pass
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
# revealed: Unbound | @Todo
|
||||
# revealed: @Todo
|
||||
reveal_type(x)
|
||||
```
|
||||
|
||||
@@ -14,7 +14,7 @@ class IntIterable:
|
||||
for x in IntIterable():
|
||||
pass
|
||||
|
||||
# revealed: Unbound | int
|
||||
# revealed: int
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -87,7 +87,7 @@ class OldStyleIterable:
|
||||
for x in OldStyleIterable():
|
||||
pass
|
||||
|
||||
# revealed: Unbound | int
|
||||
# revealed: int
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -98,7 +98,7 @@ reveal_type(x)
|
||||
for x in (1, "a", b"foo"):
|
||||
pass
|
||||
|
||||
# revealed: Unbound | Literal[1] | Literal["a"] | Literal[b"foo"]
|
||||
# revealed: Literal[1] | Literal["a"] | Literal[b"foo"]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -120,7 +120,7 @@ class NotIterable:
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
pass
|
||||
|
||||
# revealed: Unbound | Unknown
|
||||
# revealed: Unknown
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -144,3 +144,140 @@ class NotIterable:
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
pass
|
||||
```
|
||||
|
||||
## Union type as iterable
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
class Test2:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterator
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class TestIter2:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter | TestIter2:
|
||||
return TestIter()
|
||||
|
||||
for x in Test():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterable and union type as iterator
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int | Exception:
|
||||
return 42
|
||||
|
||||
class TestIter2:
|
||||
def __next__(self) -> str | tuple[int, int]:
|
||||
return "42"
|
||||
|
||||
class TestIter3:
|
||||
def __next__(self) -> bytes:
|
||||
return b"42"
|
||||
|
||||
class TestIter4:
|
||||
def __next__(self) -> memoryview:
|
||||
return memoryview(b"42")
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter | TestIter2:
|
||||
return TestIter()
|
||||
|
||||
class Test2:
|
||||
def __iter__(self) -> TestIter3 | TestIter4:
|
||||
return TestIter3()
|
||||
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# TODO: we should emit a diagnostic here (it might not be iterable)
|
||||
for x in Test() if coinflip() else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has invalid `__iter__` method
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
class Test2:
|
||||
def __iter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# error: "Object of type `Test | Test2` is not iterable"
|
||||
for x in Test() if coinflip() else Test2():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Union type as iterator where one union element has no `__next__` method
|
||||
|
||||
```py
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
|
||||
class Test:
|
||||
def __iter__(self) -> TestIter | int:
|
||||
return TestIter()
|
||||
|
||||
# error: [not-iterable] "Object of type `Test` is not iterable"
|
||||
for x in Test():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Narrowing in boolean expressions
|
||||
|
||||
In `or` expressions, the right-hand side is evaluated only if the left-hand side is **falsy**. So
|
||||
when the right-hand side is evaluated, we know the left side has failed.
|
||||
|
||||
Similarly, in `and` expressions, the right-hand side is evaluated only if the left-hand side is
|
||||
**truthy**. So when the right-hand side is evaluated, we know the left side has succeeded.
|
||||
|
||||
## Narrowing in `or`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
x is None or reveal_type(x) # revealed: A
|
||||
reveal_type(x) # revealed: A | None
|
||||
```
|
||||
|
||||
## Narrowing in `and`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
x is None and reveal_type(x) # revealed: None
|
||||
reveal_type(x) # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple `and` arms
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
bool_instance() and isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
isinstance(x, A) and bool_instance() and reveal_type(x) # revealed: A
|
||||
reveal_type(x) and isinstance(x, A) and bool_instance() # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple `or` arms
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
bool_instance() or isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
isinstance(x, A) or bool_instance() or reveal_type(x) # revealed: None
|
||||
reveal_type(x) or isinstance(x, A) or bool_instance() # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple predicates
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
|
||||
|
||||
x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Mix of `and` and `or`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
|
||||
|
||||
isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
# Narrowing for conditionals with elif and else
|
||||
|
||||
## Positive contributions become negative in elif-else blocks
|
||||
|
||||
```py
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = int_instance()
|
||||
|
||||
if x == 1:
|
||||
# cannot narrow; could be a subclass of `int`
|
||||
reveal_type(x) # revealed: int
|
||||
elif x == 2:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
elif x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
```
|
||||
|
||||
## Positive contributions become negative in elif-else blocks, with simplification
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x == 1:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
elif x == 2:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## Multiple negative contributions using elif, with simplification
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x != 2:
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
elif x == 3:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
@@ -11,6 +11,8 @@ x = None if flag else 1
|
||||
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
@@ -30,6 +32,8 @@ y = x if flag else None
|
||||
|
||||
if y is x:
|
||||
reveal_type(y) # revealed: A
|
||||
else:
|
||||
reveal_type(y) # revealed: A | None
|
||||
|
||||
reveal_type(y) # revealed: A | None
|
||||
```
|
||||
@@ -50,4 +54,26 @@ reveal_type(y) # revealed: bool
|
||||
if y is x is False: # Interpreted as `(y is x) and (x is False)`
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is not x) or (x is not False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
## `is` in elif clause
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = None if bool_instance() else (1 if bool_instance() else True)
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1] | Literal[True]
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
elif x is True:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -13,6 +13,8 @@ x = None if flag else 1
|
||||
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
@@ -29,13 +31,14 @@ reveal_type(x) # revealed: bool
|
||||
|
||||
if x is not False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: 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.
|
||||
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
|
||||
@@ -43,6 +46,27 @@ y = 345
|
||||
|
||||
if x is not y:
|
||||
reveal_type(x) # revealed: Literal[345]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[345]
|
||||
```
|
||||
|
||||
## `is not` for other types
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
x = A()
|
||||
y = x if bool_instance() else None
|
||||
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: A | None
|
||||
else:
|
||||
reveal_type(y) # revealed: A
|
||||
|
||||
reveal_type(y) # revealed: A | None
|
||||
```
|
||||
|
||||
## `is not` in chained comparisons
|
||||
@@ -63,4 +87,10 @@ reveal_type(y) # revealed: bool
|
||||
if y is not x is not False: # Interpreted as `(y is not x) and (x is not False)`
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is x) or (x is False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
## Multiple negative contributions
|
||||
|
||||
```py
|
||||
def int_instance() -> int: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = int_instance()
|
||||
|
||||
@@ -27,3 +28,29 @@ if x != 1:
|
||||
if x != 2:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## elif-else blocks
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x == 2:
|
||||
# TODO should be `Literal[2]`
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x == 3:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
elif x != 2:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Narrowing for `not` conditionals
|
||||
|
||||
The `not` operator negates a constraint.
|
||||
|
||||
## `not is None`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = None if bool_instance() else 1
|
||||
|
||||
if not x is None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `not isinstance`
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else "a"
|
||||
|
||||
if not isinstance(x, (int)):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
@@ -11,6 +11,9 @@ x = None if flag else 1
|
||||
|
||||
if x != None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
# TODO should be None
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `!=` for other singleton types
|
||||
@@ -24,6 +27,9 @@ x = True if flag else False
|
||||
|
||||
if x != False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
# TODO should be Literal[False]
|
||||
reveal_type(x) # revealed: bool
|
||||
```
|
||||
|
||||
## `x != y` where `y` is of literal type
|
||||
@@ -54,6 +60,25 @@ C = A if flag else B
|
||||
|
||||
if C != A:
|
||||
reveal_type(C) # revealed: Literal[B]
|
||||
else:
|
||||
# TODO should be Literal[A]
|
||||
reveal_type(C) # revealed: Literal[A, B]
|
||||
```
|
||||
|
||||
## `x != y` where `y` has multiple single-valued options
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2
|
||||
y = 2 if bool_instance() else 3
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## `!=` for non-single-valued types
|
||||
@@ -74,3 +99,21 @@ y = int_instance()
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Mix of single-valued and non-single-valued types
|
||||
|
||||
```py
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2
|
||||
y = 2 if bool_instance() else int_instance()
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
@@ -26,9 +26,8 @@ if isinstance(x, (int, object)):
|
||||
|
||||
## `classinfo` is a tuple of types
|
||||
|
||||
Note: `isinstance(x, (int, str))` should not be confused with
|
||||
`isinstance(x, tuple[(int, str)])`. The former is equivalent to
|
||||
`isinstance(x, int | str)`:
|
||||
Note: `isinstance(x, (int, str))` should not be confused with `isinstance(x, tuple[(int, str)])`.
|
||||
The former is equivalent to `isinstance(x, int | str)`:
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
@@ -40,6 +39,8 @@ x = 1 if flag else "a"
|
||||
|
||||
if isinstance(x, (int, str)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if isinstance(x, (int, bytes)):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
@@ -51,6 +52,8 @@ if isinstance(x, (bytes, str)):
|
||||
# one of the possibilities:
|
||||
if isinstance(x, (int, object)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
y = 1 if flag1 else "a" if flag2 else b"b"
|
||||
if isinstance(y, (int, str)):
|
||||
@@ -75,6 +78,8 @@ x = 1 if flag else "a"
|
||||
|
||||
if isinstance(x, (bool, (bytes, int))):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
## Class types
|
||||
@@ -82,6 +87,7 @@ if isinstance(x, (bool, (bytes, int))):
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
@@ -91,6 +97,16 @@ if isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
if isinstance(x, B):
|
||||
reveal_type(x) # revealed: A & B
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~B
|
||||
|
||||
if isinstance(x, (A, B)):
|
||||
reveal_type(x) # revealed: A | B
|
||||
elif isinstance(x, (A, C)):
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
else:
|
||||
# TODO: Should be simplified to ~A & ~B & ~C
|
||||
reveal_type(x) # revealed: object & ~A & ~B & ~C
|
||||
```
|
||||
|
||||
## No narrowing for instances of `builtins.type`
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# Implicit globals from `types.ModuleType`
|
||||
|
||||
## Implicit `ModuleType` globals
|
||||
|
||||
All modules are instances of `types.ModuleType`. If a name can't be found in any local or global
|
||||
scope, we look it up as an attribute on `types.ModuleType` in typeshed before deciding that the name
|
||||
is unbound.
|
||||
|
||||
```py
|
||||
reveal_type(__name__) # revealed: str
|
||||
reveal_type(__file__) # revealed: str | None
|
||||
reveal_type(__loader__) # revealed: LoaderProtocol | None
|
||||
reveal_type(__package__) # revealed: str | None
|
||||
reveal_type(__doc__) # revealed: str | None
|
||||
|
||||
# TODO: Should be `ModuleSpec | None`
|
||||
# (needs support for `*` imports)
|
||||
reveal_type(__spec__) # revealed: Unknown | None
|
||||
|
||||
# TODO: generics
|
||||
reveal_type(__path__) # revealed: @Todo
|
||||
|
||||
class X:
|
||||
reveal_type(__name__) # revealed: str
|
||||
|
||||
def foo():
|
||||
reveal_type(__name__) # revealed: str
|
||||
```
|
||||
|
||||
However, three attributes on `types.ModuleType` are not present as implicit module globals; these
|
||||
are excluded:
|
||||
|
||||
```py path=unbound_dunders.py
|
||||
# error: [unresolved-reference]
|
||||
# revealed: Unknown
|
||||
reveal_type(__getattr__)
|
||||
|
||||
# error: [unresolved-reference]
|
||||
# revealed: Unknown
|
||||
reveal_type(__dict__)
|
||||
|
||||
# error: [unresolved-reference]
|
||||
# revealed: Unknown
|
||||
reveal_type(__init__)
|
||||
```
|
||||
|
||||
## Accessed as attributes
|
||||
|
||||
`ModuleType` attributes can also be accessed as attributes on module-literal types. The special
|
||||
attributes `__dict__` and `__init__`, and all attributes on `builtins.object`, can also be accessed
|
||||
as attributes on module-literal types, despite the fact that these are inaccessible as globals from
|
||||
inside the module:
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
reveal_type(typing.__name__) # revealed: str
|
||||
reveal_type(typing.__init__) # revealed: Literal[__init__]
|
||||
|
||||
# These come from `builtins.object`, not `types.ModuleType`:
|
||||
# TODO: we don't currently understand `types.ModuleType` as inheriting from `object`;
|
||||
# these should not reveal `Unknown`:
|
||||
reveal_type(typing.__eq__) # revealed: Unknown
|
||||
reveal_type(typing.__class__) # revealed: Unknown
|
||||
reveal_type(typing.__module__) # revealed: Unknown
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties and generics;
|
||||
# should be `dict[str, Any]`
|
||||
reveal_type(typing.__dict__) # revealed: @Todo
|
||||
```
|
||||
|
||||
Typeshed includes a fake `__getattr__` method in the stub for `types.ModuleType` to help out with
|
||||
dynamic imports; but we ignore that for module-literal types where we know exactly which module
|
||||
we're dealing with:
|
||||
|
||||
```py path=__getattr__.py
|
||||
import typing
|
||||
|
||||
reveal_type(typing.__getattr__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `types.ModuleType.__dict__` takes precedence over global variable `__dict__`
|
||||
|
||||
It's impossible to override the `__dict__` attribute of `types.ModuleType` instances from inside the
|
||||
module; we should prioritise the attribute in the `types.ModuleType` stub over a variable named
|
||||
`__dict__` in the module's global namespace:
|
||||
|
||||
```py path=foo.py
|
||||
__dict__ = "foo"
|
||||
|
||||
reveal_type(__dict__) # revealed: Literal["foo"]
|
||||
```
|
||||
|
||||
```py path=bar.py
|
||||
import foo
|
||||
from foo import __dict__ as foo_dict
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties, and generics;
|
||||
# should be `dict[str, Any]` for both of these:
|
||||
reveal_type(foo.__dict__) # revealed: @Todo
|
||||
reveal_type(foo_dict) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Conditionally global or `ModuleType` attribute
|
||||
|
||||
Attributes overridden in the module namespace take priority. If a builtin name is conditionally
|
||||
defined as a global, however, a name lookup should union the `ModuleType` type with the
|
||||
conditionally defined type:
|
||||
|
||||
```py
|
||||
__file__ = 42
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
if returns_bool():
|
||||
__name__ = 1
|
||||
|
||||
reveal_type(__file__) # revealed: Literal[42]
|
||||
reveal_type(__name__) # revealed: Literal[1] | str
|
||||
```
|
||||
|
||||
## Conditionally global or `ModuleType` attribute, with annotation
|
||||
|
||||
The same is true if the name is annotated:
|
||||
|
||||
```py
|
||||
__file__: int = 42
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
if returns_bool():
|
||||
__name__: int = 1
|
||||
|
||||
reveal_type(__file__) # revealed: Literal[42]
|
||||
reveal_type(__name__) # revealed: Literal[1] | str
|
||||
```
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
## Parameter
|
||||
|
||||
Parameter `x` of type `str` is shadowed and reassigned with a new `int` value inside the function. No diagnostics should be generated.
|
||||
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):
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
## 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]): ...`.
|
||||
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): ...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bytes subscript
|
||||
# Bytes subscripts
|
||||
|
||||
## Simple
|
||||
## Indexing
|
||||
|
||||
```py
|
||||
b = b"\x00abc\xff"
|
||||
@@ -21,14 +21,37 @@ reveal_type(x) # revealed: Unknown
|
||||
|
||||
y = b[-6] # error: [index-out-of-bounds] "Index -6 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5"
|
||||
reveal_type(y) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Function return
|
||||
|
||||
```py
|
||||
def int_instance() -> int: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
a = b"abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(a) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
```py
|
||||
b = b"\x00abc\xff"
|
||||
|
||||
reveal_type(b[0:2]) # revealed: Literal[b"\x00a"]
|
||||
reveal_type(b[-3:]) # revealed: Literal[b"bc\xff"]
|
||||
|
||||
b[0:4:0] # error: [zero-stepsize-in-slice]
|
||||
b[:4:0] # error: [zero-stepsize-in-slice]
|
||||
b[0::0] # error: [zero-stepsize-in-slice]
|
||||
b[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def int_instance() -> int: ...
|
||||
|
||||
byte_slice1 = b[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice1) # revealed: @Todo
|
||||
|
||||
def bytes_instance() -> bytes: ...
|
||||
|
||||
byte_slice2 = bytes_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice2) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -68,8 +68,8 @@ if flag:
|
||||
else:
|
||||
class Spam: ...
|
||||
|
||||
# error: [call-non-callable] "Method `__class_getitem__` of type `Literal[__class_getitem__] | Unbound` is not callable on object of type `Literal[Spam, Spam]`"
|
||||
# revealed: str | Unknown
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound"
|
||||
# revealed: str
|
||||
reveal_type(Spam[42])
|
||||
```
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ 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.
|
||||
In assignment, we might also have a named assignment. This should also get type checked.
|
||||
|
||||
```py
|
||||
x = [1, 2, 3]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Stepsize zero in slices
|
||||
|
||||
We raise a `zero-stepsize-in-slice` diagnostic when trying to slice a literal string, bytes, or
|
||||
tuple with a step size of zero (see tests in `string.md`, `bytes.md` and `tuple.md`). But we don't
|
||||
want to raise this diagnostic when slicing a custom type:
|
||||
|
||||
```py
|
||||
class MySequence:
|
||||
def __getitem__(self, s: slice) -> int:
|
||||
return 0
|
||||
|
||||
MySequence()[0:1:0] # No error
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# Subscript on strings
|
||||
# String subscripts
|
||||
|
||||
## Simple
|
||||
## Indexing
|
||||
|
||||
```py
|
||||
s = "abcde"
|
||||
@@ -18,14 +18,82 @@ reveal_type(a) # revealed: Unknown
|
||||
|
||||
b = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Function return
|
||||
|
||||
```py
|
||||
def int_instance() -> int: ...
|
||||
|
||||
a = "abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(a) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
```py
|
||||
s = "abcde"
|
||||
|
||||
reveal_type(s[0:0]) # revealed: Literal[""]
|
||||
reveal_type(s[0:1]) # revealed: Literal["a"]
|
||||
reveal_type(s[0:2]) # revealed: Literal["ab"]
|
||||
reveal_type(s[0:5]) # revealed: Literal["abcde"]
|
||||
reveal_type(s[0:6]) # revealed: Literal["abcde"]
|
||||
reveal_type(s[1:3]) # revealed: Literal["bc"]
|
||||
|
||||
reveal_type(s[-3:5]) # revealed: Literal["cde"]
|
||||
reveal_type(s[-4:-2]) # revealed: Literal["bc"]
|
||||
reveal_type(s[-10:10]) # revealed: Literal["abcde"]
|
||||
|
||||
reveal_type(s[0:]) # revealed: Literal["abcde"]
|
||||
reveal_type(s[2:]) # revealed: Literal["cde"]
|
||||
reveal_type(s[5:]) # revealed: Literal[""]
|
||||
reveal_type(s[:2]) # revealed: Literal["ab"]
|
||||
reveal_type(s[:0]) # revealed: Literal[""]
|
||||
reveal_type(s[:2]) # revealed: Literal["ab"]
|
||||
reveal_type(s[:10]) # revealed: Literal["abcde"]
|
||||
reveal_type(s[:]) # revealed: Literal["abcde"]
|
||||
|
||||
reveal_type(s[::-1]) # revealed: Literal["edcba"]
|
||||
reveal_type(s[::2]) # revealed: Literal["ace"]
|
||||
reveal_type(s[-2:-5:-1]) # revealed: Literal["dcb"]
|
||||
reveal_type(s[::-2]) # revealed: Literal["eca"]
|
||||
reveal_type(s[-1::-3]) # revealed: Literal["eb"]
|
||||
|
||||
reveal_type(s[None:2:None]) # revealed: Literal["ab"]
|
||||
reveal_type(s[1:None:1]) # revealed: Literal["bcde"]
|
||||
reveal_type(s[None:None:None]) # revealed: Literal["abcde"]
|
||||
|
||||
start = 1
|
||||
stop = None
|
||||
step = 2
|
||||
reveal_type(s[start:stop:step]) # revealed: Literal["bd"]
|
||||
|
||||
reveal_type(s[False:True]) # revealed: Literal["a"]
|
||||
reveal_type(s[True:3]) # revealed: Literal["bc"]
|
||||
|
||||
s[0:4:0] # error: [zero-stepsize-in-slice]
|
||||
s[:4:0] # error: [zero-stepsize-in-slice]
|
||||
s[0::0] # error: [zero-stepsize-in-slice]
|
||||
s[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def int_instance() -> int: ...
|
||||
|
||||
substring1 = s[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `LiteralString`
|
||||
reveal_type(substring1) # revealed: @Todo
|
||||
|
||||
def str_instance() -> str: ...
|
||||
|
||||
substring2 = str_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(substring2) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
```py
|
||||
# TODO: It would be great if we raised an error here. This can be done once
|
||||
# we have support for overloads and generics, and once typeshed has a more
|
||||
# precise annotation for `str.__getitem__`, that makes use of the generic
|
||||
# `slice[..]` type. We could then infer `slice[str, str]` here and see that
|
||||
# it doesn't match the signature of `str.__getitem__`.
|
||||
"foo"["bar":"baz"]
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tuple subscripts
|
||||
|
||||
## Basic
|
||||
## Indexing
|
||||
|
||||
```py
|
||||
t = (1, "a", "b")
|
||||
@@ -10,9 +10,66 @@ reveal_type(t[1]) # revealed: Literal["a"]
|
||||
reveal_type(t[-1]) # revealed: Literal["b"]
|
||||
reveal_type(t[-2]) # revealed: Literal["a"]
|
||||
|
||||
reveal_type(t[False]) # revealed: Literal[1]
|
||||
reveal_type(t[True]) # revealed: Literal["a"]
|
||||
|
||||
a = t[4] # error: [index-out-of-bounds]
|
||||
reveal_type(a) # revealed: Unknown
|
||||
|
||||
b = t[-4] # error: [index-out-of-bounds]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
```py
|
||||
t = (1, "a", None, b"b")
|
||||
|
||||
reveal_type(t[0:0]) # revealed: tuple[()]
|
||||
reveal_type(t[0:1]) # revealed: tuple[Literal[1]]
|
||||
reveal_type(t[0:2]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||
reveal_type(t[0:4]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
reveal_type(t[0:5]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
reveal_type(t[1:3]) # revealed: tuple[Literal["a"], None]
|
||||
|
||||
reveal_type(t[-2:4]) # revealed: tuple[None, Literal[b"b"]]
|
||||
reveal_type(t[-3:-1]) # revealed: tuple[Literal["a"], None]
|
||||
reveal_type(t[-10:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
|
||||
reveal_type(t[0:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
reveal_type(t[2:]) # revealed: tuple[None, Literal[b"b"]]
|
||||
reveal_type(t[4:]) # revealed: tuple[()]
|
||||
reveal_type(t[:0]) # revealed: tuple[()]
|
||||
reveal_type(t[:2]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||
reveal_type(t[:10]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
reveal_type(t[:]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
|
||||
reveal_type(t[::-1]) # revealed: tuple[Literal[b"b"], None, Literal["a"], Literal[1]]
|
||||
reveal_type(t[::2]) # revealed: tuple[Literal[1], None]
|
||||
reveal_type(t[-2:-5:-1]) # revealed: tuple[None, Literal["a"], Literal[1]]
|
||||
reveal_type(t[::-2]) # revealed: tuple[Literal[b"b"], Literal["a"]]
|
||||
reveal_type(t[-1::-3]) # revealed: tuple[Literal[b"b"], Literal[1]]
|
||||
|
||||
reveal_type(t[None:2:None]) # revealed: tuple[Literal[1], Literal["a"]]
|
||||
reveal_type(t[1:None:1]) # revealed: tuple[Literal["a"], None, Literal[b"b"]]
|
||||
reveal_type(t[None:None:None]) # revealed: tuple[Literal[1], Literal["a"], None, Literal[b"b"]]
|
||||
|
||||
start = 1
|
||||
stop = None
|
||||
step = 2
|
||||
reveal_type(t[start:stop:step]) # revealed: tuple[Literal["a"], Literal[b"b"]]
|
||||
|
||||
reveal_type(t[False:True]) # revealed: tuple[Literal[1]]
|
||||
reveal_type(t[True:3]) # revealed: tuple[Literal["a"], None]
|
||||
|
||||
t[0:4:0] # error: [zero-stepsize-in-slice]
|
||||
t[:4:0] # error: [zero-stepsize-in-slice]
|
||||
t[0::0] # error: [zero-stepsize-in-slice]
|
||||
t[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def int_instance() -> int: ...
|
||||
|
||||
tuple_slice = t[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||
reveal_type(tuple_slice) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -81,8 +81,7 @@ reveal_type(b) # revealed: Literal[2]
|
||||
|
||||
```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"
|
||||
[a, *b, c, d] = (1, 2)
|
||||
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
|
||||
@@ -93,7 +92,7 @@ reveal_type(d) # revealed: Unknown
|
||||
### Starred expression (2)
|
||||
|
||||
```py
|
||||
[a, *b, c] = (1, 2) # error: "Object of type `None` is not iterable"
|
||||
[a, *b, c] = (1, 2)
|
||||
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
|
||||
@@ -103,8 +102,7 @@ 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"
|
||||
[a, *b, c] = (1, 2, 3)
|
||||
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
|
||||
@@ -114,8 +112,7 @@ 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"
|
||||
[a, *b, c, d] = (1, 2, 3, 4, 5, 6)
|
||||
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
|
||||
@@ -126,19 +123,31 @@ 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"
|
||||
[a, b, *c] = (1, 2, 3, 4)
|
||||
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
|
||||
```
|
||||
|
||||
### Starred expression (6)
|
||||
|
||||
```py
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, b, c, *d, e, f) = (1,)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: @Todo
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
|
||||
### 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.
|
||||
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"
|
||||
@@ -215,8 +224,7 @@ reveal_type(b) # revealed: LiteralString
|
||||
|
||||
```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"
|
||||
(a, *b, c, d) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo
|
||||
@@ -227,7 +235,7 @@ reveal_type(d) # revealed: Unknown
|
||||
### Starred expression (2)
|
||||
|
||||
```py
|
||||
(a, *b, c) = "ab" # error: "Object of type `None` is not iterable"
|
||||
(a, *b, c) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo
|
||||
@@ -237,8 +245,7 @@ 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"
|
||||
(a, *b, c) = "abc"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo
|
||||
@@ -248,8 +255,7 @@ 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"
|
||||
(a, *b, c, d) = "abcdef"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo
|
||||
@@ -260,8 +266,7 @@ 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"
|
||||
(a, b, *c) = "abcd"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
# TODO: Should be list[int] once support for assigning to starred expression is added
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Async with statements
|
||||
|
||||
## Basic `async with` statement
|
||||
|
||||
The type of the target variable in a `with` statement should be the return type from the context
|
||||
manager's `__aenter__` method. However, `async with` statements aren't supported yet. This test
|
||||
asserts that it doesn't emit any context manager-related errors.
|
||||
|
||||
```py
|
||||
class Target: ...
|
||||
|
||||
class Manager:
|
||||
async def __aenter__(self) -> Target:
|
||||
return Target()
|
||||
|
||||
async def __aexit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
async def test():
|
||||
async with Manager() as f:
|
||||
reveal_type(f) # revealed: @Todo
|
||||
```
|
||||
141
crates/red_knot_python_semantic/resources/mdtest/with/with.md
Normal file
141
crates/red_knot_python_semantic/resources/mdtest/with/with.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# With statements
|
||||
|
||||
## Basic `with` statement
|
||||
|
||||
The type of the target variable in a `with` statement is the return type from the context manager's
|
||||
`__enter__` method.
|
||||
|
||||
```py
|
||||
class Target: ...
|
||||
|
||||
class Manager:
|
||||
def __enter__(self) -> Target:
|
||||
return Target()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
with Manager() as f:
|
||||
reveal_type(f) # revealed: Target
|
||||
```
|
||||
|
||||
## Union context manager
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
class Manager2:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
context_expr = Manager1() if coinflip() else Manager2()
|
||||
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str | int
|
||||
```
|
||||
|
||||
## Context manager without an `__enter__` or `__exit__` method
|
||||
|
||||
```py
|
||||
class Manager: ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it doesn't implement `__enter__` and `__exit__`"
|
||||
with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager without an `__enter__` method
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __exit__(self, exc_tpe, exc_value, traceback): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it doesn't implement `__enter__`"
|
||||
with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager without an `__exit__` method
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __enter__(self): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because it doesn't implement `__exit__`"
|
||||
with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager with non-callable `__enter__` attribute
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
__enter__ = 42
|
||||
|
||||
def __exit__(self, exc_tpe, exc_value, traceback): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` of type `Literal[42]` is not callable"
|
||||
with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context manager with non-callable `__exit__` attribute
|
||||
|
||||
```py
|
||||
class Manager:
|
||||
def __enter__(self) -> Self: ...
|
||||
|
||||
__exit__ = 32
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__exit__` of type `Literal[32]` is not callable"
|
||||
with Manager():
|
||||
...
|
||||
```
|
||||
|
||||
## Context expression with possibly-unbound union variants
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
class NotAContextManager: ...
|
||||
|
||||
context_expr = Manager1() if coinflip() else NotAContextManager()
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__exit__` is possibly unbound"
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
## Context expression with "sometimes" callable `__enter__` method
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
class Manager:
|
||||
if coinflip():
|
||||
def __enter__(self) -> str:
|
||||
return "abcd"
|
||||
|
||||
def __exit__(self, *args): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
with Manager() as f:
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
@@ -20,6 +20,7 @@ pub mod semantic_index;
|
||||
mod semantic_model;
|
||||
pub(crate) mod site_packages;
|
||||
mod stdlib;
|
||||
pub(crate) mod symbol;
|
||||
pub mod types;
|
||||
mod util;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use ruff_index::IndexVec;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
|
||||
use ruff_python_ast::AnyParameterRef;
|
||||
use ruff_python_ast::{AnyParameterRef, BoolOp, Expr};
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
@@ -27,7 +27,7 @@ use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::Db;
|
||||
|
||||
use super::constraint::{Constraint, PatternConstraint};
|
||||
use super::constraint::{Constraint, ConstraintNode, PatternConstraint};
|
||||
use super::definition::{
|
||||
AssignmentKind, DefinitionCategory, ExceptHandlerDefinitionNodeRef,
|
||||
MatchPatternDefinitionNodeRef, WithItemDefinitionNodeRef,
|
||||
@@ -195,6 +195,10 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_symbol_table().mark_symbol_bound(id);
|
||||
}
|
||||
|
||||
fn mark_symbol_declared(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_declared(id);
|
||||
}
|
||||
|
||||
fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_used(id);
|
||||
}
|
||||
@@ -226,6 +230,9 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
if category.is_binding() {
|
||||
self.mark_symbol_bound(symbol);
|
||||
}
|
||||
if category.is_declaration() {
|
||||
self.mark_symbol_declared(symbol);
|
||||
}
|
||||
|
||||
let use_def = self.current_use_def_map_mut();
|
||||
match category {
|
||||
@@ -243,12 +250,30 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
definition
|
||||
}
|
||||
|
||||
fn add_expression_constraint(&mut self, constraint_node: &ast::Expr) -> Expression<'db> {
|
||||
let expression = self.add_standalone_expression(constraint_node);
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint(Constraint::Expression(expression));
|
||||
fn record_expression_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> {
|
||||
let constraint = self.build_constraint(constraint_node);
|
||||
self.record_constraint(constraint);
|
||||
constraint
|
||||
}
|
||||
|
||||
expression
|
||||
fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
self.current_use_def_map_mut().record_constraint(constraint);
|
||||
}
|
||||
|
||||
fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> {
|
||||
let expression = self.add_standalone_expression(constraint_node);
|
||||
Constraint {
|
||||
node: ConstraintNode::Expression(expression),
|
||||
is_positive: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint(Constraint {
|
||||
node: constraint.node,
|
||||
is_positive: false,
|
||||
});
|
||||
}
|
||||
|
||||
fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) {
|
||||
@@ -285,7 +310,10 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
countme::Count::default(),
|
||||
);
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint(Constraint::Pattern(pattern_constraint));
|
||||
.record_constraint(Constraint {
|
||||
node: ConstraintNode::Pattern(pattern_constraint),
|
||||
is_positive: true,
|
||||
});
|
||||
pattern_constraint
|
||||
}
|
||||
|
||||
@@ -338,6 +366,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
// note that the "bound" on the typevar is a totally different thing than whether
|
||||
// or not a name is "bound" by a typevar declaration; the latter is always true.
|
||||
self.mark_symbol_bound(symbol);
|
||||
self.mark_symbol_declared(symbol);
|
||||
if let Some(bounds) = bound {
|
||||
self.visit_expr(bounds);
|
||||
}
|
||||
@@ -639,7 +668,8 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
self.add_expression_constraint(&node.test);
|
||||
let constraint = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![constraint];
|
||||
self.visit_body(&node.body);
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
for clause in &node.elif_else_clauses {
|
||||
@@ -649,7 +679,14 @@ where
|
||||
// we can only take an elif/else branch if none of the previous ones were
|
||||
// taken, so the block entry state is always `pre_if`
|
||||
self.flow_restore(pre_if.clone());
|
||||
self.visit_elif_else_clause(clause);
|
||||
for constraint in &constraints {
|
||||
self.record_negated_constraint(*constraint);
|
||||
}
|
||||
if let Some(elif_test) = &clause.test {
|
||||
self.visit_expr(elif_test);
|
||||
constraints.push(self.record_expression_constraint(elif_test));
|
||||
}
|
||||
self.visit_body(&clause.body);
|
||||
}
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state);
|
||||
@@ -697,12 +734,20 @@ where
|
||||
self.flow_merge(break_state);
|
||||
}
|
||||
}
|
||||
ast::Stmt::With(ast::StmtWith { items, body, .. }) => {
|
||||
ast::Stmt::With(ast::StmtWith {
|
||||
items,
|
||||
body,
|
||||
is_async,
|
||||
..
|
||||
}) => {
|
||||
for item in items {
|
||||
self.visit_expr(&item.context_expr);
|
||||
if let Some(optional_vars) = item.optional_vars.as_deref() {
|
||||
self.add_standalone_expression(&item.context_expr);
|
||||
self.push_assignment(item.into());
|
||||
self.push_assignment(CurrentAssignment::WithItem {
|
||||
item,
|
||||
is_async: *is_async,
|
||||
});
|
||||
self.visit_expr(optional_vars);
|
||||
self.pop_assignment();
|
||||
}
|
||||
@@ -918,6 +963,12 @@ where
|
||||
};
|
||||
let symbol = self.add_symbol(id.clone());
|
||||
|
||||
if is_use {
|
||||
self.mark_symbol_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
self.current_use_def_map_mut().record_use(symbol, use_id);
|
||||
}
|
||||
|
||||
if is_definition {
|
||||
match self.current_assignment().copied() {
|
||||
Some(CurrentAssignment::Assign {
|
||||
@@ -968,12 +1019,13 @@ where
|
||||
},
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::WithItem(with_item)) => {
|
||||
Some(CurrentAssignment::WithItem { item, is_async }) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
WithItemDefinitionNodeRef {
|
||||
node: with_item,
|
||||
node: item,
|
||||
target: name_node,
|
||||
is_async,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -981,12 +1033,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if is_use {
|
||||
self.mark_symbol_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
self.current_use_def_map_mut().record_use(symbol, use_id);
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
ast::Expr::Named(node) => {
|
||||
@@ -1084,6 +1130,33 @@ where
|
||||
},
|
||||
);
|
||||
}
|
||||
ast::Expr::BoolOp(ast::ExprBoolOp {
|
||||
values,
|
||||
range: _,
|
||||
op,
|
||||
}) => {
|
||||
// TODO detect statically known truthy or falsy values (via type inference, not naive
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
let mut snapshots = vec![];
|
||||
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
self.visit_expr(value);
|
||||
// In the last value we don't need to take a snapshot nor add a constraint
|
||||
if index < values.len() - 1 {
|
||||
// Snapshot is taken after visiting the expression but before adding the constraint.
|
||||
snapshots.push(self.flow_snapshot());
|
||||
let constraint = self.build_constraint(value);
|
||||
match op {
|
||||
BoolOp::And => self.record_constraint(constraint),
|
||||
BoolOp::Or => self.record_negated_constraint(constraint),
|
||||
}
|
||||
}
|
||||
}
|
||||
for snapshot in snapshots {
|
||||
self.flow_merge(snapshot);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
@@ -1168,7 +1241,10 @@ enum CurrentAssignment<'a> {
|
||||
node: &'a ast::Comprehension,
|
||||
first: bool,
|
||||
},
|
||||
WithItem(&'a ast::WithItem),
|
||||
WithItem {
|
||||
item: &'a ast::WithItem,
|
||||
is_async: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtAnnAssign> for CurrentAssignment<'a> {
|
||||
@@ -1195,12 +1271,6 @@ impl<'a> From<&'a ast::ExprNamed> for CurrentAssignment<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::WithItem> for CurrentAssignment<'a> {
|
||||
fn from(value: &'a ast::WithItem) -> Self {
|
||||
Self::WithItem(value)
|
||||
}
|
||||
}
|
||||
|
||||
struct CurrentMatchCase<'a> {
|
||||
/// The pattern that's part of the current match case.
|
||||
pattern: &'a ast::Pattern,
|
||||
|
||||
@@ -7,7 +7,13 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Constraint<'db> {
|
||||
pub(crate) struct Constraint<'db> {
|
||||
pub(crate) node: ConstraintNode<'db>,
|
||||
pub(crate) is_positive: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ConstraintNode<'db> {
|
||||
Expression(Expression<'db>),
|
||||
Pattern(PatternConstraint<'db>),
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
|
||||
pub(crate) struct WithItemDefinitionNodeRef<'a> {
|
||||
pub(crate) node: &'a ast::WithItem,
|
||||
pub(crate) target: &'a ast::ExprName,
|
||||
pub(crate) is_async: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -277,12 +278,15 @@ impl DefinitionNodeRef<'_> {
|
||||
DefinitionKind::ParameterWithDefault(AstNodeRef::new(parsed, parameter))
|
||||
}
|
||||
},
|
||||
DefinitionNodeRef::WithItem(WithItemDefinitionNodeRef { node, target }) => {
|
||||
DefinitionKind::WithItem(WithItemDefinitionKind {
|
||||
node: AstNodeRef::new(parsed.clone(), node),
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::WithItem(WithItemDefinitionNodeRef {
|
||||
node,
|
||||
target,
|
||||
is_async,
|
||||
}) => DefinitionKind::WithItem(WithItemDefinitionKind {
|
||||
node: AstNodeRef::new(parsed.clone(), node),
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
is_async,
|
||||
}),
|
||||
DefinitionNodeRef::MatchPattern(MatchPatternDefinitionNodeRef {
|
||||
pattern,
|
||||
identifier,
|
||||
@@ -329,7 +333,11 @@ impl DefinitionNodeRef<'_> {
|
||||
ast::AnyParameterRef::Variadic(parameter) => parameter.into(),
|
||||
ast::AnyParameterRef::NonVariadic(parameter) => parameter.into(),
|
||||
},
|
||||
Self::WithItem(WithItemDefinitionNodeRef { node: _, target }) => target.into(),
|
||||
Self::WithItem(WithItemDefinitionNodeRef {
|
||||
node: _,
|
||||
target,
|
||||
is_async: _,
|
||||
}) => target.into(),
|
||||
Self::MatchPattern(MatchPatternDefinitionNodeRef { identifier, .. }) => {
|
||||
identifier.into()
|
||||
}
|
||||
@@ -534,6 +542,7 @@ pub enum AssignmentKind {
|
||||
pub struct WithItemDefinitionKind {
|
||||
node: AstNodeRef<ast::WithItem>,
|
||||
target: AstNodeRef<ast::ExprName>,
|
||||
is_async: bool,
|
||||
}
|
||||
|
||||
impl WithItemDefinitionKind {
|
||||
@@ -544,6 +553,10 @@ impl WithItemDefinitionKind {
|
||||
pub(crate) fn target(&self) -> &ast::ExprName {
|
||||
self.target.node()
|
||||
}
|
||||
|
||||
pub(crate) const fn is_async(&self) -> bool {
|
||||
self.is_async
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -47,17 +47,27 @@ impl Symbol {
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_BOUND)
|
||||
}
|
||||
|
||||
/// Is the symbol declared in its containing scope?
|
||||
pub fn is_declared(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_DECLARED)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Flags that can be queried to obtain information about a symbol in a given scope.
|
||||
///
|
||||
/// See the doc-comment at the top of [`super::use_def`] for explanations of what it
|
||||
/// means for a symbol to be *bound* as opposed to *declared*.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
struct SymbolFlags: u8 {
|
||||
const IS_USED = 1 << 0;
|
||||
const IS_BOUND = 1 << 1;
|
||||
const IS_BOUND = 1 << 1;
|
||||
const IS_DECLARED = 1 << 2;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_GLOBAL = 1 << 2;
|
||||
const MARKED_GLOBAL = 1 << 3;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_NONLOCAL = 1 << 3;
|
||||
const MARKED_NONLOCAL = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +308,10 @@ impl SymbolTableBuilder {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_BOUND);
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_declared(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_DECLARED);
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_USED);
|
||||
}
|
||||
|
||||
@@ -228,6 +228,7 @@ use self::symbol_state::{
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::symbol::Boundness;
|
||||
use ruff_index::IndexVec;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -274,8 +275,12 @@ impl<'db> UseDefMap<'db> {
|
||||
self.bindings_iterator(&self.bindings_by_use[use_id])
|
||||
}
|
||||
|
||||
pub(crate) fn use_may_be_unbound(&self, use_id: ScopedUseId) -> bool {
|
||||
self.bindings_by_use[use_id].may_be_unbound()
|
||||
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
|
||||
if self.bindings_by_use[use_id].may_be_unbound() {
|
||||
Boundness::MayBeUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn public_bindings(
|
||||
@@ -285,8 +290,12 @@ impl<'db> UseDefMap<'db> {
|
||||
self.bindings_iterator(self.public_symbols[symbol].bindings())
|
||||
}
|
||||
|
||||
pub(crate) fn public_may_be_unbound(&self, symbol: ScopedSymbolId) -> bool {
|
||||
self.public_symbols[symbol].may_be_unbound()
|
||||
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
|
||||
if self.public_symbols[symbol].may_be_unbound() {
|
||||
Boundness::MayBeUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_declaration(
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{resolve_module, Module};
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::{binding_ty, global_symbol_ty, infer_scope_types, Type};
|
||||
use crate::types::{binding_ty, infer_scope_types, Type};
|
||||
use crate::Db;
|
||||
|
||||
pub struct SemanticModel<'db> {
|
||||
@@ -38,10 +38,6 @@ impl<'db> SemanticModel<'db> {
|
||||
pub fn resolve_module(&self, module_name: &ModuleName) -> Option<Module> {
|
||||
resolve_module(self.db, module_name)
|
||||
}
|
||||
|
||||
pub fn global_symbol_ty(&self, module: &Module, symbol_name: &str) -> Type<'db> {
|
||||
global_symbol_ty(self.db, module.file(), symbol_name)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasTy {
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::semantic_index::global_scope;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{global_symbol_ty, Type};
|
||||
use crate::symbol::Symbol;
|
||||
use crate::types::global_symbol;
|
||||
use crate::Db;
|
||||
|
||||
/// Enumeration of various core stdlib modules, for which we have dedicated Salsa queries.
|
||||
@@ -10,6 +11,9 @@ use crate::Db;
|
||||
enum CoreStdlibModule {
|
||||
Builtins,
|
||||
Types,
|
||||
// the Typing enum is currently only used in tests
|
||||
#[allow(dead_code)]
|
||||
Typing,
|
||||
Typeshed,
|
||||
TypingExtensions,
|
||||
}
|
||||
@@ -19,6 +23,7 @@ impl CoreStdlibModule {
|
||||
let module_name = match self {
|
||||
Self::Builtins => "builtins",
|
||||
Self::Types => "types",
|
||||
Self::Typing => "typing",
|
||||
Self::Typeshed => "_typeshed",
|
||||
Self::TypingExtensions => "typing_extensions",
|
||||
};
|
||||
@@ -29,54 +34,55 @@ impl CoreStdlibModule {
|
||||
|
||||
/// Lookup the type of `symbol` in a given core module
|
||||
///
|
||||
/// Returns `Unbound` if the given core module cannot be resolved for some reason
|
||||
fn core_module_symbol_ty<'db>(
|
||||
/// Returns `Symbol::Unbound` if the given core module cannot be resolved for some reason
|
||||
fn core_module_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
core_module: CoreStdlibModule,
|
||||
symbol: &str,
|
||||
) -> Type<'db> {
|
||||
) -> Symbol<'db> {
|
||||
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)
|
||||
.map(|module| global_symbol(db, module.file(), symbol))
|
||||
.unwrap_or(Symbol::Unbound)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the builtins namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `builtins` module isn't available for some reason.
|
||||
/// Returns `Symbol::Unbound` if the `builtins` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn builtins_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Builtins, symbol)
|
||||
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Builtins, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `types` module namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `types` module isn't available for some reason.
|
||||
/// Returns `Symbol::Unbound` if the `types` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn types_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Types, symbol)
|
||||
pub(crate) fn types_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Types, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `typing` module namespace.
|
||||
///
|
||||
/// Returns `Symbol::Unbound` if the `typing` module isn't available for some reason.
|
||||
#[inline]
|
||||
#[allow(dead_code)] // currently only used in tests
|
||||
pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Typing, symbol)
|
||||
}
|
||||
/// Lookup the type of `symbol` in the `_typeshed` module namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `_typeshed` module isn't available for some reason.
|
||||
/// Returns `Symbol::Unbound` if the `_typeshed` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn typeshed_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::Typeshed, symbol)
|
||||
pub(crate) fn typeshed_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Typeshed, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `typing_extensions` module namespace.
|
||||
///
|
||||
/// Returns `Unbound` if the `typing_extensions` module isn't available for some reason.
|
||||
/// Returns `Symbol::Unbound` if the `typing_extensions` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn typing_extensions_symbol_ty<'db>(db: &'db dyn Db, symbol: &str) -> Type<'db> {
|
||||
core_module_symbol_ty(db, CoreStdlibModule::TypingExtensions, symbol)
|
||||
pub(crate) fn typing_extensions_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::TypingExtensions, symbol)
|
||||
}
|
||||
|
||||
/// Get the scope of a core stdlib module.
|
||||
|
||||
92
crates/red_knot_python_semantic/src/symbol.rs
Normal file
92
crates/red_knot_python_semantic/src/symbol.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use crate::{
|
||||
types::{Type, UnionType},
|
||||
Db,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum Boundness {
|
||||
Bound,
|
||||
MayBeUnbound,
|
||||
}
|
||||
|
||||
/// The result of a symbol lookup, which can either be a (possibly unbound) type
|
||||
/// or a completely unbound symbol.
|
||||
///
|
||||
/// Consider this example:
|
||||
/// ```py
|
||||
/// bound = 1
|
||||
///
|
||||
/// if flag:
|
||||
/// maybe_unbound = 2
|
||||
/// ```
|
||||
///
|
||||
/// If we look up symbols in this scope, we would get the following results:
|
||||
/// ```rs
|
||||
/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound),
|
||||
/// maybe_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::MayBeUnbound),
|
||||
/// non_existent: Symbol::Unbound,
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Symbol<'db> {
|
||||
Type(Type<'db>, Boundness),
|
||||
Unbound,
|
||||
}
|
||||
|
||||
impl<'db> Symbol<'db> {
|
||||
pub(crate) fn is_unbound(&self) -> bool {
|
||||
matches!(self, Symbol::Unbound)
|
||||
}
|
||||
|
||||
pub(crate) fn may_be_unbound(&self) -> bool {
|
||||
match self {
|
||||
Symbol::Type(_, Boundness::MayBeUnbound) | Symbol::Unbound => true,
|
||||
Symbol::Type(_, Boundness::Bound) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or(&self, other: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => *ty,
|
||||
Symbol::Unbound => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
|
||||
self.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => Some(*ty),
|
||||
Symbol::Unbound => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[track_caller]
|
||||
pub(crate) fn expect_type(self) -> Type<'db> {
|
||||
self.as_type()
|
||||
.expect("Expected a (possibly unbound) type, not an unbound symbol")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn replace_unbound_with(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
replacement: &Symbol<'db>,
|
||||
) -> Symbol<'db> {
|
||||
match replacement {
|
||||
Symbol::Type(replacement, _) => Symbol::Type(
|
||||
match self {
|
||||
Symbol::Type(ty, Boundness::Bound) => ty,
|
||||
Symbol::Type(ty, Boundness::MayBeUnbound) => {
|
||||
UnionType::from_elements(db, [*replacement, ty])
|
||||
}
|
||||
Symbol::Unbound => *replacement,
|
||||
},
|
||||
Boundness::Bound,
|
||||
),
|
||||
Symbol::Unbound => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -173,14 +173,37 @@ impl<'db> IntersectionBuilder<'db> {
|
||||
pub(crate) fn add_negative(mut self, ty: Type<'db>) -> Self {
|
||||
// See comments above in `add_positive`; this is just the negated version.
|
||||
if let Type::Union(union) = ty {
|
||||
union
|
||||
.elements(self.db)
|
||||
for elem in union.elements(self.db) {
|
||||
self = self.add_negative(*elem);
|
||||
}
|
||||
self
|
||||
} else if let Type::Intersection(intersection) = ty {
|
||||
// (A | B) & ~(C & ~D)
|
||||
// -> (A | B) & (~C | D)
|
||||
// -> ((A | B) & ~C) | ((A | B) & D)
|
||||
// i.e. if we have an intersection of positive constraints C
|
||||
// and negative constraints D, then our new intersection
|
||||
// is (existing & ~C) | (existing & D)
|
||||
|
||||
let positive_side = intersection
|
||||
.positive(self.db)
|
||||
.iter()
|
||||
.map(|elem| self.clone().add_negative(*elem))
|
||||
.fold(IntersectionBuilder::empty(self.db), |mut builder, sub| {
|
||||
// we negate all the positive constraints while distributing
|
||||
.map(|elem| self.clone().add_negative(*elem));
|
||||
|
||||
let negative_side = intersection
|
||||
.negative(self.db)
|
||||
.iter()
|
||||
// all negative constraints end up becoming positive constraints
|
||||
.map(|elem| self.clone().add_positive(*elem));
|
||||
|
||||
positive_side.chain(negative_side).fold(
|
||||
IntersectionBuilder::empty(self.db),
|
||||
|mut builder, sub| {
|
||||
builder.intersections.extend(sub.intersections);
|
||||
builder
|
||||
})
|
||||
},
|
||||
)
|
||||
} else {
|
||||
for inner in &mut self.intersections {
|
||||
inner.add_negative(self.db, ty);
|
||||
@@ -293,7 +316,6 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
self.add_positive(db, *neg);
|
||||
}
|
||||
}
|
||||
Type::Unbound => {}
|
||||
ty @ (Type::Any | Type::Unknown | Type::Todo) => {
|
||||
// Adding any of these types to the negative side of an intersection
|
||||
// is equivalent to adding it to the positive side. We do this to
|
||||
@@ -344,15 +366,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn simplify_unbound(&mut self) {
|
||||
if self.positive.contains(&Type::Unbound) {
|
||||
self.positive.retain(Type::is_unbound);
|
||||
self.negative.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.simplify_unbound();
|
||||
match (self.positive.len(), self.negative.len()) {
|
||||
(0, 0) => KnownClass::Object.to_instance(db),
|
||||
(1, 0) => self.positive[0],
|
||||
@@ -371,6 +385,7 @@ mod tests {
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::stdlib::typing_symbol;
|
||||
use crate::types::{KnownClass, StringLiteralType, UnionBuilder};
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
@@ -561,18 +576,22 @@ mod tests {
|
||||
let ta = Type::Any;
|
||||
let t1 = Type::IntLiteral(1);
|
||||
let t2 = KnownClass::Int.to_instance(&db);
|
||||
// i0 = Any & ~Literal[1]
|
||||
let i0 = IntersectionBuilder::new(&db)
|
||||
.add_positive(ta)
|
||||
.add_negative(t1)
|
||||
.build();
|
||||
let intersection = IntersectionBuilder::new(&db)
|
||||
// ta_not_i0 = int & ~(Any & ~Literal[1])
|
||||
// -> int & (~Any | Literal[1])
|
||||
// (~Any is equivalent to Any)
|
||||
// -> (int & Any) | (int & Literal[1])
|
||||
// -> (int & Any) | Literal[1]
|
||||
let ta_not_i0 = IntersectionBuilder::new(&db)
|
||||
.add_positive(t2)
|
||||
.add_negative(i0)
|
||||
.build()
|
||||
.expect_intersection();
|
||||
.build();
|
||||
|
||||
assert_eq!(intersection.pos_vec(&db), &[ta, t1]);
|
||||
assert_eq!(intersection.neg_vec(&db), &[]);
|
||||
assert_eq!(ta_not_i0.display(&db).to_string(), "int & Any | Literal[1]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -595,6 +614,63 @@ mod tests {
|
||||
assert_eq!(i1.pos_vec(&db), &[ta, t1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_negation_distributes_over_union() {
|
||||
let db = setup_db();
|
||||
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
|
||||
let ht = typing_symbol(&db, "Hashable")
|
||||
.expect_type()
|
||||
.to_instance(&db);
|
||||
// sh_t: Sized & Hashable
|
||||
let sh_t = IntersectionBuilder::new(&db)
|
||||
.add_positive(st)
|
||||
.add_positive(ht)
|
||||
.build()
|
||||
.expect_intersection();
|
||||
assert_eq!(sh_t.pos_vec(&db), &[st, ht]);
|
||||
assert_eq!(sh_t.neg_vec(&db), &[]);
|
||||
|
||||
// ~sh_t => ~Sized | ~Hashable
|
||||
let not_s_h_t = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::Intersection(sh_t))
|
||||
.build()
|
||||
.expect_union();
|
||||
|
||||
// should have as elements: (~Sized),(~Hashable)
|
||||
let not_st = st.negate(&db);
|
||||
let not_ht = ht.negate(&db);
|
||||
assert_eq!(not_s_h_t.elements(&db), &[not_st, not_ht]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_intersection_negation_distributes_over_union() {
|
||||
let db = setup_db();
|
||||
let it = KnownClass::Int.to_instance(&db);
|
||||
let st = typing_symbol(&db, "Sized").expect_type().to_instance(&db);
|
||||
let ht = typing_symbol(&db, "Hashable")
|
||||
.expect_type()
|
||||
.to_instance(&db);
|
||||
// s_not_h_t: Sized & ~Hashable
|
||||
let s_not_h_t = IntersectionBuilder::new(&db)
|
||||
.add_positive(st)
|
||||
.add_negative(ht)
|
||||
.build()
|
||||
.expect_intersection();
|
||||
assert_eq!(s_not_h_t.pos_vec(&db), &[st]);
|
||||
assert_eq!(s_not_h_t.neg_vec(&db), &[ht]);
|
||||
|
||||
// let's build int & ~(Sized & ~Hashable)
|
||||
let tt = IntersectionBuilder::new(&db)
|
||||
.add_positive(it)
|
||||
.add_negative(Type::Intersection(s_not_h_t))
|
||||
.build();
|
||||
|
||||
// int & ~(Sized & ~Hashable)
|
||||
// -> int & (~Sized | Hashable)
|
||||
// -> (int & ~Sized) | (int & Hashable)
|
||||
assert_eq!(tt.display(&db).to_string(), "int & ~Sized | int & Hashable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_self_negation() {
|
||||
let db = setup_db();
|
||||
@@ -628,28 +704,6 @@ mod tests {
|
||||
assert_eq!(ty, Type::Never);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_positive_unbound() {
|
||||
let db = setup_db();
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(Type::Unbound)
|
||||
.add_positive(Type::IntLiteral(1))
|
||||
.build();
|
||||
|
||||
assert_eq!(ty, Type::Unbound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_negative_unbound() {
|
||||
let db = setup_db();
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::Unbound)
|
||||
.add_positive(Type::IntLiteral(1))
|
||||
.build();
|
||||
|
||||
assert_eq!(ty, Type::IntLiteral(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_negative_none() {
|
||||
let db = setup_db();
|
||||
@@ -667,6 +721,27 @@ mod tests {
|
||||
assert_eq!(ty, Type::IntLiteral(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_negative_union_de_morgan() {
|
||||
let db = setup_db();
|
||||
|
||||
let union = UnionBuilder::new(&db)
|
||||
.add(Type::IntLiteral(1))
|
||||
.add(Type::IntLiteral(2))
|
||||
.build();
|
||||
assert_eq!(union.display(&db).to_string(), "Literal[1, 2]");
|
||||
|
||||
let ty = IntersectionBuilder::new(&db).add_negative(union).build();
|
||||
|
||||
let expected = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::IntLiteral(1))
|
||||
.add_negative(Type::IntLiteral(2))
|
||||
.build();
|
||||
|
||||
assert_eq!(ty.display(&db).to_string(), "~Literal[1] & ~Literal[2]");
|
||||
assert_eq!(ty, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_positive_type_and_positive_subtype() {
|
||||
let db = setup_db();
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::types::Type;
|
||||
use crate::Db;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
// TODO: Don't use string keys for rules
|
||||
@@ -109,3 +113,174 @@ impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TypeCheckDiagnosticsBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
}
|
||||
|
||||
impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||
pub(super) fn new(db: &'db dyn Db, file: File) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
diagnostics: TypeCheckDiagnostics::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn add_index_out_of_bounds(
|
||||
&mut self,
|
||||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type<'db>,
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
pub(super) fn add_non_subscriptable(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
non_subscriptable_ty: Type<'db>,
|
||||
method: &str,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"non-subscriptable",
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_module(
|
||||
&mut self,
|
||||
import_node: impl Into<AnyNodeRef<'db>>,
|
||||
level: u32,
|
||||
module: Option<&str>,
|
||||
) {
|
||||
self.add(
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
module.unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
||||
self.add(
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_invalid_assignment(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
declared_ty: Type<'db>,
|
||||
assigned_ty: Type<'db>,
|
||||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(class) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(self.db)));
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(self.db)));
|
||||
}
|
||||
_ => {
|
||||
self.add(
|
||||
node,
|
||||
"invalid-assignment",
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(self.db),
|
||||
declared_ty.display(self.db),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_possibly_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a new diagnostic.
|
||||
///
|
||||
/// The diagnostic does not get added if the rule isn't enabled for this file.
|
||||
pub(super) fn add(&mut self, node: AnyNodeRef, rule: &str, message: std::fmt::Arguments) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Don't emit the diagnostic if:
|
||||
// * The enclosing node contains any syntax errors
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
|
||||
self.diagnostics.push(TypeCheckDiagnostic {
|
||||
file: self.file,
|
||||
rule: rule.to_string(),
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn extend(&mut self, diagnostics: &TypeCheckDiagnostics) {
|
||||
self.diagnostics.extend(diagnostics);
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> TypeCheckDiagnostics {
|
||||
self.diagnostics.shrink_to_fit();
|
||||
self.diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ impl Display for DisplayRepresentation<'_> {
|
||||
Type::Any => f.write_str("Any"),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::Unknown => f.write_str("Unknown"),
|
||||
Type::Unbound => f.write_str("Unbound"),
|
||||
Type::None => f.write_str("None"),
|
||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
@@ -90,6 +89,28 @@ impl Display for DisplayRepresentation<'_> {
|
||||
|
||||
escape.bytes_repr().write(f)
|
||||
}
|
||||
Type::SliceLiteral(slice) => {
|
||||
f.write_str("slice[")?;
|
||||
if let Some(start) = slice.start(self.db) {
|
||||
write!(f, "Literal[{start}]")?;
|
||||
} else {
|
||||
f.write_str("None")?;
|
||||
}
|
||||
|
||||
f.write_str(", ")?;
|
||||
|
||||
if let Some(stop) = slice.stop(self.db) {
|
||||
write!(f, "Literal[{stop}]")?;
|
||||
} else {
|
||||
f.write_str("None")?;
|
||||
}
|
||||
|
||||
if let Some(step) = slice.step(self.db) {
|
||||
write!(f, ", Literal[{step}]")?;
|
||||
}
|
||||
|
||||
f.write_str("]")
|
||||
}
|
||||
Type::Tuple(tuple) => {
|
||||
f.write_str("tuple[")?;
|
||||
let elements = tuple.elements(self.db);
|
||||
@@ -301,7 +322,9 @@ mod tests {
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::types::{global_symbol_ty, BytesLiteralType, StringLiteralType, Type, UnionType};
|
||||
use crate::types::{
|
||||
global_symbol, BytesLiteralType, SliceLiteralType, StringLiteralType, Type, UnionType,
|
||||
};
|
||||
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
@@ -346,16 +369,16 @@ mod tests {
|
||||
let union_elements = &[
|
||||
Type::Unknown,
|
||||
Type::IntLiteral(-1),
|
||||
global_symbol_ty(&db, mod_file, "A"),
|
||||
global_symbol(&db, mod_file, "A").expect_type(),
|
||||
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, "B")),
|
||||
global_symbol_ty(&db, mod_file, "foo"),
|
||||
global_symbol_ty(&db, mod_file, "bar"),
|
||||
global_symbol_ty(&db, mod_file, "B"),
|
||||
global_symbol(&db, mod_file, "foo").expect_type(),
|
||||
global_symbol(&db, mod_file, "bar").expect_type(),
|
||||
global_symbol(&db, mod_file, "B").expect_type(),
|
||||
Type::BooleanLiteral(true),
|
||||
Type::None,
|
||||
];
|
||||
@@ -376,4 +399,46 @@ mod tests {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slice_literal_display() {
|
||||
let db = setup_db();
|
||||
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, None, None, None))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[None, None]"
|
||||
);
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), None, None))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[Literal[1], None]"
|
||||
);
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, None, Some(2), None))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[None, Literal[2]]"
|
||||
);
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), Some(5), None))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[Literal[1], Literal[5]]"
|
||||
);
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, Some(1), Some(5), Some(2)))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[Literal[1], Literal[5], Literal[2]]"
|
||||
);
|
||||
assert_eq!(
|
||||
Type::SliceLiteral(SliceLiteralType::new(&db, None, None, Some(2)))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
"slice[None, None, Literal[2]]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::constraint::{Constraint, PatternConstraint};
|
||||
use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraint};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
@@ -34,13 +34,19 @@ pub(crate) fn narrowing_constraint<'db>(
|
||||
constraint: Constraint<'db>,
|
||||
definition: Definition<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
match constraint {
|
||||
Constraint::Expression(expression) => {
|
||||
all_narrowing_constraints_for_expression(db, expression)
|
||||
.get(&definition.symbol(db))
|
||||
.copied()
|
||||
match constraint.node {
|
||||
ConstraintNode::Expression(expression) => {
|
||||
if constraint.is_positive {
|
||||
all_narrowing_constraints_for_expression(db, expression)
|
||||
.get(&definition.symbol(db))
|
||||
.copied()
|
||||
} else {
|
||||
all_negative_narrowing_constraints_for_expression(db, expression)
|
||||
.get(&definition.symbol(db))
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
Constraint::Pattern(pattern) => all_narrowing_constraints_for_pattern(db, pattern)
|
||||
ConstraintNode::Pattern(pattern) => all_narrowing_constraints_for_pattern(db, pattern)
|
||||
.get(&definition.symbol(db))
|
||||
.copied(),
|
||||
}
|
||||
@@ -51,7 +57,7 @@ fn all_narrowing_constraints_for_pattern<'db>(
|
||||
db: &'db dyn Db,
|
||||
pattern: PatternConstraint<'db>,
|
||||
) -> NarrowingConstraints<'db> {
|
||||
NarrowingConstraintsBuilder::new(db, Constraint::Pattern(pattern)).finish()
|
||||
NarrowingConstraintsBuilder::new(db, ConstraintNode::Pattern(pattern), true).finish()
|
||||
}
|
||||
|
||||
#[salsa::tracked(return_ref)]
|
||||
@@ -59,7 +65,15 @@ fn all_narrowing_constraints_for_expression<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> NarrowingConstraints<'db> {
|
||||
NarrowingConstraintsBuilder::new(db, Constraint::Expression(expression)).finish()
|
||||
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), true).finish()
|
||||
}
|
||||
|
||||
#[salsa::tracked(return_ref)]
|
||||
fn all_negative_narrowing_constraints_for_expression<'db>(
|
||||
db: &'db dyn Db,
|
||||
expression: Expression<'db>,
|
||||
) -> NarrowingConstraints<'db> {
|
||||
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), false).finish()
|
||||
}
|
||||
|
||||
/// Generate a constraint from the *type* of the second argument of an `isinstance` call.
|
||||
@@ -88,36 +102,56 @@ type NarrowingConstraints<'db> = FxHashMap<ScopedSymbolId, Type<'db>>;
|
||||
|
||||
struct NarrowingConstraintsBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
constraint: Constraint<'db>,
|
||||
constraint: ConstraintNode<'db>,
|
||||
is_positive: bool,
|
||||
constraints: NarrowingConstraints<'db>,
|
||||
}
|
||||
|
||||
impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
fn new(db: &'db dyn Db, constraint: Constraint<'db>) -> Self {
|
||||
fn new(db: &'db dyn Db, constraint: ConstraintNode<'db>, is_positive: bool) -> Self {
|
||||
Self {
|
||||
db,
|
||||
constraint,
|
||||
is_positive,
|
||||
constraints: NarrowingConstraints::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) -> NarrowingConstraints<'db> {
|
||||
match self.constraint {
|
||||
Constraint::Expression(expression) => self.evaluate_expression_constraint(expression),
|
||||
Constraint::Pattern(pattern) => self.evaluate_pattern_constraint(pattern),
|
||||
ConstraintNode::Expression(expression) => {
|
||||
self.evaluate_expression_constraint(expression, self.is_positive);
|
||||
}
|
||||
ConstraintNode::Pattern(pattern) => self.evaluate_pattern_constraint(pattern),
|
||||
}
|
||||
|
||||
self.constraints.shrink_to_fit();
|
||||
self.constraints
|
||||
}
|
||||
|
||||
fn evaluate_expression_constraint(&mut self, expression: Expression<'db>) {
|
||||
match expression.node_ref(self.db).node() {
|
||||
fn evaluate_expression_constraint(&mut self, expression: Expression<'db>, is_positive: bool) {
|
||||
let expression_node = expression.node_ref(self.db).node();
|
||||
self.evaluate_expression_node_constraint(expression_node, expression, is_positive);
|
||||
}
|
||||
|
||||
fn evaluate_expression_node_constraint(
|
||||
&mut self,
|
||||
expression_node: &ruff_python_ast::Expr,
|
||||
expression: Expression<'db>,
|
||||
is_positive: bool,
|
||||
) {
|
||||
match expression_node {
|
||||
ast::Expr::Compare(expr_compare) => {
|
||||
self.add_expr_compare(expr_compare, expression);
|
||||
self.add_expr_compare(expr_compare, expression, is_positive);
|
||||
}
|
||||
ast::Expr::Call(expr_call) => {
|
||||
self.add_expr_call(expr_call, expression);
|
||||
self.add_expr_call(expr_call, expression, is_positive);
|
||||
}
|
||||
ast::Expr::UnaryOp(unary_op) if unary_op.op == ast::UnaryOp::Not => {
|
||||
self.evaluate_expression_node_constraint(
|
||||
&unary_op.operand,
|
||||
expression,
|
||||
!is_positive,
|
||||
);
|
||||
}
|
||||
_ => {} // TODO other test expression kinds
|
||||
}
|
||||
@@ -160,12 +194,17 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
|
||||
fn scope(&self) -> ScopeId<'db> {
|
||||
match self.constraint {
|
||||
Constraint::Expression(expression) => expression.scope(self.db),
|
||||
Constraint::Pattern(pattern) => pattern.scope(self.db),
|
||||
ConstraintNode::Expression(expression) => expression.scope(self.db),
|
||||
ConstraintNode::Pattern(pattern) => pattern.scope(self.db),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_expr_compare(&mut self, expr_compare: &ast::ExprCompare, expression: Expression<'db>) {
|
||||
fn add_expr_compare(
|
||||
&mut self,
|
||||
expr_compare: &ast::ExprCompare,
|
||||
expression: Expression<'db>,
|
||||
is_positive: bool,
|
||||
) {
|
||||
let ast::ExprCompare {
|
||||
range: _,
|
||||
left,
|
||||
@@ -177,6 +216,13 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
// we have no symbol to narrow down the type of.
|
||||
return;
|
||||
}
|
||||
if !is_positive && comparators.len() > 1 {
|
||||
// We can't negate a constraint made by a multi-comparator expression, since we can't
|
||||
// know which comparison part is the one being negated.
|
||||
// For example, the negation of `x is 1 is y is 2`, would be `(x is not 1) or (y is not 1) or (y is not 2)`
|
||||
// and that requires cross-symbol constraints, which we don't support yet.
|
||||
return;
|
||||
}
|
||||
let scope = self.scope();
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
|
||||
@@ -192,12 +238,13 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
{
|
||||
// SAFETY: we should always have a symbol for every Name node.
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
let comp_ty = inference.expression_ty(right.scoped_ast_id(self.db, scope));
|
||||
match op {
|
||||
let rhs_ty = inference.expression_ty(right.scoped_ast_id(self.db, scope));
|
||||
|
||||
match if is_positive { *op } else { op.negate() } {
|
||||
ast::CmpOp::IsNot => {
|
||||
if comp_ty.is_singleton() {
|
||||
if rhs_ty.is_singleton() {
|
||||
let ty = IntersectionBuilder::new(self.db)
|
||||
.add_negative(comp_ty)
|
||||
.add_negative(rhs_ty)
|
||||
.build();
|
||||
self.constraints.insert(symbol, ty);
|
||||
} else {
|
||||
@@ -205,12 +252,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
}
|
||||
}
|
||||
ast::CmpOp::Is => {
|
||||
self.constraints.insert(symbol, comp_ty);
|
||||
self.constraints.insert(symbol, rhs_ty);
|
||||
}
|
||||
ast::CmpOp::NotEq => {
|
||||
if comp_ty.is_single_valued(self.db) {
|
||||
if rhs_ty.is_single_valued(self.db) {
|
||||
let ty = IntersectionBuilder::new(self.db)
|
||||
.add_negative(comp_ty)
|
||||
.add_negative(rhs_ty)
|
||||
.build();
|
||||
self.constraints.insert(symbol, ty);
|
||||
}
|
||||
@@ -223,7 +270,12 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_expr_call(&mut self, expr_call: &ast::ExprCall, expression: Expression<'db>) {
|
||||
fn add_expr_call(
|
||||
&mut self,
|
||||
expr_call: &ast::ExprCall,
|
||||
expression: Expression<'db>,
|
||||
is_positive: bool,
|
||||
) {
|
||||
let scope = self.scope();
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
|
||||
@@ -242,7 +294,11 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
|
||||
// TODO: add support for PEP 604 union types on the right hand side:
|
||||
// isinstance(x, str | (int | float))
|
||||
if let Some(constraint) = generate_isinstance_constraint(self.db, &rhs_type) {
|
||||
if let Some(mut constraint) = generate_isinstance_constraint(self.db, &rhs_type)
|
||||
{
|
||||
if !is_positive {
|
||||
constraint = constraint.negate(self.db);
|
||||
}
|
||||
self.constraints.insert(symbol, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,192 @@
|
||||
pub(crate) trait PythonSubscript {
|
||||
//! This module provides utility functions for indexing (`PyIndex`) and slicing
|
||||
//! operations (`PySlice`) on iterators, following the semantics of equivalent
|
||||
//! operations in Python.
|
||||
|
||||
use itertools::Either;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct OutOfBoundsError;
|
||||
|
||||
pub(crate) trait PyIndex {
|
||||
type Item;
|
||||
|
||||
fn python_subscript(&mut self, index: i64) -> Option<Self::Item>;
|
||||
fn py_index(&mut self, index: i32) -> Result<Self::Item, OutOfBoundsError>;
|
||||
}
|
||||
|
||||
impl<I, T: DoubleEndedIterator<Item = I>> PythonSubscript for T {
|
||||
fn from_nonnegative_i32(index: i32) -> usize {
|
||||
static_assertions::const_assert!(usize::BITS >= 32);
|
||||
debug_assert!(index >= 0);
|
||||
|
||||
usize::try_from(index)
|
||||
.expect("Should only ever pass a positive integer to `from_nonnegative_i32`")
|
||||
}
|
||||
|
||||
fn from_negative_i32(index: i32) -> usize {
|
||||
static_assertions::const_assert!(usize::BITS >= 32);
|
||||
|
||||
index.checked_neg().map(from_nonnegative_i32).unwrap_or({
|
||||
// 'checked_neg' only fails for i32::MIN. We can not
|
||||
// represent -i32::MIN as a i32, but we can represent
|
||||
// it as a usize, since usize is at least 32 bits.
|
||||
from_nonnegative_i32(i32::MAX) + 1
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
enum Position {
|
||||
BeforeStart,
|
||||
AtIndex(usize),
|
||||
AfterEnd,
|
||||
}
|
||||
|
||||
enum Nth {
|
||||
FromStart(usize),
|
||||
FromEnd(usize),
|
||||
}
|
||||
|
||||
impl Nth {
|
||||
fn from_index(index: i32) -> Self {
|
||||
if index >= 0 {
|
||||
Nth::FromStart(from_nonnegative_i32(index))
|
||||
} else {
|
||||
Nth::FromEnd(from_negative_i32(index) - 1)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_position(&self, len: usize) -> Position {
|
||||
debug_assert!(len > 0);
|
||||
|
||||
match self {
|
||||
Nth::FromStart(nth) => {
|
||||
if *nth < len {
|
||||
Position::AtIndex(*nth)
|
||||
} else {
|
||||
Position::AfterEnd
|
||||
}
|
||||
}
|
||||
Nth::FromEnd(nth_rev) => {
|
||||
if *nth_rev < len {
|
||||
Position::AtIndex(len - 1 - *nth_rev)
|
||||
} else {
|
||||
Position::BeforeStart
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, T> PyIndex for T
|
||||
where
|
||||
T: DoubleEndedIterator<Item = I>,
|
||||
{
|
||||
type Item = I;
|
||||
|
||||
fn python_subscript(&mut self, index: i64) -> Option<I> {
|
||||
if index >= 0 {
|
||||
self.nth(usize::try_from(index).ok()?)
|
||||
fn py_index(&mut self, index: i32) -> Result<I, OutOfBoundsError> {
|
||||
match Nth::from_index(index) {
|
||||
Nth::FromStart(nth) => self.nth(nth).ok_or(OutOfBoundsError),
|
||||
Nth::FromEnd(nth_rev) => self.nth_back(nth_rev).ok_or(OutOfBoundsError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) struct StepSizeZeroError;
|
||||
|
||||
pub(crate) trait PySlice {
|
||||
type Item;
|
||||
|
||||
fn py_slice(
|
||||
&self,
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step: Option<i32>,
|
||||
) -> Result<
|
||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||
StepSizeZeroError,
|
||||
>;
|
||||
}
|
||||
|
||||
impl<T> PySlice for [T] {
|
||||
type Item = T;
|
||||
|
||||
fn py_slice(
|
||||
&self,
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step_int: Option<i32>,
|
||||
) -> Result<
|
||||
Either<impl Iterator<Item = &Self::Item>, impl Iterator<Item = &Self::Item>>,
|
||||
StepSizeZeroError,
|
||||
> {
|
||||
let step_int = step_int.unwrap_or(1);
|
||||
if step_int == 0 {
|
||||
return Err(StepSizeZeroError);
|
||||
}
|
||||
|
||||
let len = self.len();
|
||||
if len == 0 {
|
||||
// The iterator needs to have the same type as the step>0 case below,
|
||||
// so we need to use `.skip(0)`.
|
||||
#[allow(clippy::iter_skip_zero)]
|
||||
return Ok(Either::Left(self.iter().skip(0).take(0).step_by(1)));
|
||||
}
|
||||
|
||||
let to_position = |index| Nth::from_index(index).to_position(len);
|
||||
|
||||
if step_int.is_positive() {
|
||||
let step = from_nonnegative_i32(step_int);
|
||||
|
||||
let start = start.map(to_position).unwrap_or(Position::BeforeStart);
|
||||
let stop = stop.map(to_position).unwrap_or(Position::AfterEnd);
|
||||
|
||||
let (skip, take, step) = if start < stop {
|
||||
let skip = match start {
|
||||
Position::BeforeStart => 0,
|
||||
Position::AtIndex(start_index) => start_index,
|
||||
Position::AfterEnd => len,
|
||||
};
|
||||
|
||||
let take = match stop {
|
||||
Position::BeforeStart => 0,
|
||||
Position::AtIndex(stop_index) => stop_index - skip,
|
||||
Position::AfterEnd => len - skip,
|
||||
};
|
||||
|
||||
(skip, take, step)
|
||||
} else {
|
||||
(0, 0, step)
|
||||
};
|
||||
|
||||
Ok(Either::Left(
|
||||
self.iter().skip(skip).take(take).step_by(step),
|
||||
))
|
||||
} else {
|
||||
let nth_rev = usize::try_from(index.checked_neg()?).ok()?.checked_sub(1)?;
|
||||
self.rev().nth(nth_rev)
|
||||
let step = from_negative_i32(step_int);
|
||||
|
||||
let start = start.map(to_position).unwrap_or(Position::AfterEnd);
|
||||
let stop = stop.map(to_position).unwrap_or(Position::BeforeStart);
|
||||
|
||||
let (skip, take, step) = if start <= stop {
|
||||
(0, 0, step)
|
||||
} else {
|
||||
let skip = match start {
|
||||
Position::BeforeStart => len,
|
||||
Position::AtIndex(start_index) => len - 1 - start_index,
|
||||
Position::AfterEnd => 0,
|
||||
};
|
||||
|
||||
let take = match stop {
|
||||
Position::BeforeStart => len - skip,
|
||||
Position::AtIndex(stop_index) => (len - 1) - skip - stop_index,
|
||||
Position::AfterEnd => 0,
|
||||
};
|
||||
|
||||
(skip, take, step)
|
||||
};
|
||||
|
||||
Ok(Either::Right(
|
||||
self.iter().rev().skip(skip).take(take).step_by(step),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,64 +194,309 @@ impl<I, T: DoubleEndedIterator<Item = I>> PythonSubscript for T {
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
mod tests {
|
||||
use super::PythonSubscript;
|
||||
use crate::util::subscript::{OutOfBoundsError, StepSizeZeroError};
|
||||
|
||||
use super::{PyIndex, PySlice};
|
||||
use itertools::assert_equal;
|
||||
|
||||
#[test]
|
||||
fn python_subscript_basic() {
|
||||
let iter = 'a'..='e';
|
||||
fn py_index_empty() {
|
||||
let iter = std::iter::empty::<char>();
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(0), Some('a'));
|
||||
assert_eq!(iter.clone().python_subscript(1), Some('b'));
|
||||
assert_eq!(iter.clone().python_subscript(4), Some('e'));
|
||||
assert_eq!(iter.clone().python_subscript(5), None);
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(-1), Some('e'));
|
||||
assert_eq!(iter.clone().python_subscript(-2), Some('d'));
|
||||
assert_eq!(iter.clone().python_subscript(-5), Some('a'));
|
||||
assert_eq!(iter.clone().python_subscript(-6), None);
|
||||
assert_eq!(iter.clone().py_index(0), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(-1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(i32::MIN), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(i32::MAX), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_subscript_empty() {
|
||||
let iter = 'a'..'a';
|
||||
fn py_index_single_element() {
|
||||
let iter = ['a'].into_iter();
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(0), None);
|
||||
assert_eq!(iter.clone().python_subscript(1), None);
|
||||
assert_eq!(iter.clone().python_subscript(-1), None);
|
||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(1), Err(OutOfBoundsError));
|
||||
assert_eq!(iter.clone().py_index(-1), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(-2), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_subscript_single_element() {
|
||||
let iter = 'a'..='a';
|
||||
fn py_index_more_elements() {
|
||||
let iter = ['a', 'b', 'c', 'd', 'e'].into_iter();
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(0), Some('a'));
|
||||
assert_eq!(iter.clone().python_subscript(1), None);
|
||||
assert_eq!(iter.clone().python_subscript(-1), Some('a'));
|
||||
assert_eq!(iter.clone().python_subscript(-2), None);
|
||||
assert_eq!(iter.clone().py_index(0), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(1), Ok('b'));
|
||||
assert_eq!(iter.clone().py_index(4), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(5), Err(OutOfBoundsError));
|
||||
|
||||
assert_eq!(iter.clone().py_index(-1), Ok('e'));
|
||||
assert_eq!(iter.clone().py_index(-2), Ok('d'));
|
||||
assert_eq!(iter.clone().py_index(-5), Ok('a'));
|
||||
assert_eq!(iter.clone().py_index(-6), Err(OutOfBoundsError));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_subscript_uses_full_index_range() {
|
||||
let iter = 0..=u64::MAX;
|
||||
fn py_index_uses_full_index_range() {
|
||||
let iter = 0..=u32::MAX;
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(0), Some(0));
|
||||
assert_eq!(iter.clone().python_subscript(1), Some(1));
|
||||
assert_eq!(
|
||||
iter.clone().python_subscript(i64::MAX),
|
||||
Some(i64::MAX as u64)
|
||||
);
|
||||
// u32::MAX - |i32::MIN| + 1 = 2^32 - 1 - 2^31 + 1 = 2^31
|
||||
assert_eq!(iter.clone().py_index(i32::MIN), Ok(2u32.pow(31)));
|
||||
assert_eq!(iter.clone().py_index(-2), Ok(u32::MAX - 2 + 1));
|
||||
assert_eq!(iter.clone().py_index(-1), Ok(u32::MAX - 1 + 1));
|
||||
|
||||
assert_eq!(iter.clone().python_subscript(-1), Some(u64::MAX));
|
||||
assert_eq!(iter.clone().python_subscript(-2), Some(u64::MAX - 1));
|
||||
assert_eq!(iter.clone().py_index(0), Ok(0));
|
||||
assert_eq!(iter.clone().py_index(1), Ok(1));
|
||||
assert_eq!(iter.clone().py_index(i32::MAX), Ok(i32::MAX as u32));
|
||||
}
|
||||
|
||||
// i64::MIN is not representable as a positive number, so it is not
|
||||
// a valid index:
|
||||
assert_eq!(iter.clone().python_subscript(i64::MIN), None);
|
||||
#[track_caller]
|
||||
fn assert_eq_slice<const N: usize, const M: usize>(
|
||||
input: &[char; N],
|
||||
start: Option<i32>,
|
||||
stop: Option<i32>,
|
||||
step: Option<i32>,
|
||||
expected: &[char; M],
|
||||
) {
|
||||
assert_equal(input.py_slice(start, stop, step).unwrap(), expected.iter());
|
||||
}
|
||||
|
||||
// but i64::MIN +1 is:
|
||||
assert_eq!(
|
||||
iter.clone().python_subscript(i64::MIN + 1),
|
||||
Some(2u64.pow(63) + 1)
|
||||
);
|
||||
#[test]
|
||||
fn py_slice_empty_input() {
|
||||
let input = [];
|
||||
|
||||
assert_eq_slice(&input, None, None, None, &[]);
|
||||
assert_eq_slice(&input, Some(0), None, None, &[]);
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, None, None, Some(-1), &[]);
|
||||
assert_eq_slice(&input, None, None, Some(2), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_single_element_input() {
|
||||
let input = ['a'];
|
||||
|
||||
assert_eq_slice(&input, None, None, None, &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), None, None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(2), None, &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), None, None, &['a']);
|
||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-1), Some(2), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(-1), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-2), None, None, &['a']);
|
||||
assert_eq_slice(&input, Some(-2), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-2), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(-2), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-2), Some(2), None, &['a']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_nonnegative_indices() {
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, None, Some(0), None, &[]);
|
||||
assert_eq_slice(&input, None, Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, None, Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, None, Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, None, None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(0), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(0), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(0), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(1), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(1), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(2), None, &['b']);
|
||||
assert_eq_slice(&input, Some(1), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(1), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(1), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(1), None, None, &['b', 'c', 'd', 'e']);
|
||||
|
||||
assert_eq_slice(&input, Some(4), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(4), Some(4), None, &[]);
|
||||
assert_eq_slice(&input, Some(4), Some(5), None, &['e']);
|
||||
assert_eq_slice(&input, Some(4), Some(6), None, &['e']);
|
||||
assert_eq_slice(&input, Some(4), None, None, &['e']);
|
||||
|
||||
assert_eq_slice(&input, Some(5), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(5), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(6), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), None, None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(6), Some(0), None, &[]);
|
||||
assert_eq_slice(&input, Some(6), Some(6), None, &[]);
|
||||
assert_eq_slice(&input, Some(6), None, None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_negatice_indices() {
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, Some(-6), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(-6), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(-6), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-5), None, None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-5), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(-5), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-4), None, None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-3), None, &['b']);
|
||||
assert_eq_slice(&input, Some(-4), Some(-4), None, &[]);
|
||||
assert_eq_slice(&input, Some(-4), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), None, None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(-10), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, None, Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, None, Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, None, Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, None, Some(-6), None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_mixed_positive_negative_indices() {
|
||||
let input = ['a', 'b', 'c', 'd', 'e'];
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-1), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(1), Some(-1), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(3), Some(-1), None, &['d']);
|
||||
assert_eq_slice(&input, Some(4), Some(-1), None, &[]);
|
||||
assert_eq_slice(&input, Some(5), Some(-1), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-4), None, &['a']);
|
||||
assert_eq_slice(&input, Some(1), Some(-4), None, &[]);
|
||||
assert_eq_slice(&input, Some(3), Some(-4), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(-5), None, &[]);
|
||||
assert_eq_slice(&input, Some(3), Some(-5), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(-6), None, &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(-6), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-6), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-6), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-5), Some(6), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(5), None, &['a', 'b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-5), Some(4), None, &['a', 'b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-5), Some(1), None, &['a']);
|
||||
assert_eq_slice(&input, Some(-5), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-4), Some(6), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(5), None, &['b', 'c', 'd', 'e']);
|
||||
assert_eq_slice(&input, Some(-4), Some(4), None, &['b', 'c', 'd']);
|
||||
assert_eq_slice(&input, Some(-4), Some(2), None, &['b']);
|
||||
assert_eq_slice(&input, Some(-4), Some(1), None, &[]);
|
||||
assert_eq_slice(&input, Some(-4), Some(0), None, &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-1), Some(6), None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(5), None, &['e']);
|
||||
assert_eq_slice(&input, Some(-1), Some(4), None, &[]);
|
||||
assert_eq_slice(&input, Some(-1), Some(1), None, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_step_forward() {
|
||||
// indices: 0 1 2 3 4 5 6
|
||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||
|
||||
// Step size zero is invalid:
|
||||
assert!(matches!(
|
||||
input.py_slice(None, None, Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
assert!(matches!(
|
||||
input.py_slice(Some(0), Some(5), Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
assert!(matches!(
|
||||
input.py_slice(Some(0), Some(0), Some(0)),
|
||||
Err(StepSizeZeroError)
|
||||
));
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(8), Some(2), &['a', 'c', 'e', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(7), Some(2), &['a', 'c', 'e', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(5), Some(2), &['a', 'c', 'e']);
|
||||
assert_eq_slice(&input, Some(0), Some(4), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&input, Some(0), Some(3), Some(2), &['a', 'c']);
|
||||
assert_eq_slice(&input, Some(0), Some(2), Some(2), &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(1), Some(2), &['a']);
|
||||
assert_eq_slice(&input, Some(0), Some(0), Some(2), &[]);
|
||||
assert_eq_slice(&input, Some(1), Some(5), Some(2), &['b', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), Some(7), Some(3), &['a', 'd', 'g']);
|
||||
assert_eq_slice(&input, Some(0), Some(6), Some(3), &['a', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(0), None, Some(10), &['a']);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn py_slice_step_backward() {
|
||||
// indices: 0 1 2 3 4 5 6
|
||||
let input = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
|
||||
|
||||
assert_eq_slice(&input, Some(7), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&input, Some(6), Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
assert_eq_slice(&input, Some(5), Some(0), Some(-2), &['f', 'd', 'b']);
|
||||
assert_eq_slice(&input, Some(4), Some(0), Some(-2), &['e', 'c']);
|
||||
assert_eq_slice(&input, Some(3), Some(0), Some(-2), &['d', 'b']);
|
||||
assert_eq_slice(&input, Some(2), Some(0), Some(-2), &['c']);
|
||||
assert_eq_slice(&input, Some(1), Some(0), Some(-2), &['b']);
|
||||
assert_eq_slice(&input, Some(0), Some(0), Some(-2), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(7), None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&input, None, None, Some(-2), &['g', 'e', 'c', 'a']);
|
||||
assert_eq_slice(&input, None, Some(0), Some(-2), &['g', 'e', 'c']);
|
||||
|
||||
assert_eq_slice(&input, Some(5), Some(1), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&input, Some(5), Some(2), Some(-2), &['f', 'd']);
|
||||
assert_eq_slice(&input, Some(5), Some(3), Some(-2), &['f']);
|
||||
assert_eq_slice(&input, Some(5), Some(4), Some(-2), &['f']);
|
||||
assert_eq_slice(&input, Some(5), Some(5), Some(-2), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(6), None, Some(-3), &['g', 'd', 'a']);
|
||||
assert_eq_slice(&input, Some(6), Some(0), Some(-3), &['g', 'd']);
|
||||
|
||||
assert_eq_slice(&input, Some(7), None, Some(-10), &['g']);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(-9), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-8), Some(-1), &['b', 'a']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-7), Some(-1), &['b']);
|
||||
assert_eq_slice(&input, Some(-6), Some(-6), Some(-1), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-7), Some(-9), Some(-1), &['a']);
|
||||
|
||||
assert_eq_slice(&input, Some(-8), Some(-9), Some(-1), &[]);
|
||||
assert_eq_slice(&input, Some(-9), Some(-9), Some(-1), &[]);
|
||||
|
||||
assert_eq_slice(&input, Some(-6), Some(-2), Some(-1), &[]);
|
||||
assert_eq_slice(&input, Some(-9), Some(-6), Some(-1), &[]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::{line_index, source_text, SourceText};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{LineIndex, Locator, OneIndexed};
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use smallvec::SmallVec;
|
||||
use std::ops::Deref;
|
||||
@@ -67,16 +67,12 @@ impl InlineFileAssertions {
|
||||
}
|
||||
}
|
||||
|
||||
fn locator(&self) -> Locator {
|
||||
Locator::with_index(&self.source, self.lines.clone())
|
||||
}
|
||||
|
||||
fn line_number(&self, range: &impl Ranged) -> OneIndexed {
|
||||
self.lines.line_index(range.start())
|
||||
}
|
||||
|
||||
fn is_own_line_comment(&self, ranged_assertion: &AssertionWithRange) -> bool {
|
||||
CommentRanges::is_own_line(ranged_assertion.start(), &self.locator())
|
||||
CommentRanges::is_own_line(ranged_assertion.start(), self.source.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,10 +127,9 @@ impl<'a> Iterator for AssertionWithRangeIterator<'a> {
|
||||
type Item = AssertionWithRange<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let locator = self.file_assertions.locator();
|
||||
loop {
|
||||
let inner_next = self.inner.next()?;
|
||||
let comment = locator.slice(inner_next);
|
||||
let comment = &self.file_assertions.source[inner_next];
|
||||
if let Some(assertion) = Assertion::from_comment(comment) {
|
||||
return Some(AssertionWithRange(assertion, inner_next));
|
||||
};
|
||||
|
||||
@@ -148,7 +148,7 @@ static HEADER_RE: LazyLock<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: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"^```(?<lang>(?-u:\w)+)?(?<config>(?: +\S+)*)\s*\n(?<code>(?:.|\n)*?)\n?```\s*\n")
|
||||
Regex::new(r"^```(?<lang>(?-u:\w)+)?(?<config>(?: +\S+)*)\s*\n(?<code>(?:.|\n)*?)\n?```\s*\n?")
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
@@ -421,6 +421,31 @@ mod tests {
|
||||
assert_eq!(file.code, "x = 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_new_line_at_eof() {
|
||||
let source = dedent(
|
||||
"
|
||||
```py
|
||||
x = 1
|
||||
```",
|
||||
);
|
||||
let mf = super::parse("file.md", &source).unwrap();
|
||||
|
||||
let [test] = &mf.tests().collect::<Vec<_>>()[..] else {
|
||||
panic!("expected one test");
|
||||
};
|
||||
|
||||
assert_eq!(test.name(), "file.md");
|
||||
|
||||
let [file] = test.files().collect::<Vec<_>>()[..] else {
|
||||
panic!("expected one file");
|
||||
};
|
||||
|
||||
assert_eq!(file.path, "test.py");
|
||||
assert_eq!(file.lang, "py");
|
||||
assert_eq!(file.code, "x = 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_tests() {
|
||||
let source = dedent(
|
||||
|
||||
@@ -1 +1 @@
|
||||
a871efd90ca2734b3341dde98cffab66f3e08cee
|
||||
d262beb07502cda412db2179fb406d45d1a9486f
|
||||
|
||||
@@ -22,6 +22,7 @@ __main__: 3.0-
|
||||
_ast: 3.0-
|
||||
_asyncio: 3.0-
|
||||
_bisect: 3.0-
|
||||
_blake2: 3.6-
|
||||
_bootlocale: 3.4-3.9
|
||||
_codecs: 3.0-
|
||||
_collections_abc: 3.3-
|
||||
@@ -33,6 +34,8 @@ _curses: 3.0-
|
||||
_decimal: 3.3-
|
||||
_dummy_thread: 3.0-3.8
|
||||
_dummy_threading: 3.0-3.8
|
||||
_frozen_importlib: 3.0-
|
||||
_frozen_importlib_external: 3.5-
|
||||
_heapq: 3.0-
|
||||
_imp: 3.0-
|
||||
_interpchannels: 3.13-
|
||||
@@ -160,6 +163,8 @@ imghdr: 3.0-3.12
|
||||
imp: 3.0-3.11
|
||||
importlib: 3.0-
|
||||
importlib._abc: 3.10-
|
||||
importlib._bootstrap: 3.0-
|
||||
importlib._bootstrap_external: 3.5-
|
||||
importlib.metadata: 3.8-
|
||||
importlib.metadata._meta: 3.10-
|
||||
importlib.metadata.diagnose: 3.13-
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sys
|
||||
from asyncio.events import AbstractEventLoop
|
||||
from collections.abc import Awaitable, Callable, Coroutine, Generator, Iterable
|
||||
from collections.abc import Awaitable, Callable, Coroutine, Generator
|
||||
from contextvars import Context
|
||||
from types import FrameType
|
||||
from typing import Any, Literal, TextIO, TypeVar
|
||||
@@ -13,7 +13,7 @@ _T = TypeVar("_T")
|
||||
_T_co = TypeVar("_T_co", covariant=True)
|
||||
_TaskYieldType: TypeAlias = Future[object] | None
|
||||
|
||||
class Future(Awaitable[_T], Iterable[_T]):
|
||||
class Future(Awaitable[_T]):
|
||||
_state: str
|
||||
@property
|
||||
def _exception(self) -> BaseException | None: ...
|
||||
|
||||
117
crates/red_knot_vendored/vendor/typeshed/stdlib/_blake2.pyi
vendored
Normal file
117
crates/red_knot_vendored/vendor/typeshed/stdlib/_blake2.pyi
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
import sys
|
||||
from _typeshed import ReadableBuffer
|
||||
from typing import ClassVar, final
|
||||
from typing_extensions import Self
|
||||
|
||||
BLAKE2B_MAX_DIGEST_SIZE: int = 64
|
||||
BLAKE2B_MAX_KEY_SIZE: int = 64
|
||||
BLAKE2B_PERSON_SIZE: int = 16
|
||||
BLAKE2B_SALT_SIZE: int = 16
|
||||
BLAKE2S_MAX_DIGEST_SIZE: int = 32
|
||||
BLAKE2S_MAX_KEY_SIZE: int = 32
|
||||
BLAKE2S_PERSON_SIZE: int = 8
|
||||
BLAKE2S_SALT_SIZE: int = 8
|
||||
|
||||
@final
|
||||
class blake2b:
|
||||
MAX_DIGEST_SIZE: ClassVar[int] = 64
|
||||
MAX_KEY_SIZE: ClassVar[int] = 64
|
||||
PERSON_SIZE: ClassVar[int] = 16
|
||||
SALT_SIZE: ClassVar[int] = 16
|
||||
block_size: int
|
||||
digest_size: int
|
||||
name: str
|
||||
if sys.version_info >= (3, 9):
|
||||
def __init__(
|
||||
self,
|
||||
data: ReadableBuffer = b"",
|
||||
/,
|
||||
*,
|
||||
digest_size: int = 64,
|
||||
key: ReadableBuffer = b"",
|
||||
salt: ReadableBuffer = b"",
|
||||
person: ReadableBuffer = b"",
|
||||
fanout: int = 1,
|
||||
depth: int = 1,
|
||||
leaf_size: int = 0,
|
||||
node_offset: int = 0,
|
||||
node_depth: int = 0,
|
||||
inner_size: int = 0,
|
||||
last_node: bool = False,
|
||||
usedforsecurity: bool = True,
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(
|
||||
self,
|
||||
data: ReadableBuffer = b"",
|
||||
/,
|
||||
*,
|
||||
digest_size: int = 64,
|
||||
key: ReadableBuffer = b"",
|
||||
salt: ReadableBuffer = b"",
|
||||
person: ReadableBuffer = b"",
|
||||
fanout: int = 1,
|
||||
depth: int = 1,
|
||||
leaf_size: int = 0,
|
||||
node_offset: int = 0,
|
||||
node_depth: int = 0,
|
||||
inner_size: int = 0,
|
||||
last_node: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def copy(self) -> Self: ...
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def update(self, data: ReadableBuffer, /) -> None: ...
|
||||
|
||||
@final
|
||||
class blake2s:
|
||||
MAX_DIGEST_SIZE: ClassVar[int] = 32
|
||||
MAX_KEY_SIZE: ClassVar[int] = 32
|
||||
PERSON_SIZE: ClassVar[int] = 8
|
||||
SALT_SIZE: ClassVar[int] = 8
|
||||
block_size: int
|
||||
digest_size: int
|
||||
name: str
|
||||
if sys.version_info >= (3, 9):
|
||||
def __init__(
|
||||
self,
|
||||
data: ReadableBuffer = b"",
|
||||
/,
|
||||
*,
|
||||
digest_size: int = 32,
|
||||
key: ReadableBuffer = b"",
|
||||
salt: ReadableBuffer = b"",
|
||||
person: ReadableBuffer = b"",
|
||||
fanout: int = 1,
|
||||
depth: int = 1,
|
||||
leaf_size: int = 0,
|
||||
node_offset: int = 0,
|
||||
node_depth: int = 0,
|
||||
inner_size: int = 0,
|
||||
last_node: bool = False,
|
||||
usedforsecurity: bool = True,
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(
|
||||
self,
|
||||
data: ReadableBuffer = b"",
|
||||
/,
|
||||
*,
|
||||
digest_size: int = 32,
|
||||
key: ReadableBuffer = b"",
|
||||
salt: ReadableBuffer = b"",
|
||||
person: ReadableBuffer = b"",
|
||||
fanout: int = 1,
|
||||
depth: int = 1,
|
||||
leaf_size: int = 0,
|
||||
node_offset: int = 0,
|
||||
node_depth: int = 0,
|
||||
inner_size: int = 0,
|
||||
last_node: bool = False,
|
||||
) -> None: ...
|
||||
|
||||
def copy(self) -> Self: ...
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def update(self, data: ReadableBuffer, /) -> None: ...
|
||||
@@ -1,3 +1,4 @@
|
||||
import csv
|
||||
import sys
|
||||
from _typeshed import SupportsWrite
|
||||
from collections.abc import Iterable, Iterator
|
||||
@@ -20,7 +21,7 @@ _QuotingType: TypeAlias = int
|
||||
|
||||
class Error(Exception): ...
|
||||
|
||||
_DialectLike: TypeAlias = str | Dialect | type[Dialect]
|
||||
_DialectLike: TypeAlias = str | Dialect | csv.Dialect | type[Dialect | csv.Dialect]
|
||||
|
||||
class Dialect:
|
||||
delimiter: str
|
||||
|
||||
112
crates/red_knot_vendored/vendor/typeshed/stdlib/_frozen_importlib.pyi
vendored
Normal file
112
crates/red_knot_vendored/vendor/typeshed/stdlib/_frozen_importlib.pyi
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import sys
|
||||
import types
|
||||
from _typeshed.importlib import LoaderProtocol
|
||||
from collections.abc import Mapping, Sequence
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
# Signature of `builtins.__import__` should be kept identical to `importlib.__import__`
|
||||
def __import__(
|
||||
name: str,
|
||||
globals: Mapping[str, object] | None = None,
|
||||
locals: Mapping[str, object] | None = None,
|
||||
fromlist: Sequence[str] = (),
|
||||
level: int = 0,
|
||||
) -> ModuleType: ...
|
||||
def spec_from_loader(
|
||||
name: str, loader: LoaderProtocol | None, *, origin: str | None = None, is_package: bool | None = None
|
||||
) -> importlib.machinery.ModuleSpec | None: ...
|
||||
def module_from_spec(spec: importlib.machinery.ModuleSpec) -> types.ModuleType: ...
|
||||
def _init_module_attrs(
|
||||
spec: importlib.machinery.ModuleSpec, module: types.ModuleType, *, override: bool = False
|
||||
) -> types.ModuleType: ...
|
||||
|
||||
class ModuleSpec:
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
loader: importlib.abc.Loader | None,
|
||||
*,
|
||||
origin: str | None = None,
|
||||
loader_state: Any = None,
|
||||
is_package: bool | None = None,
|
||||
) -> None: ...
|
||||
name: str
|
||||
loader: importlib.abc.Loader | None
|
||||
origin: str | None
|
||||
submodule_search_locations: list[str] | None
|
||||
loader_state: Any
|
||||
cached: str | None
|
||||
@property
|
||||
def parent(self) -> str | None: ...
|
||||
has_location: bool
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
|
||||
class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
# MetaPathFinder
|
||||
if sys.version_info < (3, 12):
|
||||
@classmethod
|
||||
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
|
||||
|
||||
@classmethod
|
||||
def find_spec(
|
||||
cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None
|
||||
) -> ModuleSpec | None: ...
|
||||
# InspectLoader
|
||||
@classmethod
|
||||
def is_package(cls, fullname: str) -> bool: ...
|
||||
@classmethod
|
||||
def load_module(cls, fullname: str) -> types.ModuleType: ...
|
||||
@classmethod
|
||||
def get_code(cls, fullname: str) -> None: ...
|
||||
@classmethod
|
||||
def get_source(cls, fullname: str) -> None: ...
|
||||
# Loader
|
||||
if sys.version_info < (3, 12):
|
||||
@staticmethod
|
||||
def module_repr(module: types.ModuleType) -> str: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
def create_module(spec: ModuleSpec) -> types.ModuleType | None: ...
|
||||
@staticmethod
|
||||
def exec_module(module: types.ModuleType) -> None: ...
|
||||
else:
|
||||
@classmethod
|
||||
def create_module(cls, spec: ModuleSpec) -> types.ModuleType | None: ...
|
||||
@classmethod
|
||||
def exec_module(cls, module: types.ModuleType) -> None: ...
|
||||
|
||||
class FrozenImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
|
||||
# MetaPathFinder
|
||||
if sys.version_info < (3, 12):
|
||||
@classmethod
|
||||
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
|
||||
|
||||
@classmethod
|
||||
def find_spec(
|
||||
cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None
|
||||
) -> ModuleSpec | None: ...
|
||||
# InspectLoader
|
||||
@classmethod
|
||||
def is_package(cls, fullname: str) -> bool: ...
|
||||
@classmethod
|
||||
def load_module(cls, fullname: str) -> types.ModuleType: ...
|
||||
@classmethod
|
||||
def get_code(cls, fullname: str) -> None: ...
|
||||
@classmethod
|
||||
def get_source(cls, fullname: str) -> None: ...
|
||||
# Loader
|
||||
if sys.version_info < (3, 12):
|
||||
@staticmethod
|
||||
def module_repr(m: types.ModuleType) -> str: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
def create_module(spec: ModuleSpec) -> types.ModuleType | None: ...
|
||||
else:
|
||||
@classmethod
|
||||
def create_module(cls, spec: ModuleSpec) -> types.ModuleType | None: ...
|
||||
|
||||
@staticmethod
|
||||
def exec_module(module: types.ModuleType) -> None: ...
|
||||
178
crates/red_knot_vendored/vendor/typeshed/stdlib/_frozen_importlib_external.pyi
vendored
Normal file
178
crates/red_knot_vendored/vendor/typeshed/stdlib/_frozen_importlib_external.pyi
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
import _ast
|
||||
import _io
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import sys
|
||||
import types
|
||||
from _typeshed import ReadableBuffer, StrOrBytesPath, StrPath
|
||||
from _typeshed.importlib import LoaderProtocol
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping, MutableSequence, Sequence
|
||||
from importlib.machinery import ModuleSpec
|
||||
from importlib.metadata import DistributionFinder, PathDistribution
|
||||
from typing import Any, Literal
|
||||
from typing_extensions import Self, deprecated
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
import importlib.readers
|
||||
|
||||
if sys.platform == "win32":
|
||||
path_separators: Literal["\\/"]
|
||||
path_sep: Literal["\\"]
|
||||
path_sep_tuple: tuple[Literal["\\"], Literal["/"]]
|
||||
else:
|
||||
path_separators: Literal["/"]
|
||||
path_sep: Literal["/"]
|
||||
path_sep_tuple: tuple[Literal["/"]]
|
||||
|
||||
MAGIC_NUMBER: bytes
|
||||
|
||||
def cache_from_source(path: str, debug_override: bool | None = None, *, optimization: Any | None = None) -> str: ...
|
||||
def source_from_cache(path: str) -> str: ...
|
||||
def decode_source(source_bytes: ReadableBuffer) -> str: ...
|
||||
def spec_from_file_location(
|
||||
name: str,
|
||||
location: StrOrBytesPath | None = None,
|
||||
*,
|
||||
loader: LoaderProtocol | None = None,
|
||||
submodule_search_locations: list[str] | None = ...,
|
||||
) -> importlib.machinery.ModuleSpec | None: ...
|
||||
|
||||
class WindowsRegistryFinder(importlib.abc.MetaPathFinder):
|
||||
if sys.version_info < (3, 12):
|
||||
@classmethod
|
||||
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
|
||||
|
||||
@classmethod
|
||||
def find_spec(
|
||||
cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None
|
||||
) -> ModuleSpec | None: ...
|
||||
|
||||
class PathFinder(importlib.abc.MetaPathFinder):
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
def invalidate_caches() -> None: ...
|
||||
else:
|
||||
@classmethod
|
||||
def invalidate_caches(cls) -> None: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
def find_distributions(context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ...
|
||||
else:
|
||||
@classmethod
|
||||
def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ...
|
||||
|
||||
@classmethod
|
||||
def find_spec(
|
||||
cls, fullname: str, path: Sequence[str] | None = None, target: types.ModuleType | None = None
|
||||
) -> ModuleSpec | None: ...
|
||||
if sys.version_info < (3, 12):
|
||||
@classmethod
|
||||
def find_module(cls, fullname: str, path: Sequence[str] | None = None) -> importlib.abc.Loader | None: ...
|
||||
|
||||
SOURCE_SUFFIXES: list[str]
|
||||
DEBUG_BYTECODE_SUFFIXES: list[str]
|
||||
OPTIMIZED_BYTECODE_SUFFIXES: list[str]
|
||||
BYTECODE_SUFFIXES: list[str]
|
||||
EXTENSION_SUFFIXES: list[str]
|
||||
|
||||
class FileFinder(importlib.abc.PathEntryFinder):
|
||||
path: str
|
||||
def __init__(self, path: str, *loader_details: tuple[type[importlib.abc.Loader], list[str]]) -> None: ...
|
||||
@classmethod
|
||||
def path_hook(
|
||||
cls, *loader_details: tuple[type[importlib.abc.Loader], list[str]]
|
||||
) -> Callable[[str], importlib.abc.PathEntryFinder]: ...
|
||||
|
||||
class _LoaderBasics:
|
||||
def is_package(self, fullname: str) -> bool: ...
|
||||
def create_module(self, spec: ModuleSpec) -> types.ModuleType | None: ...
|
||||
def exec_module(self, module: types.ModuleType) -> None: ...
|
||||
def load_module(self, fullname: str) -> types.ModuleType: ...
|
||||
|
||||
class SourceLoader(_LoaderBasics):
|
||||
def path_mtime(self, path: str) -> float: ...
|
||||
def set_data(self, path: str, data: bytes) -> None: ...
|
||||
def get_source(self, fullname: str) -> str | None: ...
|
||||
def path_stats(self, path: str) -> Mapping[str, Any]: ...
|
||||
def source_to_code(
|
||||
self, data: ReadableBuffer | str | _ast.Module | _ast.Expression | _ast.Interactive, path: ReadableBuffer | StrPath
|
||||
) -> types.CodeType: ...
|
||||
def get_code(self, fullname: str) -> types.CodeType | None: ...
|
||||
|
||||
class FileLoader:
|
||||
name: str
|
||||
path: str
|
||||
def __init__(self, fullname: str, path: str) -> None: ...
|
||||
def get_data(self, path: str) -> bytes: ...
|
||||
def get_filename(self, name: str | None = None) -> str: ...
|
||||
def load_module(self, name: str | None = None) -> types.ModuleType: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.FileReader: ...
|
||||
else:
|
||||
def get_resource_reader(self, module: types.ModuleType) -> Self | None: ...
|
||||
def open_resource(self, resource: str) -> _io.FileIO: ...
|
||||
def resource_path(self, resource: str) -> str: ...
|
||||
def is_resource(self, name: str) -> bool: ...
|
||||
def contents(self) -> Iterator[str]: ...
|
||||
|
||||
class SourceFileLoader(importlib.abc.FileLoader, FileLoader, importlib.abc.SourceLoader, SourceLoader): # type: ignore[misc] # incompatible method arguments in base classes
|
||||
def set_data(self, path: str, data: ReadableBuffer, *, _mode: int = 0o666) -> None: ...
|
||||
def path_stats(self, path: str) -> Mapping[str, Any]: ...
|
||||
|
||||
class SourcelessFileLoader(importlib.abc.FileLoader, FileLoader, _LoaderBasics):
|
||||
def get_code(self, fullname: str) -> types.CodeType | None: ...
|
||||
def get_source(self, fullname: str) -> None: ...
|
||||
|
||||
class ExtensionFileLoader(FileLoader, _LoaderBasics, importlib.abc.ExecutionLoader):
|
||||
def __init__(self, name: str, path: str) -> None: ...
|
||||
def get_filename(self, name: str | None = None) -> str: ...
|
||||
def get_source(self, fullname: str) -> None: ...
|
||||
def create_module(self, spec: ModuleSpec) -> types.ModuleType: ...
|
||||
def exec_module(self, module: types.ModuleType) -> None: ...
|
||||
def get_code(self, fullname: str) -> None: ...
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
class NamespaceLoader(importlib.abc.InspectLoader):
|
||||
def __init__(
|
||||
self, name: str, path: MutableSequence[str], path_finder: Callable[[str, tuple[str, ...]], ModuleSpec]
|
||||
) -> None: ...
|
||||
def is_package(self, fullname: str) -> Literal[True]: ...
|
||||
def get_source(self, fullname: str) -> Literal[""]: ...
|
||||
def get_code(self, fullname: str) -> types.CodeType: ...
|
||||
def create_module(self, spec: ModuleSpec) -> None: ...
|
||||
def exec_module(self, module: types.ModuleType) -> None: ...
|
||||
@deprecated("load_module() is deprecated; use exec_module() instead")
|
||||
def load_module(self, fullname: str) -> types.ModuleType: ...
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
|
||||
if sys.version_info < (3, 12):
|
||||
@staticmethod
|
||||
@deprecated("module_repr() is deprecated, and has been removed in Python 3.12")
|
||||
def module_repr(module: types.ModuleType) -> str: ...
|
||||
|
||||
_NamespaceLoader = NamespaceLoader
|
||||
else:
|
||||
class _NamespaceLoader:
|
||||
def __init__(
|
||||
self, name: str, path: MutableSequence[str], path_finder: Callable[[str, tuple[str, ...]], ModuleSpec]
|
||||
) -> None: ...
|
||||
def is_package(self, fullname: str) -> Literal[True]: ...
|
||||
def get_source(self, fullname: str) -> Literal[""]: ...
|
||||
def get_code(self, fullname: str) -> types.CodeType: ...
|
||||
def create_module(self, spec: ModuleSpec) -> None: ...
|
||||
def exec_module(self, module: types.ModuleType) -> None: ...
|
||||
@deprecated("load_module() is deprecated; use exec_module() instead")
|
||||
def load_module(self, fullname: str) -> types.ModuleType: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
@staticmethod
|
||||
@deprecated("module_repr() is deprecated, and has been removed in Python 3.12")
|
||||
def module_repr(module: types.ModuleType) -> str: ...
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.NamespaceReader: ...
|
||||
else:
|
||||
@classmethod
|
||||
@deprecated("module_repr() is deprecated, and has been removed in Python 3.12")
|
||||
def module_repr(cls, module: types.ModuleType) -> str: ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
class AppleFrameworkLoader(ExtensionFileLoader, importlib.abc.ExecutionLoader): ...
|
||||
@@ -7,7 +7,7 @@ _Configs: TypeAlias = Literal["default", "isolated", "legacy", "empty", ""]
|
||||
|
||||
class InterpreterError(Exception): ...
|
||||
class InterpreterNotFoundError(InterpreterError): ...
|
||||
class NotShareableError(Exception): ...
|
||||
class NotShareableError(ValueError): ...
|
||||
|
||||
class CrossInterpreterBufferView:
|
||||
def __buffer__(self, flags: int, /) -> memoryview: ...
|
||||
|
||||
@@ -86,19 +86,24 @@ class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc]
|
||||
|
||||
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 __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> 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 __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def write(self, buffer: ReadableBuffer, /) -> int: ...
|
||||
|
||||
class BufferedRandom(BufferedReader, BufferedWriter, BufferedIOBase, _BufferedIOBase): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
mode: str
|
||||
name: Any
|
||||
raw: RawIOBase
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def seek(self, target: int, whence: int = 0, /) -> int: ... # stubtest needs this
|
||||
def peek(self, size: int = 0, /) -> bytes: ...
|
||||
|
||||
class BufferedRWPair(BufferedIOBase, _BufferedIOBase):
|
||||
def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = ...) -> None: ...
|
||||
def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def peek(self, size: int = ..., /) -> bytes: ...
|
||||
|
||||
class _TextIOBase(_IOBase):
|
||||
@@ -173,19 +178,23 @@ class TextIOWrapper(TextIOBase, _TextIOBase, TextIO, Generic[_BufferT_co]): # t
|
||||
# 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
|
||||
class StringIO(TextIOBase, _TextIOBase, TextIO): # 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: ...
|
||||
@property
|
||||
def line_buffering(self) -> bool: ...
|
||||
|
||||
class IncrementalNewlineDecoder(codecs.IncrementalDecoder):
|
||||
class IncrementalNewlineDecoder:
|
||||
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 getstate(self) -> tuple[bytes, int]: ...
|
||||
def reset(self) -> None: ...
|
||||
def setstate(self, state: tuple[bytes, int], /) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
|
||||
@@ -105,7 +105,7 @@ class _SSLContext:
|
||||
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
|
||||
self, callback: Callable[[str | None], bytes] | None, identity_hint: str | None = None
|
||||
) -> None: ...
|
||||
|
||||
@final
|
||||
|
||||
@@ -2023,11 +2023,18 @@ class NodeVisitor:
|
||||
def visit_AugLoad(self, node: AugLoad) -> Any: ...
|
||||
def visit_AugStore(self, node: AugStore) -> Any: ...
|
||||
def visit_Param(self, node: Param) -> Any: ...
|
||||
def visit_Num(self, node: Num) -> Any: ...
|
||||
def visit_Str(self, node: Str) -> Any: ...
|
||||
def visit_Bytes(self, node: Bytes) -> Any: ...
|
||||
def visit_NameConstant(self, node: NameConstant) -> Any: ...
|
||||
def visit_Ellipsis(self, node: Ellipsis) -> Any: ...
|
||||
|
||||
if sys.version_info < (3, 14):
|
||||
@deprecated("Replaced by visit_Constant; removed in Python 3.14")
|
||||
def visit_Num(self, node: Num) -> Any: ... # type: ignore[deprecated]
|
||||
@deprecated("Replaced by visit_Constant; removed in Python 3.14")
|
||||
def visit_Str(self, node: Str) -> Any: ... # type: ignore[deprecated]
|
||||
@deprecated("Replaced by visit_Constant; removed in Python 3.14")
|
||||
def visit_Bytes(self, node: Bytes) -> Any: ... # type: ignore[deprecated]
|
||||
@deprecated("Replaced by visit_Constant; removed in Python 3.14")
|
||||
def visit_NameConstant(self, node: NameConstant) -> Any: ... # type: ignore[deprecated]
|
||||
@deprecated("Replaced by visit_Constant; removed in Python 3.14")
|
||||
def visit_Ellipsis(self, node: Ellipsis) -> Any: ... # type: ignore[deprecated]
|
||||
|
||||
class NodeTransformer(NodeVisitor):
|
||||
def generic_visit(self, node: AST) -> AST: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import ssl
|
||||
import sys
|
||||
from _typeshed import ReadableBuffer, StrPath
|
||||
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence, Sized
|
||||
from collections.abc import Awaitable, Callable, Iterable, Sequence, Sized
|
||||
from types import ModuleType
|
||||
from typing import Any, Protocol, SupportsIndex
|
||||
from typing_extensions import Self, TypeAlias
|
||||
@@ -137,7 +137,7 @@ class StreamWriter:
|
||||
elif sys.version_info >= (3, 11):
|
||||
def __del__(self) -> None: ...
|
||||
|
||||
class StreamReader(AsyncIterator[bytes]):
|
||||
class StreamReader:
|
||||
def __init__(self, limit: int = 65536, loop: events.AbstractEventLoop | None = None) -> None: ...
|
||||
def exception(self) -> Exception: ...
|
||||
def set_exception(self, exc: Exception) -> None: ...
|
||||
|
||||
@@ -7,7 +7,7 @@ from _asyncio import (
|
||||
_register_task as _register_task,
|
||||
_unregister_task as _unregister_task,
|
||||
)
|
||||
from collections.abc import Awaitable, Coroutine, Generator, Iterable, Iterator
|
||||
from collections.abc import AsyncIterator, Awaitable, Coroutine, Generator, Iterable, Iterator
|
||||
from typing import Any, Literal, Protocol, TypeVar, overload
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
@@ -84,7 +84,12 @@ FIRST_COMPLETED = concurrent.futures.FIRST_COMPLETED
|
||||
FIRST_EXCEPTION = concurrent.futures.FIRST_EXCEPTION
|
||||
ALL_COMPLETED = concurrent.futures.ALL_COMPLETED
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
if sys.version_info >= (3, 13):
|
||||
class _SyncAndAsyncIterator(Iterator[_T_co], AsyncIterator[_T_co], Protocol[_T_co]): ...
|
||||
|
||||
def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> _SyncAndAsyncIterator[Future[_T]]: ...
|
||||
|
||||
elif sys.version_info >= (3, 10):
|
||||
def as_completed(fs: Iterable[_FutureLike[_T]], *, timeout: float | None = None) -> Iterator[Future[_T]]: ...
|
||||
|
||||
else:
|
||||
|
||||
@@ -9,6 +9,7 @@ from _typeshed import (
|
||||
ConvertibleToFloat,
|
||||
ConvertibleToInt,
|
||||
FileDescriptorOrPath,
|
||||
MaybeNone,
|
||||
OpenBinaryMode,
|
||||
OpenBinaryModeReading,
|
||||
OpenBinaryModeUpdating,
|
||||
@@ -94,6 +95,9 @@ _SupportsAnextT = TypeVar("_SupportsAnextT", bound=SupportsAnext[Any], covariant
|
||||
_AwaitableT = TypeVar("_AwaitableT", bound=Awaitable[Any])
|
||||
_AwaitableT_co = TypeVar("_AwaitableT_co", bound=Awaitable[Any], covariant=True)
|
||||
_P = ParamSpec("_P")
|
||||
_StartT = TypeVar("_StartT", covariant=True, default=Any)
|
||||
_StopT = TypeVar("_StopT", covariant=True, default=Any)
|
||||
_StepT = TypeVar("_StepT", covariant=True, default=Any)
|
||||
|
||||
class object:
|
||||
__doc__: str | None
|
||||
@@ -834,7 +838,7 @@ _IntegerFormats: TypeAlias = Literal[
|
||||
]
|
||||
|
||||
@final
|
||||
class memoryview(Generic[_I]):
|
||||
class memoryview(Sequence[_I]):
|
||||
@property
|
||||
def format(self) -> str: ...
|
||||
@property
|
||||
@@ -884,7 +888,7 @@ class memoryview(Generic[_I]):
|
||||
@overload
|
||||
def __setitem__(self, key: slice, value: ReadableBuffer, /) -> None: ...
|
||||
@overload
|
||||
def __setitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], value: SupportsIndex, /) -> None: ...
|
||||
def __setitem__(self, key: SupportsIndex | tuple[SupportsIndex, ...], value: _I, /) -> None: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
def tobytes(self, order: Literal["C", "F", "A"] | None = "C") -> bytes: ...
|
||||
else:
|
||||
@@ -897,6 +901,11 @@ class memoryview(Generic[_I]):
|
||||
def __buffer__(self, flags: int, /) -> memoryview: ...
|
||||
def __release_buffer__(self, buffer: memoryview, /) -> None: ...
|
||||
|
||||
# These are inherited from the Sequence ABC, but don't actually exist on memoryview.
|
||||
# See https://github.com/python/cpython/issues/125420
|
||||
index: ClassVar[None] # type: ignore[assignment]
|
||||
count: ClassVar[None] # type: ignore[assignment]
|
||||
|
||||
@final
|
||||
class bool(int):
|
||||
def __new__(cls, o: object = ..., /) -> Self: ...
|
||||
@@ -931,19 +940,31 @@ class bool(int):
|
||||
def __invert__(self) -> int: ...
|
||||
|
||||
@final
|
||||
class slice:
|
||||
class slice(Generic[_StartT, _StopT, _StepT]):
|
||||
@property
|
||||
def start(self) -> Any: ...
|
||||
def start(self) -> _StartT: ...
|
||||
@property
|
||||
def step(self) -> Any: ...
|
||||
def step(self) -> _StepT: ...
|
||||
@property
|
||||
def stop(self) -> Any: ...
|
||||
def stop(self) -> _StopT: ...
|
||||
@overload
|
||||
def __new__(cls, stop: Any, /) -> Self: ...
|
||||
def __new__(cls, stop: int | None, /) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
|
||||
@overload
|
||||
def __new__(cls, start: Any, stop: Any, step: Any = ..., /) -> Self: ...
|
||||
def __new__(
|
||||
cls, start: int | None, stop: int | None, step: int | None = None, /
|
||||
) -> slice[int | MaybeNone, int | MaybeNone, int | MaybeNone]: ...
|
||||
@overload
|
||||
def __new__(cls, stop: _T2, /) -> slice[Any, _T2, Any]: ...
|
||||
@overload
|
||||
def __new__(cls, start: _T1, stop: _T2, /) -> slice[_T1, _T2, Any]: ...
|
||||
@overload
|
||||
def __new__(cls, start: _T1, stop: _T2, step: _T3, /) -> slice[_T1, _T2, _T3]: ...
|
||||
def __eq__(self, value: object, /) -> bool: ...
|
||||
__hash__: ClassVar[None] # type: ignore[assignment]
|
||||
if sys.version_info >= (3, 12):
|
||||
def __hash__(self) -> int: ...
|
||||
else:
|
||||
__hash__: ClassVar[None] # type: ignore[assignment]
|
||||
|
||||
def indices(self, len: SupportsIndex, /) -> tuple[int, int, int]: ...
|
||||
|
||||
class tuple(Sequence[_T_co]):
|
||||
@@ -1207,7 +1228,7 @@ class frozenset(AbstractSet[_T_co]):
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
class enumerate(Iterator[tuple[int, _T]]):
|
||||
class enumerate(Generic[_T]):
|
||||
def __new__(cls, iterable: Iterable[_T], start: int = 0) -> Self: ...
|
||||
def __iter__(self) -> Self: ...
|
||||
def __next__(self) -> tuple[int, _T]: ...
|
||||
@@ -1401,7 +1422,7 @@ else:
|
||||
|
||||
def exit(code: sys._ExitCode = None) -> NoReturn: ...
|
||||
|
||||
class filter(Iterator[_T]):
|
||||
class filter(Generic[_T]):
|
||||
@overload
|
||||
def __new__(cls, function: None, iterable: Iterable[_T | None], /) -> Self: ...
|
||||
@overload
|
||||
@@ -1462,7 +1483,7 @@ def len(obj: Sized, /) -> int: ...
|
||||
def license() -> None: ...
|
||||
def locals() -> dict[str, Any]: ...
|
||||
|
||||
class map(Iterator[_S]):
|
||||
class map(Generic[_S]):
|
||||
@overload
|
||||
def __new__(cls, func: Callable[[_T1], _S], iter1: Iterable[_T1], /) -> Self: ...
|
||||
@overload
|
||||
@@ -1704,7 +1725,7 @@ def pow(base: _SupportsSomeKindOfPow, exp: float, mod: None = None) -> Any: ...
|
||||
def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = None) -> complex: ...
|
||||
def quit(code: sys._ExitCode = None) -> NoReturn: ...
|
||||
|
||||
class reversed(Iterator[_T]):
|
||||
class reversed(Generic[_T]):
|
||||
@overload
|
||||
def __new__(cls, sequence: Reversible[_T], /) -> Iterator[_T]: ... # type: ignore[misc]
|
||||
@overload
|
||||
@@ -1765,7 +1786,7 @@ def vars(object: type, /) -> types.MappingProxyType[str, Any]: ...
|
||||
@overload
|
||||
def vars(object: Any = ..., /) -> dict[str, Any]: ...
|
||||
|
||||
class zip(Iterator[_T_co]):
|
||||
class zip(Generic[_T_co]):
|
||||
if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def __new__(cls, *, strict: bool = ...) -> zip[Any]: ...
|
||||
@@ -1895,7 +1916,7 @@ class StopIteration(Exception):
|
||||
value: Any
|
||||
|
||||
class OSError(Exception):
|
||||
errno: int
|
||||
errno: int | None
|
||||
strerror: str
|
||||
# filename, filename2 are actually str | bytes | None
|
||||
filename: Any
|
||||
|
||||
@@ -58,9 +58,11 @@ class ContextDecorator:
|
||||
def _recreate_cm(self) -> Self: ...
|
||||
def __call__(self, func: _F) -> _F: ...
|
||||
|
||||
class _GeneratorContextManager(AbstractContextManager[_T_co, bool | None], ContextDecorator):
|
||||
class _GeneratorContextManagerBase: ...
|
||||
|
||||
class _GeneratorContextManager(_GeneratorContextManagerBase, AbstractContextManager[_T_co, bool | None], ContextDecorator):
|
||||
# __init__ and all instance attributes are actually inherited from _GeneratorContextManagerBase
|
||||
# _GeneratorContextManagerBase is more trouble than it's worth to include in the stub; see #6676
|
||||
# adding them there is more trouble than it's worth to include in the stub; see #6676
|
||||
def __init__(self, func: Callable[..., Iterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
||||
gen: Generator[_T_co, Any, Any]
|
||||
func: Callable[..., Generator[_T_co, Any, Any]]
|
||||
@@ -84,9 +86,11 @@ if sys.version_info >= (3, 10):
|
||||
def _recreate_cm(self) -> Self: ...
|
||||
def __call__(self, func: _AF) -> _AF: ...
|
||||
|
||||
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator):
|
||||
class _AsyncGeneratorContextManager(
|
||||
_GeneratorContextManagerBase, AbstractAsyncContextManager[_T_co, bool | None], AsyncContextDecorator
|
||||
):
|
||||
# __init__ and these attributes are actually defined in the base class _GeneratorContextManagerBase,
|
||||
# which is more trouble than it's worth to include in the stub (see #6676)
|
||||
# adding them there is more trouble than it's worth to include in the stub (see #6676)
|
||||
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
||||
gen: AsyncGenerator[_T_co, Any]
|
||||
func: Callable[..., AsyncGenerator[_T_co, Any]]
|
||||
@@ -97,7 +101,7 @@ if sys.version_info >= (3, 10):
|
||||
) -> bool | None: ...
|
||||
|
||||
else:
|
||||
class _AsyncGeneratorContextManager(AbstractAsyncContextManager[_T_co, bool | None]):
|
||||
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, AbstractAsyncContextManager[_T_co, bool | None]):
|
||||
def __init__(self, func: Callable[..., AsyncIterator[_T_co]], args: tuple[Any, ...], kwds: dict[str, Any]) -> None: ...
|
||||
gen: AsyncGenerator[_T_co, Any]
|
||||
func: Callable[..., AsyncGenerator[_T_co, Any]]
|
||||
|
||||
@@ -4,7 +4,6 @@ from _csv import (
|
||||
QUOTE_MINIMAL as QUOTE_MINIMAL,
|
||||
QUOTE_NONE as QUOTE_NONE,
|
||||
QUOTE_NONNUMERIC as QUOTE_NONNUMERIC,
|
||||
Dialect as _Dialect,
|
||||
Error as Error,
|
||||
__version__ as __version__,
|
||||
_DialectLike,
|
||||
@@ -24,7 +23,7 @@ if sys.version_info >= (3, 12):
|
||||
from _csv import QUOTE_NOTNULL as QUOTE_NOTNULL, QUOTE_STRINGS as QUOTE_STRINGS
|
||||
|
||||
from _typeshed import SupportsWrite
|
||||
from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence
|
||||
from collections.abc import Collection, Iterable, Mapping, Sequence
|
||||
from typing import Any, Generic, Literal, TypeVar, overload
|
||||
from typing_extensions import Self
|
||||
|
||||
@@ -59,14 +58,22 @@ if sys.version_info < (3, 13):
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class Dialect(_Dialect):
|
||||
class Dialect:
|
||||
delimiter: str
|
||||
quotechar: str | None
|
||||
escapechar: str | None
|
||||
doublequote: bool
|
||||
skipinitialspace: bool
|
||||
lineterminator: str
|
||||
quoting: _QuotingType
|
||||
strict: bool
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
class excel(Dialect): ...
|
||||
class excel_tab(excel): ...
|
||||
class unix_dialect(Dialect): ...
|
||||
|
||||
class DictReader(Iterator[dict[_T | Any, str | Any]], Generic[_T]):
|
||||
class DictReader(Generic[_T]):
|
||||
fieldnames: Sequence[_T] | None
|
||||
restkey: _T | None
|
||||
restval: str | Any | None
|
||||
|
||||
13
crates/red_knot_vendored/vendor/typeshed/stdlib/distutils/_msvccompiler.pyi
vendored
Normal file
13
crates/red_knot_vendored/vendor/typeshed/stdlib/distutils/_msvccompiler.pyi
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
from _typeshed import Incomplete
|
||||
from distutils.ccompiler import CCompiler
|
||||
from typing import ClassVar, Final
|
||||
|
||||
PLAT_SPEC_TO_RUNTIME: Final[dict[str, str]]
|
||||
PLAT_TO_VCVARS: Final[dict[str, str]]
|
||||
|
||||
class MSVCCompiler(CCompiler):
|
||||
compiler_type: ClassVar[str]
|
||||
executables: ClassVar[dict[Incomplete, Incomplete]]
|
||||
res_extension: ClassVar[str]
|
||||
initialized: bool
|
||||
def initialize(self, plat_name: str | None = None) -> None: ...
|
||||
@@ -1,6 +1,6 @@
|
||||
from _typeshed import Unused
|
||||
from _typeshed import Incomplete, Unused
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -15,13 +15,13 @@ class bdist(Command):
|
||||
default_format: ClassVar[dict[str, str]]
|
||||
format_commands: ClassVar[list[str]]
|
||||
format_command: ClassVar[dict[str, tuple[str, str]]]
|
||||
bdist_base: Any
|
||||
plat_name: Any
|
||||
formats: Any
|
||||
dist_dir: Any
|
||||
bdist_base: Incomplete
|
||||
plat_name: Incomplete
|
||||
formats: Incomplete
|
||||
dist_dir: Incomplete
|
||||
skip_build: int
|
||||
group: Any
|
||||
owner: Any
|
||||
group: Incomplete
|
||||
owner: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -7,15 +8,15 @@ class bdist_dumb(Command):
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
default_format: ClassVar[dict[str, str]]
|
||||
bdist_dir: Any
|
||||
plat_name: Any
|
||||
format: Any
|
||||
bdist_dir: Incomplete
|
||||
plat_name: Incomplete
|
||||
format: Incomplete
|
||||
keep_temp: int
|
||||
dist_dir: Any
|
||||
skip_build: Any
|
||||
dist_dir: Incomplete
|
||||
skip_build: Incomplete
|
||||
relative: int
|
||||
owner: Any
|
||||
group: Any
|
||||
owner: Incomplete
|
||||
group: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
import sys
|
||||
from typing import Any, ClassVar, Literal
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar, Literal
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
if sys.platform == "win32":
|
||||
from msilib import Dialog
|
||||
from msilib import Control, Dialog
|
||||
|
||||
class PyDialog(Dialog):
|
||||
def __init__(self, *args, **kw) -> None: ...
|
||||
def title(self, title) -> None: ...
|
||||
def back(self, title, next, name: str = "Back", active: bool | Literal[0, 1] = 1): ...
|
||||
def cancel(self, title, next, name: str = "Cancel", active: bool | Literal[0, 1] = 1): ...
|
||||
def next(self, title, next, name: str = "Next", active: bool | Literal[0, 1] = 1): ...
|
||||
def xbutton(self, name, title, next, xpos): ...
|
||||
def back(self, title, next, name: str = "Back", active: bool | Literal[0, 1] = 1) -> Control: ...
|
||||
def cancel(self, title, next, name: str = "Cancel", active: bool | Literal[0, 1] = 1) -> Control: ...
|
||||
def next(self, title, next, name: str = "Next", active: bool | Literal[0, 1] = 1) -> Control: ...
|
||||
def xbutton(self, name, title, next, xpos) -> Control: ...
|
||||
|
||||
class bdist_msi(Command):
|
||||
description: str
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
all_versions: Any
|
||||
all_versions: Incomplete
|
||||
other_version: str
|
||||
if sys.version_info >= (3, 9):
|
||||
def __init__(self, *args, **kw) -> None: ...
|
||||
bdist_dir: Any
|
||||
plat_name: Any
|
||||
bdist_dir: Incomplete
|
||||
plat_name: Incomplete
|
||||
keep_temp: int
|
||||
no_target_compile: int
|
||||
no_target_optimize: int
|
||||
target_version: Any
|
||||
dist_dir: Any
|
||||
skip_build: Any
|
||||
install_script: Any
|
||||
pre_install_script: Any
|
||||
versions: Any
|
||||
target_version: Incomplete
|
||||
dist_dir: Incomplete
|
||||
skip_build: Incomplete
|
||||
install_script: Incomplete
|
||||
pre_install_script: Incomplete
|
||||
versions: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
install_script_key: Any
|
||||
install_script_key: Incomplete
|
||||
def finalize_options(self) -> None: ...
|
||||
db: Any
|
||||
db: Incomplete
|
||||
def run(self) -> None: ...
|
||||
def add_files(self) -> None: ...
|
||||
def add_find_python(self) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -7,44 +8,44 @@ class bdist_rpm(Command):
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
negative_opt: ClassVar[dict[str, str]]
|
||||
bdist_base: Any
|
||||
rpm_base: Any
|
||||
dist_dir: Any
|
||||
python: Any
|
||||
fix_python: Any
|
||||
spec_only: Any
|
||||
binary_only: Any
|
||||
source_only: Any
|
||||
use_bzip2: Any
|
||||
distribution_name: Any
|
||||
group: Any
|
||||
release: Any
|
||||
serial: Any
|
||||
vendor: Any
|
||||
packager: Any
|
||||
doc_files: Any
|
||||
changelog: Any
|
||||
icon: Any
|
||||
prep_script: Any
|
||||
build_script: Any
|
||||
install_script: Any
|
||||
clean_script: Any
|
||||
verify_script: Any
|
||||
pre_install: Any
|
||||
post_install: Any
|
||||
pre_uninstall: Any
|
||||
post_uninstall: Any
|
||||
prep: Any
|
||||
provides: Any
|
||||
requires: Any
|
||||
conflicts: Any
|
||||
build_requires: Any
|
||||
obsoletes: Any
|
||||
bdist_base: Incomplete
|
||||
rpm_base: Incomplete
|
||||
dist_dir: Incomplete
|
||||
python: Incomplete
|
||||
fix_python: Incomplete
|
||||
spec_only: Incomplete
|
||||
binary_only: Incomplete
|
||||
source_only: Incomplete
|
||||
use_bzip2: Incomplete
|
||||
distribution_name: Incomplete
|
||||
group: Incomplete
|
||||
release: Incomplete
|
||||
serial: Incomplete
|
||||
vendor: Incomplete
|
||||
packager: Incomplete
|
||||
doc_files: Incomplete
|
||||
changelog: Incomplete
|
||||
icon: Incomplete
|
||||
prep_script: Incomplete
|
||||
build_script: Incomplete
|
||||
install_script: Incomplete
|
||||
clean_script: Incomplete
|
||||
verify_script: Incomplete
|
||||
pre_install: Incomplete
|
||||
post_install: Incomplete
|
||||
pre_uninstall: Incomplete
|
||||
post_uninstall: Incomplete
|
||||
prep: Incomplete
|
||||
provides: Incomplete
|
||||
requires: Incomplete
|
||||
conflicts: Incomplete
|
||||
build_requires: Incomplete
|
||||
obsoletes: Incomplete
|
||||
keep_temp: int
|
||||
use_rpm_opt_flags: int
|
||||
rpm3_mode: int
|
||||
no_autoreq: int
|
||||
force_arch: Any
|
||||
force_arch: Incomplete
|
||||
quiet: int
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from _typeshed import Unused
|
||||
from _typeshed import Incomplete, Unused
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
|
||||
@@ -12,17 +12,17 @@ class build(Command):
|
||||
boolean_options: ClassVar[list[str]]
|
||||
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]]
|
||||
build_base: str
|
||||
build_purelib: Any
|
||||
build_platlib: Any
|
||||
build_lib: Any
|
||||
build_temp: Any
|
||||
build_scripts: Any
|
||||
compiler: Any
|
||||
plat_name: Any
|
||||
debug: Any
|
||||
build_purelib: Incomplete
|
||||
build_platlib: Incomplete
|
||||
build_lib: Incomplete
|
||||
build_temp: Incomplete
|
||||
build_scripts: Incomplete
|
||||
compiler: Incomplete
|
||||
plat_name: Incomplete
|
||||
debug: Incomplete
|
||||
force: int
|
||||
executable: Any
|
||||
parallel: Any
|
||||
executable: Incomplete
|
||||
parallel: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from _typeshed import Unused
|
||||
from _typeshed import Incomplete, Unused
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -11,15 +11,15 @@ class build_clib(Command):
|
||||
user_options: ClassVar[list[tuple[str, str, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]]
|
||||
build_clib: Any
|
||||
build_temp: Any
|
||||
libraries: Any
|
||||
include_dirs: Any
|
||||
define: Any
|
||||
undef: Any
|
||||
debug: Any
|
||||
build_clib: Incomplete
|
||||
build_temp: Incomplete
|
||||
libraries: Incomplete
|
||||
include_dirs: Incomplete
|
||||
define: Incomplete
|
||||
undef: Incomplete
|
||||
debug: Incomplete
|
||||
force: int
|
||||
compiler: Any
|
||||
compiler: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
from _typeshed import Unused
|
||||
from _typeshed import Incomplete, Unused
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
extension_name_re: Any
|
||||
extension_name_re: Incomplete
|
||||
|
||||
def show_compilers() -> None: ...
|
||||
|
||||
class build_ext(Command):
|
||||
description: str
|
||||
sep_by: Any
|
||||
sep_by: Incomplete
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
help_options: ClassVar[list[tuple[str, str | None, str, Callable[[], Unused]]]]
|
||||
extensions: Any
|
||||
build_lib: Any
|
||||
plat_name: Any
|
||||
build_temp: Any
|
||||
extensions: Incomplete
|
||||
build_lib: Incomplete
|
||||
plat_name: Incomplete
|
||||
build_temp: Incomplete
|
||||
inplace: int
|
||||
package: Any
|
||||
include_dirs: Any
|
||||
define: Any
|
||||
undef: Any
|
||||
libraries: Any
|
||||
library_dirs: Any
|
||||
rpath: Any
|
||||
link_objects: Any
|
||||
debug: Any
|
||||
force: Any
|
||||
compiler: Any
|
||||
swig: Any
|
||||
swig_cpp: Any
|
||||
swig_opts: Any
|
||||
user: Any
|
||||
parallel: Any
|
||||
package: Incomplete
|
||||
include_dirs: Incomplete
|
||||
define: Incomplete
|
||||
undef: Incomplete
|
||||
libraries: Incomplete
|
||||
library_dirs: Incomplete
|
||||
rpath: Incomplete
|
||||
link_objects: Incomplete
|
||||
debug: Incomplete
|
||||
force: Incomplete
|
||||
compiler: Incomplete
|
||||
swig: Incomplete
|
||||
swig_cpp: Incomplete
|
||||
swig_opts: Incomplete
|
||||
user: Incomplete
|
||||
parallel: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, ClassVar, Literal
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar, Literal
|
||||
|
||||
from ..cmd import Command
|
||||
from ..util import Mixin2to3 as Mixin2to3
|
||||
@@ -8,17 +9,17 @@ class build_py(Command):
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
negative_opt: ClassVar[dict[str, str]]
|
||||
build_lib: Any
|
||||
py_modules: Any
|
||||
package: Any
|
||||
package_data: Any
|
||||
package_dir: Any
|
||||
build_lib: Incomplete
|
||||
py_modules: Incomplete
|
||||
package: Incomplete
|
||||
package_data: Incomplete
|
||||
package_dir: Incomplete
|
||||
compile: int
|
||||
optimize: int
|
||||
force: Any
|
||||
force: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
packages: Any
|
||||
data_files: Any
|
||||
packages: Incomplete
|
||||
data_files: Incomplete
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
def get_data_files(self): ...
|
||||
@@ -32,13 +33,13 @@ class build_py(Command):
|
||||
def find_all_modules(self): ...
|
||||
def get_source_files(self): ...
|
||||
def get_module_outfile(self, build_dir, package, module): ...
|
||||
def get_outputs(self, include_bytecode: bool | Literal[0, 1] = 1): ...
|
||||
def get_outputs(self, include_bytecode: bool | Literal[0, 1] = 1) -> list[str]: ...
|
||||
def build_module(self, module, module_file, package): ...
|
||||
def build_modules(self) -> None: ...
|
||||
def build_packages(self) -> None: ...
|
||||
def byte_compile(self, files) -> None: ...
|
||||
|
||||
class build_py_2to3(build_py, Mixin2to3):
|
||||
updated_files: Any
|
||||
updated_files: Incomplete
|
||||
def run(self) -> None: ...
|
||||
def build_module(self, module, module_file, package): ...
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
from ..util import Mixin2to3 as Mixin2to3
|
||||
|
||||
first_line_re: Any
|
||||
first_line_re: Incomplete
|
||||
|
||||
class build_scripts(Command):
|
||||
description: str
|
||||
user_options: ClassVar[list[tuple[str, str, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
build_dir: Any
|
||||
scripts: Any
|
||||
force: Any
|
||||
executable: Any
|
||||
outfiles: Any
|
||||
build_dir: Incomplete
|
||||
scripts: Incomplete
|
||||
force: Incomplete
|
||||
executable: Incomplete
|
||||
outfiles: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def get_source_files(self): ...
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from _typeshed import Incomplete
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
@@ -9,13 +10,13 @@ _Reporter: TypeAlias = Any # really docutils.utils.Reporter
|
||||
# Depends on a third-party stub. Since distutils is deprecated anyway,
|
||||
# it's easier to just suppress the "any subclassing" error.
|
||||
class SilentReporter(_Reporter):
|
||||
messages: Any
|
||||
messages: Incomplete
|
||||
def __init__(
|
||||
self,
|
||||
source,
|
||||
report_level,
|
||||
halt_level,
|
||||
stream: Any | None = ...,
|
||||
stream: Incomplete | None = ...,
|
||||
debug: bool | Literal[0, 1] = 0,
|
||||
encoding: str = ...,
|
||||
error_handler: str = ...,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -6,12 +7,12 @@ class clean(Command):
|
||||
description: str
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
build_base: Any
|
||||
build_lib: Any
|
||||
build_temp: Any
|
||||
build_scripts: Any
|
||||
bdist_base: Any
|
||||
all: Any
|
||||
build_base: Incomplete
|
||||
build_lib: Incomplete
|
||||
build_temp: Incomplete
|
||||
build_scripts: Incomplete
|
||||
bdist_base: Incomplete
|
||||
all: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from _typeshed import StrOrBytesPath
|
||||
from _typeshed import Incomplete, StrOrBytesPath
|
||||
from collections.abc import Sequence
|
||||
from re import Pattern
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
from typing import ClassVar, Final, Literal
|
||||
|
||||
from ..ccompiler import CCompiler
|
||||
from ..cmd import Command
|
||||
@@ -81,4 +81,4 @@ class config(Command):
|
||||
self, header: str, include_dirs: Sequence[str] | None = None, library_dirs: Sequence[str] | None = None, lang: str = "c"
|
||||
) -> bool: ...
|
||||
|
||||
def dump_file(filename: StrOrBytesPath, head: Any | None = None) -> None: ...
|
||||
def dump_file(filename: StrOrBytesPath, head: Incomplete | None = None) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from _typeshed import Incomplete
|
||||
from collections.abc import Callable
|
||||
from typing import Any, ClassVar, Final, Literal
|
||||
|
||||
@@ -18,33 +19,33 @@ class install(Command):
|
||||
boolean_options: ClassVar[list[str]]
|
||||
negative_opt: ClassVar[dict[str, str]]
|
||||
prefix: str | None
|
||||
exec_prefix: Any
|
||||
exec_prefix: Incomplete
|
||||
home: str | None
|
||||
user: bool
|
||||
install_base: Any
|
||||
install_platbase: Any
|
||||
install_base: Incomplete
|
||||
install_platbase: Incomplete
|
||||
root: str | None
|
||||
install_purelib: Any
|
||||
install_platlib: Any
|
||||
install_headers: Any
|
||||
install_purelib: Incomplete
|
||||
install_platlib: Incomplete
|
||||
install_headers: Incomplete
|
||||
install_lib: str | None
|
||||
install_scripts: Any
|
||||
install_data: Any
|
||||
install_userbase: Any
|
||||
install_usersite: Any
|
||||
compile: Any
|
||||
optimize: Any
|
||||
extra_path: Any
|
||||
install_scripts: Incomplete
|
||||
install_data: Incomplete
|
||||
install_userbase: Incomplete
|
||||
install_usersite: Incomplete
|
||||
compile: Incomplete
|
||||
optimize: Incomplete
|
||||
extra_path: Incomplete
|
||||
install_path_file: int
|
||||
force: int
|
||||
skip_build: int
|
||||
warn_dir: int
|
||||
build_base: Any
|
||||
build_lib: Any
|
||||
record: Any
|
||||
build_base: Incomplete
|
||||
build_lib: Incomplete
|
||||
record: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
config_vars: Any
|
||||
install_libbase: Any
|
||||
config_vars: Incomplete
|
||||
install_libbase: Incomplete
|
||||
def finalize_options(self) -> None: ...
|
||||
def dump_dirs(self, msg) -> None: ...
|
||||
def finalize_unix(self) -> None: ...
|
||||
@@ -53,8 +54,8 @@ class install(Command):
|
||||
def expand_basedirs(self) -> None: ...
|
||||
def expand_dirs(self) -> None: ...
|
||||
def convert_paths(self, *names) -> None: ...
|
||||
path_file: Any
|
||||
extra_dirs: Any
|
||||
path_file: Incomplete
|
||||
extra_dirs: Incomplete
|
||||
def handle_extra_path(self) -> None: ...
|
||||
def change_roots(self, *names) -> None: ...
|
||||
def create_home_path(self) -> None: ...
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
@@ -6,11 +7,11 @@ class install_data(Command):
|
||||
description: str
|
||||
user_options: ClassVar[list[tuple[str, str | None, str]]]
|
||||
boolean_options: ClassVar[list[str]]
|
||||
install_dir: Any
|
||||
outfiles: Any
|
||||
root: Any
|
||||
install_dir: Incomplete
|
||||
outfiles: Incomplete
|
||||
root: Incomplete
|
||||
force: int
|
||||
data_files: Any
|
||||
data_files: Incomplete
|
||||
warn_dir: int
|
||||
def initialize_options(self) -> None: ...
|
||||
def finalize_options(self) -> None: ...
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
from typing import Any, ClassVar
|
||||
from _typeshed import Incomplete
|
||||
from typing import ClassVar
|
||||
|
||||
from ..cmd import Command
|
||||
|
||||
class install_egg_info(Command):
|
||||
description: ClassVar[str]
|
||||
user_options: ClassVar[list[tuple[str, str, str]]]
|
||||
install_dir: Any
|
||||
install_dir: Incomplete
|
||||
def initialize_options(self) -> None: ...
|
||||
target: Any
|
||||
outputs: Any
|
||||
target: Incomplete
|
||||
outputs: Incomplete
|
||||
def finalize_options(self) -> None: ...
|
||||
def run(self) -> None: ...
|
||||
def get_outputs(self) -> list[str]: ...
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user