Compare commits
40 Commits
david/fix-
...
dcreager/h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb95e49f96 | ||
|
|
0817a367e1 | ||
|
|
34b463d0a1 | ||
|
|
b438631de2 | ||
|
|
fbc211f344 | ||
|
|
1810b7c7a3 | ||
|
|
39353da108 | ||
|
|
7b88440fc2 | ||
|
|
8426cc6915 | ||
|
|
c08a2d6d65 | ||
|
|
6ddf729864 | ||
|
|
eaba8bc61e | ||
|
|
b18d213869 | ||
|
|
847e5f0c68 | ||
|
|
41772466d5 | ||
|
|
180d9de472 | ||
|
|
adf58b6c19 | ||
|
|
b683da8cde | ||
|
|
6d1c549c4e | ||
|
|
c574dff6b0 | ||
|
|
8c12fcb927 | ||
|
|
a2eaf7ce26 | ||
|
|
c3626a6d74 | ||
|
|
8221450cbc | ||
|
|
93db8833ef | ||
|
|
51d5bc709d | ||
|
|
0198857224 | ||
|
|
35b568dd6b | ||
|
|
35a5fd767d | ||
|
|
ac1b68c56d | ||
|
|
ab261360e4 | ||
|
|
19600ecd51 | ||
|
|
319f5be78c | ||
|
|
6370aea644 | ||
|
|
9d8e35b165 | ||
|
|
5c75e91abe | ||
|
|
6e73b859ef | ||
|
|
11e5ecb91e | ||
|
|
be47fbe0f6 | ||
|
|
37effea8fd |
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -452,7 +452,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
||||
uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
|
||||
- name: "Install cargo-fuzz"
|
||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||
@@ -703,7 +703,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
|
||||
- uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -953,7 +953,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
@@ -988,23 +988,19 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
benchmarks-walltime:
|
||||
name: "benchmarks walltime (${{ matrix.benchmarks }})"
|
||||
runs-on: codspeed-macro
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
benchmarks:
|
||||
- "medium|multithreaded"
|
||||
- "small|large"
|
||||
env:
|
||||
TY_LOG: ruff_benchmark=debug
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -1026,7 +1022,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
|
||||
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
|
||||
env:
|
||||
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
|
||||
# appear to provide much useful insight for our walltime benchmarks right now
|
||||
@@ -1034,5 +1030,5 @@ jobs:
|
||||
CODSPEED_PERF_ENABLED: false
|
||||
with:
|
||||
mode: walltime
|
||||
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
148
Cargo.lock
generated
148
Cargo.lock
generated
@@ -50,9 +50,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -65,9 +65,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-lossy"
|
||||
@@ -214,6 +214,15 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.1"
|
||||
@@ -234,26 +243,6 @@ dependencies = [
|
||||
"virtue",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -327,9 +316,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.2.1"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
|
||||
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
@@ -361,15 +350,6 @@ dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
@@ -420,17 +400,6 @@ dependencies = [
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.48"
|
||||
@@ -517,17 +486,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f62ea8934802f8b374bf691eea524c3aa444d7014f604dd4182a3667b69510"
|
||||
checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen",
|
||||
"cc",
|
||||
"bincode 1.3.3",
|
||||
"colored 2.2.0",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix 0.30.1",
|
||||
"nix 0.29.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"statrs",
|
||||
@@ -536,22 +504,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87efbc015fc0ff1b2001cd87df01c442824de677e01a77230bf091534687abb"
|
||||
checksum = "78f6c1c6bed5fd84d319e8b0889da051daa361c79b7709c9394dfe1a882bba67"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
"colored 2.2.0",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae5713ace440123bb4f1f78dd068d46872cb8548bfe61f752e7b2ad2c06d7f00"
|
||||
checksum = "c989289ce6b1cbde72ed560496cb8fbf5aa14d5ef5666f168e7f87751038352e"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
@@ -574,22 +540,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95b4214b974f8f5206497153e89db90274e623f06b00bf4b9143eeb7735d975d"
|
||||
checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"codspeed",
|
||||
"codspeed-divan-compat-macros",
|
||||
"codspeed-divan-compat-walltime",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a53f34a16cb70ce4fd9ad57e1db016f0718e434f34179ca652006443b9a39967"
|
||||
checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools 0.14.0",
|
||||
@@ -601,9 +565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "4.0.4"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a5099050c8948dce488b8eaa2e68dc5cf571cb8f9fce99aaaecbdddb940bcd"
|
||||
checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -1563,7 +1527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1837,15 +1801,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.177"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.8.5"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d56bcd52d9b5e5f43e7fba20eb1f423ccb18c84cdf1cb506b8c1b95776b0b49"
|
||||
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"libcst_derive",
|
||||
@@ -1858,24 +1822,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.8.5"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fcf5a725c4db703660124fe0edb98285f1605d0b87b7ee8684b699764a4f01a"
|
||||
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.44"
|
||||
@@ -2017,9 +1971,9 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -2528,16 +2482,6 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.4.0"
|
||||
@@ -2569,9 +2513,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.13.7"
|
||||
version = "0.13.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d755483ad14b49e76713b52285235461a5b4f73f17612353e11a5de36a5fd2"
|
||||
checksum = "ec768e063102b426e8962989758115e8659485124de9207bc365fab524125d65"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
@@ -2769,9 +2713,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.3"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2781,9 +2725,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.11"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2820,7 +2764,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_fs",
|
||||
"bincode",
|
||||
"bincode 2.0.1",
|
||||
"bitflags 2.9.4",
|
||||
"cachedir",
|
||||
"clap",
|
||||
@@ -4499,7 +4443,6 @@ dependencies = [
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
"memchr",
|
||||
"path-slash",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
@@ -4517,6 +4460,7 @@ dependencies = [
|
||||
"thiserror 2.0.16",
|
||||
"toml",
|
||||
"tracing",
|
||||
"ty_ide",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
|
||||
@@ -71,8 +71,8 @@ clap = { version = "4.5.3", features = ["derive"] }
|
||||
clap_complete_command = { version = "0.6.0" }
|
||||
clearscreen = { version = "4.0.0" }
|
||||
csv = { version = "1.3.1" }
|
||||
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
|
||||
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
|
||||
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
|
||||
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
|
||||
colored = { version = "3.0.0" }
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
|
||||
@@ -599,7 +599,7 @@ impl<'a> ProjectBenchmark<'a> {
|
||||
self.project
|
||||
.check_paths()
|
||||
.iter()
|
||||
.map(|path| SystemPathBuf::from(*path))
|
||||
.map(|path| path.to_path_buf())
|
||||
.collect(),
|
||||
);
|
||||
|
||||
@@ -645,8 +645,8 @@ fn hydra(criterion: &mut Criterion) {
|
||||
name: "hydra-zen",
|
||||
repository: "https://github.com/mit-ll-responsible-ai/hydra-zen",
|
||||
commit: "dd2b50a9614c6f8c46c5866f283c8f7e7a960aa8",
|
||||
paths: &["src"],
|
||||
dependencies: &["pydantic", "beartype", "hydra-core"],
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec!["pydantic", "beartype", "hydra-core"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -662,8 +662,8 @@ fn attrs(criterion: &mut Criterion) {
|
||||
name: "attrs",
|
||||
repository: "https://github.com/python-attrs/attrs",
|
||||
commit: "a6ae894aad9bc09edc7cdad8c416898784ceec9b",
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -679,8 +679,8 @@ fn anyio(criterion: &mut Criterion) {
|
||||
name: "anyio",
|
||||
repository: "https://github.com/agronholm/anyio",
|
||||
commit: "561d81270a12f7c6bbafb5bc5fad99a2a13f96be",
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
@@ -696,8 +696,8 @@ fn datetype(criterion: &mut Criterion) {
|
||||
name: "DateType",
|
||||
repository: "https://github.com/glyph/DateType",
|
||||
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
|
||||
paths: &["src"],
|
||||
dependencies: &[],
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
max_dep_date: "2025-07-04",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use divan::{Bencher, bench};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use divan::{Bencher, bench};
|
||||
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
@@ -12,39 +13,29 @@ use ty_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||
|
||||
struct Benchmark<'a> {
|
||||
project: RealWorldProject<'a>,
|
||||
installed_project: std::sync::OnceLock<InstalledProject<'a>>,
|
||||
project: InstalledProject<'a>,
|
||||
max_diagnostics: usize,
|
||||
}
|
||||
|
||||
impl<'a> Benchmark<'a> {
|
||||
const fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
|
||||
fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
|
||||
let setup_project = project.setup().expect("Failed to setup project");
|
||||
|
||||
Self {
|
||||
project,
|
||||
installed_project: std::sync::OnceLock::new(),
|
||||
project: setup_project,
|
||||
max_diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
fn installed_project(&self) -> &InstalledProject<'a> {
|
||||
self.installed_project.get_or_init(|| {
|
||||
self.project
|
||||
.clone()
|
||||
.setup()
|
||||
.expect("Failed to setup project")
|
||||
})
|
||||
}
|
||||
|
||||
fn setup_iteration(&self) -> ProjectDatabase {
|
||||
let installed_project = self.installed_project();
|
||||
let root = SystemPathBuf::from_path_buf(installed_project.path.clone()).unwrap();
|
||||
let root = SystemPathBuf::from_path_buf(self.project.path.clone()).unwrap();
|
||||
let system = OsSystem::new(&root);
|
||||
|
||||
let mut metadata = ProjectMetadata::discover(&root, &system).unwrap();
|
||||
|
||||
metadata.apply_options(Options {
|
||||
environment: Some(EnvironmentOptions {
|
||||
python_version: Some(RangedValue::cli(installed_project.config.python_version)),
|
||||
python_version: Some(RangedValue::cli(self.project.config.python_version)),
|
||||
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
|
||||
..EnvironmentOptions::default()
|
||||
}),
|
||||
@@ -55,7 +46,7 @@ impl<'a> Benchmark<'a> {
|
||||
|
||||
db.project().set_included_paths(
|
||||
&mut db,
|
||||
installed_project
|
||||
self.project
|
||||
.check_paths()
|
||||
.iter()
|
||||
.map(|path| SystemPath::absolute(path, &root))
|
||||
@@ -67,7 +58,7 @@ impl<'a> Benchmark<'a> {
|
||||
|
||||
impl Display for Benchmark<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.project.name.fmt(f)
|
||||
f.write_str(self.project.config.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,150 +75,166 @@ fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
|
||||
);
|
||||
}
|
||||
|
||||
static ALTAIR: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "altair",
|
||||
repository: "https://github.com/vega/altair",
|
||||
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
|
||||
paths: &["altair"],
|
||||
dependencies: &[
|
||||
"jinja2",
|
||||
"narwhals",
|
||||
"numpy",
|
||||
"packaging",
|
||||
"pandas-stubs",
|
||||
"pyarrow-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
"types-jsonschema",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
1000,
|
||||
);
|
||||
static ALTAIR: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "altair",
|
||||
repository: "https://github.com/vega/altair",
|
||||
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
|
||||
paths: vec![SystemPath::new("altair")],
|
||||
dependencies: vec![
|
||||
"jinja2",
|
||||
"narwhals",
|
||||
"numpy",
|
||||
"packaging",
|
||||
"pandas-stubs",
|
||||
"pyarrow-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
"types-jsonschema",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
1000,
|
||||
)
|
||||
});
|
||||
|
||||
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "colour-science",
|
||||
repository: "https://github.com/colour-science/colour",
|
||||
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
|
||||
paths: &["colour"],
|
||||
dependencies: &[
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
600,
|
||||
);
|
||||
static COLOUR_SCIENCE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "colour-science",
|
||||
repository: "https://github.com/colour-science/colour",
|
||||
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
|
||||
paths: vec![SystemPath::new("colour")],
|
||||
dependencies: vec![
|
||||
"matplotlib",
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pytest",
|
||||
"scipy-stubs",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY310,
|
||||
},
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
static FREQTRADE: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "freqtrade",
|
||||
repository: "https://github.com/freqtrade/freqtrade",
|
||||
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
|
||||
paths: &["freqtrade"],
|
||||
dependencies: &[
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pydantic",
|
||||
"sqlalchemy",
|
||||
"types-cachetools",
|
||||
"types-filelock",
|
||||
"types-python-dateutil",
|
||||
"types-requests",
|
||||
"types-tabulate",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
400,
|
||||
);
|
||||
static FREQTRADE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "freqtrade",
|
||||
repository: "https://github.com/freqtrade/freqtrade",
|
||||
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
|
||||
paths: vec![SystemPath::new("freqtrade")],
|
||||
dependencies: vec![
|
||||
"numpy",
|
||||
"pandas-stubs",
|
||||
"pydantic",
|
||||
"sqlalchemy",
|
||||
"types-cachetools",
|
||||
"types-filelock",
|
||||
"types-python-dateutil",
|
||||
"types-requests",
|
||||
"types-tabulate",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
400,
|
||||
)
|
||||
});
|
||||
|
||||
static PANDAS: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pandas",
|
||||
repository: "https://github.com/pandas-dev/pandas",
|
||||
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
|
||||
paths: &["pandas"],
|
||||
dependencies: &[
|
||||
"numpy",
|
||||
"types-python-dateutil",
|
||||
"types-pytz",
|
||||
"types-PyMySQL",
|
||||
"types-setuptools",
|
||||
"pytest",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
3000,
|
||||
);
|
||||
static PANDAS: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pandas",
|
||||
repository: "https://github.com/pandas-dev/pandas",
|
||||
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
|
||||
paths: vec![SystemPath::new("pandas")],
|
||||
dependencies: vec![
|
||||
"numpy",
|
||||
"types-python-dateutil",
|
||||
"types-pytz",
|
||||
"types-PyMySQL",
|
||||
"types-setuptools",
|
||||
"pytest",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
3000,
|
||||
)
|
||||
});
|
||||
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pydantic",
|
||||
repository: "https://github.com/pydantic/pydantic",
|
||||
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
|
||||
paths: &["pydantic"],
|
||||
dependencies: &[
|
||||
"annotated-types",
|
||||
"pydantic-core",
|
||||
"typing-extensions",
|
||||
"typing-inspection",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
1000,
|
||||
);
|
||||
static PYDANTIC: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "pydantic",
|
||||
repository: "https://github.com/pydantic/pydantic",
|
||||
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
|
||||
paths: vec![SystemPath::new("pydantic")],
|
||||
dependencies: vec![
|
||||
"annotated-types",
|
||||
"pydantic-core",
|
||||
"typing-extensions",
|
||||
"typing-inspection",
|
||||
],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
1000,
|
||||
)
|
||||
});
|
||||
|
||||
static SYMPY: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "sympy",
|
||||
repository: "https://github.com/sympy/sympy",
|
||||
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
|
||||
paths: &["sympy"],
|
||||
dependencies: &["mpmath"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13000,
|
||||
);
|
||||
static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "sympy",
|
||||
repository: "https://github.com/sympy/sympy",
|
||||
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
|
||||
paths: vec![SystemPath::new("sympy")],
|
||||
dependencies: vec!["mpmath"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13000,
|
||||
)
|
||||
});
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "tanjun",
|
||||
repository: "https://github.com/FasterSpeeding/Tanjun",
|
||||
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
|
||||
paths: &["tanjun"],
|
||||
dependencies: &["hikari", "alluka"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
100,
|
||||
);
|
||||
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "tanjun",
|
||||
repository: "https://github.com/FasterSpeeding/Tanjun",
|
||||
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
|
||||
paths: vec![SystemPath::new("tanjun")],
|
||||
dependencies: vec!["hikari", "alluka"],
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
100,
|
||||
)
|
||||
});
|
||||
|
||||
static STATIC_FRAME: Benchmark = Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "static-frame",
|
||||
repository: "https://github.com/static-frame/static-frame",
|
||||
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
|
||||
paths: &["static_frame"],
|
||||
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
|
||||
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
|
||||
// (seems to be built from source on the Codspeed CI runners for some reason).
|
||||
dependencies: &["numpy"],
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
630,
|
||||
);
|
||||
static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
|
||||
Benchmark::new(
|
||||
RealWorldProject {
|
||||
name: "static-frame",
|
||||
repository: "https://github.com/static-frame/static-frame",
|
||||
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
|
||||
paths: vec![SystemPath::new("static_frame")],
|
||||
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
|
||||
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
|
||||
// (seems to be built from source on the Codspeed CI runners for some reason).
|
||||
dependencies: vec!["numpy"],
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
#[track_caller]
|
||||
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
@@ -238,22 +245,22 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
});
|
||||
}
|
||||
|
||||
#[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
|
||||
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
|
||||
fn small(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS, &*STATIC_FRAME], sample_size=1, sample_count=3)]
|
||||
fn medium(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
|
||||
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
|
||||
fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
|
||||
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ pub struct RealWorldProject<'a> {
|
||||
/// Specific commit hash to checkout
|
||||
pub commit: &'a str,
|
||||
/// List of paths within the project to check (`ty check <paths>`)
|
||||
pub paths: &'a [&'a str],
|
||||
pub paths: Vec<&'a SystemPath>,
|
||||
/// Dependencies to install via uv
|
||||
pub dependencies: &'a [&'a str],
|
||||
pub dependencies: Vec<&'a str>,
|
||||
/// Limit candidate packages to those that were uploaded prior to a given point in time (ISO 8601 format).
|
||||
/// Maps to uv's `exclude-newer`.
|
||||
pub max_dep_date: &'a str,
|
||||
@@ -125,9 +125,9 @@ impl<'a> InstalledProject<'a> {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get the benchmark paths
|
||||
pub fn check_paths(&self) -> &[&str] {
|
||||
self.config.paths
|
||||
/// Get the benchmark paths as `SystemPathBuf`
|
||||
pub fn check_paths(&self) -> &[&SystemPath] {
|
||||
&self.config.paths
|
||||
}
|
||||
|
||||
/// Get the virtual environment path
|
||||
@@ -297,7 +297,7 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
|
||||
"--exclude-newer",
|
||||
checkout.project().max_dep_date,
|
||||
])
|
||||
.args(checkout.project().dependencies);
|
||||
.args(&checkout.project().dependencies);
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
|
||||
@@ -227,32 +227,3 @@ async def read_thing(query: str):
|
||||
@app.get("/things/{ thing_id : str }")
|
||||
async def read_thing(query: str):
|
||||
return {"query": query}
|
||||
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20680
|
||||
# These should NOT trigger FAST003 because FastAPI doesn't recognize them as path parameters
|
||||
|
||||
# Non-ASCII characters in parameter name
|
||||
@app.get("/f1/{用户身份}")
|
||||
async def f1():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name
|
||||
@app.get("/f2/{x: str}")
|
||||
async def f2():
|
||||
return locals()
|
||||
|
||||
# Non-ASCII converter
|
||||
@app.get("/f3/{complex_number:ℂ}")
|
||||
async def f3():
|
||||
return locals()
|
||||
|
||||
# Mixed non-ASCII characters
|
||||
@app.get("/f4/{用户_id}")
|
||||
async def f4():
|
||||
return locals()
|
||||
|
||||
# Space in parameter name with converter
|
||||
@app.get("/f5/{param: int}")
|
||||
async def f5():
|
||||
return locals()
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import logging
|
||||
|
||||
variablename = "value"
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.info(f"a" f"b {variablename}")
|
||||
log.info("a " f"b {variablename}")
|
||||
log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::iter::Peekable;
|
||||
use std::ops::Range;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use regex::{CaptureMatches, Regex};
|
||||
use std::str::CharIndices;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault};
|
||||
use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::Fix;
|
||||
@@ -165,6 +165,11 @@ pub(crate) fn fastapi_unused_path_parameter(
|
||||
|
||||
// Check if any of the path parameters are not in the function signature.
|
||||
for (path_param, range) in path_params {
|
||||
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
|
||||
if !is_identifier(path_param) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the path parameter is already in the function or the dependency signature,
|
||||
// we don't need to do anything.
|
||||
if named_args.contains(&path_param) {
|
||||
@@ -456,19 +461,15 @@ fn parameter_alias<'a>(parameter: &'a Parameter, semantic: &SemanticModel) -> Op
|
||||
/// the parameter name. For example, `/{x}` is a valid parameter, but `/{ x }` is treated literally.
|
||||
#[derive(Debug)]
|
||||
struct PathParamIterator<'a> {
|
||||
inner: CaptureMatches<'a, 'a>,
|
||||
input: &'a str,
|
||||
chars: Peekable<CharIndices<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> PathParamIterator<'a> {
|
||||
fn new(input: &'a str) -> Self {
|
||||
/// Matches the Starlette pattern for path parameters with optional converters from
|
||||
/// <https://github.com/Kludex/starlette/blob/e18637c68e36d112b1983bc0c8b663681e6a4c50/starlette/routing.py#L121>
|
||||
static FASTAPI_PATH_PARAM_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::[a-zA-Z_][a-zA-Z0-9_]*)?\}").unwrap()
|
||||
});
|
||||
|
||||
Self {
|
||||
inner: FASTAPI_PATH_PARAM_REGEX.captures_iter(input),
|
||||
PathParamIterator {
|
||||
input,
|
||||
chars: input.char_indices().peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -477,10 +478,19 @@ impl<'a> Iterator for PathParamIterator<'a> {
|
||||
type Item = (&'a str, Range<usize>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner
|
||||
.next()
|
||||
// Extract the first capture group (the path parameter), but return the range of the
|
||||
// whole match (everything in braces and including the braces themselves).
|
||||
.and_then(|capture| Some((capture.get(1)?.as_str(), capture.get(0)?.range())))
|
||||
while let Some((start, c)) = self.chars.next() {
|
||||
if c == '{' {
|
||||
if let Some((end, _)) = self.chars.by_ref().find(|&(_, ch)| ch == '}') {
|
||||
let param_content = &self.input[start + 1..end];
|
||||
// We ignore text after a colon, since those are path converters
|
||||
// See also: https://fastapi.tiangolo.com/tutorial/path-params/?h=path#path-convertor
|
||||
let param_name_end = param_content.find(':').unwrap_or(param_content.len());
|
||||
let param_name = ¶m_content[..param_name_end];
|
||||
|
||||
return Some((param_name, start..end + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1091,12 +1091,9 @@ fn suspicious_function(
|
||||
] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherModeUsage, range),
|
||||
|
||||
// Mktemp
|
||||
["tempfile", "mktemp"] => checker
|
||||
.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
|
||||
.map(|mut diagnostic| {
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic
|
||||
}),
|
||||
["tempfile", "mktemp"] => {
|
||||
checker.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
|
||||
}
|
||||
|
||||
// Eval
|
||||
["" | "builtins", "eval"] => {
|
||||
|
||||
@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for usage of `datetime.date.fromtimestamp()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python date objects are naive, that is, not timezone-aware. While an aware
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.date.fromtimestamp(ts)` returns a naive date object.
|
||||
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...).date()` to
|
||||
/// create a timezone-aware datetime object and retrieve its date component.
|
||||
/// `datetime.date.fromtimestamp(ts)` returns a naive datetime object.
|
||||
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` to create a
|
||||
/// timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -32,14 +32,14 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc).date()
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
|
||||
/// ```
|
||||
///
|
||||
/// Or, for Python 3.11 and later:
|
||||
/// ```python
|
||||
/// import datetime
|
||||
///
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC).date()
|
||||
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -11,15 +11,14 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for usage of `datetime.date.today()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python date objects are naive, that is, not timezone-aware. While an aware
|
||||
/// Python datetime objects can be naive or timezone-aware. While an aware
|
||||
/// object represents a specific moment in time, a naive object does not
|
||||
/// contain enough information to unambiguously locate itself relative to other
|
||||
/// datetime objects. Since this can lead to errors, it is recommended to
|
||||
/// always use timezone-aware objects.
|
||||
///
|
||||
/// `datetime.date.today` returns a naive date object without taking timezones
|
||||
/// into account. Instead, use `datetime.datetime.now(tz=...).date()` to
|
||||
/// create a timezone-aware object and retrieve its date component.
|
||||
/// `datetime.date.today` returns a naive datetime object. Instead, use
|
||||
/// `datetime.datetime.now(tz=...).date()` to create a timezone-aware object.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -42,9 +42,6 @@ use crate::rules::flake8_datetimez::helpers;
|
||||
///
|
||||
/// datetime.datetime.now(tz=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CallDatetimeToday;
|
||||
|
||||
|
||||
@@ -41,9 +41,6 @@ use crate::rules::flake8_datetimez::helpers::{self, DatetimeModuleAntipattern};
|
||||
///
|
||||
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct CallDatetimeWithoutTzinfo(DatetimeModuleAntipattern);
|
||||
|
||||
|
||||
@@ -38,9 +38,6 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// datetime.datetime.max.replace(tzinfo=datetime.UTC)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DatetimeMinMax {
|
||||
min_max: MinMax,
|
||||
|
||||
@@ -23,7 +23,6 @@ mod tests {
|
||||
#[test_case(Path::new("G003.py"))]
|
||||
#[test_case(Path::new("G004.py"))]
|
||||
#[test_case(Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Path::new("G004_implicit_concat.py"))]
|
||||
#[test_case(Path::new("G010.py"))]
|
||||
#[test_case(Path::new("G101_1.py"))]
|
||||
#[test_case(Path::new("G101_2.py"))]
|
||||
@@ -53,7 +52,6 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_implicit_concat.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -42,52 +42,38 @@ fn logging_f_string(
|
||||
// Default to double quotes if we can't determine it.
|
||||
let quote_str = f_string
|
||||
.value
|
||||
.iter()
|
||||
.map(|part| match part {
|
||||
ast::FStringPart::Literal(literal) => literal.flags.quote_str(),
|
||||
ast::FStringPart::FString(f) => f.flags.quote_str(),
|
||||
})
|
||||
.f_strings()
|
||||
.next()
|
||||
.map(|f| f.flags.quote_str())
|
||||
.unwrap_or("\"");
|
||||
|
||||
for part in &f_string.value {
|
||||
match part {
|
||||
ast::FStringPart::Literal(literal) => {
|
||||
let literal_text = literal.as_str();
|
||||
if literal_text.contains('%') {
|
||||
return;
|
||||
for f in f_string.value.f_strings() {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
format_string.push_str(literal_text);
|
||||
}
|
||||
ast::FStringPart::FString(f) => {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 50
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
assertion_line: 71
|
||||
---
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:6:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
3 | variablename = "value"
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
- log.info(f"a" f"b {variablename}")
|
||||
6 + log.info("ab %s", variablename)
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:7:10
|
||||
|
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
4 |
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
- log.info("a " f"b {variablename}")
|
||||
7 + log.info("a b %s", variablename)
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004_implicit_concat.py:8:10
|
||||
|
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
5 | log = logging.getLogger(__name__)
|
||||
6 | log.info(f"a" f"b {variablename}")
|
||||
7 | log.info("a " f"b {variablename}")
|
||||
- log.info("prefix " f"middle {variablename}" f" suffix")
|
||||
8 + log.info("prefix middle %s suffix", variablename)
|
||||
@@ -74,8 +74,7 @@ pub(crate) fn bytestring_attribute(checker: &Checker, attribute: &Expr) {
|
||||
["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc,
|
||||
_ => return,
|
||||
};
|
||||
let mut diagnostic = checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
|
||||
}
|
||||
|
||||
/// PYI057
|
||||
@@ -95,9 +94,7 @@ pub(crate) fn bytestring_import(checker: &Checker, import_from: &ast::StmtImport
|
||||
|
||||
for name in names {
|
||||
if name.name.as_str() == "ByteString" {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -898,9 +898,7 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato
|
||||
/// PT020
|
||||
fn check_fixture_decorator_name(checker: &Checker, decorator: &Decorator) {
|
||||
if is_pytest_yield_fixture(decorator, checker.semantic()) {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ enum Argumentable {
|
||||
|
||||
impl Argumentable {
|
||||
fn check_for(self, checker: &Checker, name: String, range: TextRange) {
|
||||
let mut diagnostic = match self {
|
||||
match self {
|
||||
Self::Function => checker.report_diagnostic(UnusedFunctionArgument { name }, range),
|
||||
Self::Method => checker.report_diagnostic(UnusedMethodArgument { name }, range),
|
||||
Self::ClassMethod => {
|
||||
@@ -234,7 +234,6 @@ impl Argumentable {
|
||||
}
|
||||
Self::Lambda => checker.report_diagnostic(UnusedLambdaArgument { name }, range),
|
||||
};
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
}
|
||||
|
||||
const fn rule_code(self) -> Rule {
|
||||
|
||||
@@ -80,7 +80,6 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) {
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import_from("numpy", replacement),
|
||||
|
||||
@@ -80,7 +80,6 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) {
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
|
||||
let type_name = match type_name {
|
||||
"unicode" => "str",
|
||||
_ => type_name,
|
||||
|
||||
@@ -15,16 +15,14 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::HasDefinition;
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
call_signature_details, definitions_for_keyword_argument,
|
||||
};
|
||||
use ty_python_semantic::types::definitions_for_keyword_argument;
|
||||
use ty_python_semantic::types::{Type, call_signature_details};
|
||||
use ty_python_semantic::{
|
||||
HasType, SemanticModel, definitions_for_imported_symbol, definitions_for_name,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum GotoTarget<'a> {
|
||||
pub enum GotoTarget<'a> {
|
||||
Expression(ast::ExprRef<'a>),
|
||||
FunctionDef(&'a ast::StmtFunctionDef),
|
||||
ClassDef(&'a ast::StmtClassDef),
|
||||
@@ -271,7 +269,7 @@ impl<'db> DefinitionsOrTargets<'db> {
|
||||
}
|
||||
|
||||
impl GotoTarget<'_> {
|
||||
pub(crate) fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
|
||||
pub fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Option<Type<'db>> {
|
||||
let ty = match self {
|
||||
GotoTarget::Expression(expression) => expression.inferred_type(model),
|
||||
GotoTarget::FunctionDef(function) => function.inferred_type(model),
|
||||
@@ -822,10 +820,7 @@ fn definitions_to_navigation_targets<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn find_goto_target(
|
||||
parsed: &ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<GotoTarget<'_>> {
|
||||
pub fn find_goto_target(parsed: &ParsedModuleRef, offset: TextSize) -> Option<GotoTarget<'_>> {
|
||||
let token = parsed
|
||||
.tokens()
|
||||
.at_offset(offset)
|
||||
|
||||
@@ -308,8 +308,26 @@ mod tests {
|
||||
"#,
|
||||
);
|
||||
|
||||
// TODO: This should jump to the definition of `Alias` above.
|
||||
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing_extensions import TypeAliasType
|
||||
3 |
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | Alias
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:1
|
||||
|
|
||||
4 | Alias = TypeAliasType("Alias", tuple[int, int])
|
||||
5 |
|
||||
6 | Alias
|
||||
| ^^^^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,8 +6,7 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::inlay_hint_function_argument_details;
|
||||
use ty_python_semantic::types::{Type, inlay_hint_function_argument_details};
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -30,7 +30,7 @@ pub use all_symbols::{AllSymbolInfo, all_symbols};
|
||||
pub use completion::{Completion, CompletionKind, CompletionSettings, completion};
|
||||
pub use doc_highlights::document_highlights;
|
||||
pub use document_symbols::document_symbols;
|
||||
pub use goto::{goto_declaration, goto_definition, goto_type_definition};
|
||||
pub use goto::{find_goto_target, goto_declaration, goto_definition, goto_type_definition};
|
||||
pub use goto_references::goto_references;
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::{InlayHintKind, InlayHintLabel, InlayHintSettings, inlay_hints};
|
||||
|
||||
@@ -13,8 +13,9 @@ use ruff_python_ast::{
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
use std::ops::Deref;
|
||||
use ty_python_semantic::{
|
||||
HasType, SemanticModel, semantic_index::definition::DefinitionKind, types::Type,
|
||||
types::ide_support::definition_kind_for_name,
|
||||
HasType, SemanticModel,
|
||||
semantic_index::definition::DefinitionKind,
|
||||
types::{Type, definition_kind_for_name},
|
||||
};
|
||||
|
||||
// This module walks the AST and collects a set of "semantic tokens" for a file
|
||||
|
||||
@@ -17,7 +17,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
use ty_python_semantic::types::{
|
||||
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
|
||||
};
|
||||
|
||||
|
||||
@@ -130,9 +130,13 @@ type IntList = list[int]
|
||||
m: IntList = [1, 2, 3]
|
||||
reveal_type(m) # revealed: list[int]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
# TODO: this should type-check and avoid literal promotion
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
|
||||
o: list[typing.LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(o) # revealed: list[LiteralString]
|
||||
|
||||
@@ -146,75 +150,7 @@ r: dict[int | str, int | str] = {1: 1, 2: 2, 3: 3}
|
||||
reveal_type(r) # revealed: dict[int | str, int | str]
|
||||
```
|
||||
|
||||
## Optional collection literal annotations are understood
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
a: list[int] | None = [1, 2, 3]
|
||||
reveal_type(a) # revealed: list[int]
|
||||
|
||||
b: list[int | str] | None = [1, 2, 3]
|
||||
reveal_type(b) # revealed: list[int | str]
|
||||
|
||||
c: typing.List[int] | None = [1, 2, 3]
|
||||
reveal_type(c) # revealed: list[int]
|
||||
|
||||
d: list[typing.Any] | None = []
|
||||
reveal_type(d) # revealed: list[Any]
|
||||
|
||||
e: set[int] | None = {1, 2, 3}
|
||||
reveal_type(e) # revealed: set[int]
|
||||
|
||||
f: set[int | str] | None = {1, 2, 3}
|
||||
reveal_type(f) # revealed: set[int | str]
|
||||
|
||||
g: typing.Set[int] | None = {1, 2, 3}
|
||||
reveal_type(g) # revealed: set[int]
|
||||
|
||||
h: list[list[int]] | None = [[], [42]]
|
||||
reveal_type(h) # revealed: list[list[int]]
|
||||
|
||||
i: list[typing.Any] | None = [1, 2, "3", ([4],)]
|
||||
reveal_type(i) # revealed: list[Any | int | str | tuple[list[Unknown | int]]]
|
||||
|
||||
j: list[tuple[str | int, ...]] | None = [(1, 2), ("foo", "bar"), ()]
|
||||
reveal_type(j) # revealed: list[tuple[str | int, ...]]
|
||||
|
||||
k: list[tuple[list[int], ...]] | None = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
|
||||
reveal_type(k) # revealed: list[tuple[list[int], ...]]
|
||||
|
||||
l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] | None = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
|
||||
# TODO: this should be `tuple[list[int], list[Any | int], list[Any | int], list[str]]`
|
||||
reveal_type(l) # revealed: tuple[list[Unknown | int], list[Unknown | int], list[Unknown | int], list[Unknown | str]]
|
||||
|
||||
type IntList = list[int]
|
||||
|
||||
m: IntList | None = [1, 2, 3]
|
||||
reveal_type(m) # revealed: list[int]
|
||||
|
||||
n: list[typing.Literal[1, 2, 3]] | None = [1, 2, 3]
|
||||
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
o: list[typing.LiteralString] | None = ["a", "b", "c"]
|
||||
reveal_type(o) # revealed: list[LiteralString]
|
||||
|
||||
p: dict[int, int] | None = {}
|
||||
reveal_type(p) # revealed: dict[int, int]
|
||||
|
||||
q: dict[int | str, int] | None = {1: 1, 2: 2, 3: 3}
|
||||
reveal_type(q) # revealed: dict[int | str, int]
|
||||
|
||||
r: dict[int | str, int | str] | None = {1: 1, 2: 2, 3: 3}
|
||||
reveal_type(r) # revealed: dict[int | str, int | str]
|
||||
```
|
||||
|
||||
## Incorrect collection literal assignments are complained about
|
||||
## Incorrect collection literal assignments are complained aobut
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
|
||||
@@ -224,81 +160,6 @@ a: list[str] = [1, 2, 3]
|
||||
b: set[int] = {1, 2, "3"}
|
||||
```
|
||||
|
||||
## Literal annnotations are respected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
from typing_extensions import Literal, LiteralString
|
||||
|
||||
a: list[Literal[1]] = [1]
|
||||
reveal_type(a) # revealed: list[Literal[1]]
|
||||
|
||||
b: list[Literal[True]] = [True]
|
||||
reveal_type(b) # revealed: list[Literal[True]]
|
||||
|
||||
c: list[Literal["a"]] = ["a"]
|
||||
reveal_type(c) # revealed: list[Literal["a"]]
|
||||
|
||||
d: list[LiteralString] = ["a", "b", "c"]
|
||||
reveal_type(d) # revealed: list[LiteralString]
|
||||
|
||||
e: list[list[Literal[1]]] = [[1]]
|
||||
reveal_type(e) # revealed: list[list[Literal[1]]]
|
||||
|
||||
class Color(Enum):
|
||||
RED = "red"
|
||||
|
||||
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
|
||||
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
|
||||
|
||||
class X[T]:
|
||||
def __init__(self, value: T): ...
|
||||
|
||||
g: X[Literal[1]] = X(1)
|
||||
reveal_type(g) # revealed: X[Literal[1]]
|
||||
|
||||
h: X[int] = X(1)
|
||||
reveal_type(h) # revealed: X[int]
|
||||
|
||||
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
|
||||
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
|
||||
|
||||
j: list[Literal[1, 2, 3]] = [1, 2, 3]
|
||||
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
|
||||
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
|
||||
|
||||
type Y[T] = list[T]
|
||||
|
||||
l: Y[Y[Literal[1]]] = [[1]]
|
||||
reveal_type(l) # revealed: list[list[Literal[1]]]
|
||||
|
||||
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
|
||||
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
|
||||
|
||||
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
|
||||
reveal_type(n) # revealed: list[tuple[int, str, int]]
|
||||
|
||||
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
|
||||
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
|
||||
|
||||
p: list[tuple[int, ...]] = [(1, 1, 1)]
|
||||
reveal_type(p) # revealed: list[tuple[int, ...]]
|
||||
|
||||
# literal promotion occurs based on assignability, an exact match is not required
|
||||
q: list[int | Literal[1]] = [1]
|
||||
reveal_type(q) # revealed: list[int]
|
||||
|
||||
r: list[Literal[1, 2, 3, 4]] = [1, 2]
|
||||
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
|
||||
```
|
||||
|
||||
## PEP-604 annotations are supported
|
||||
|
||||
```py
|
||||
@@ -376,22 +237,6 @@ x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
|
||||
## Annotations are deferred by default in Python 3.14 and later
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.14"
|
||||
```
|
||||
|
||||
```py
|
||||
x: Foo
|
||||
|
||||
class Foo: ...
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
|
||||
## Annotated assignments in stub files are inferred correctly
|
||||
|
||||
```pyi
|
||||
|
||||
@@ -820,30 +820,22 @@ reveal_type(C().c) # revealed: int
|
||||
|
||||
### Inheritance of class/instance attributes
|
||||
|
||||
#### Instance variable defined in a base class
|
||||
|
||||
```py
|
||||
class Base:
|
||||
attribute: int | None = 1
|
||||
declared_in_body: int | None = 1
|
||||
|
||||
redeclared_with_same_type: str | None
|
||||
redeclared_with_narrower_type: str | None
|
||||
redeclared_with_wider_type: str | None
|
||||
|
||||
overwritten_in_subclass_body: str
|
||||
overwritten_in_subclass_method: str
|
||||
|
||||
undeclared = "base"
|
||||
base_class_attribute_1: str | None
|
||||
base_class_attribute_2: str | None
|
||||
base_class_attribute_3: str | None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.pure_attribute: str | None = "value in base"
|
||||
|
||||
self.pure_overwritten_in_subclass_body: str = "value in base"
|
||||
self.pure_overwritten_in_subclass_method: str = "value in base"
|
||||
|
||||
self.pure_undeclared = "base"
|
||||
self.defined_in_init: str | None = "value in base"
|
||||
|
||||
class Intermediate(Base):
|
||||
# Redeclaring base class attributes with the *same *type is fine:
|
||||
redeclared_with_same_type: str | None = None
|
||||
base_class_attribute_1: str | None = None
|
||||
|
||||
# Redeclaring them with a *narrower type* is unsound, because modifications
|
||||
# through a `Base` reference could violate that constraint.
|
||||
@@ -855,67 +847,22 @@ class Intermediate(Base):
|
||||
# enabled by default can still be discussed.
|
||||
#
|
||||
# TODO: This should be an error
|
||||
redeclared_with_narrower_type: str
|
||||
base_class_attribute_2: str
|
||||
|
||||
# Redeclaring attributes with a *wider type* directly violates LSP.
|
||||
#
|
||||
# In this case, both mypy and pyright report an error.
|
||||
#
|
||||
# TODO: This should be an error
|
||||
redeclared_with_wider_type: str | int | None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
overwritten_in_subclass_body = None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
pure_overwritten_in_subclass_body = None
|
||||
|
||||
undeclared = "intermediate"
|
||||
|
||||
def set_attributes(self) -> None:
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
self.overwritten_in_subclass_method = None
|
||||
|
||||
# TODO: This should be an `invalid-assignment` error
|
||||
self.pure_overwritten_in_subclass_method = None
|
||||
|
||||
self.pure_undeclared = "intermediate"
|
||||
base_class_attribute_3: str | int | None
|
||||
|
||||
class Derived(Intermediate): ...
|
||||
|
||||
reveal_type(Derived.attribute) # revealed: int | None
|
||||
reveal_type(Derived().attribute) # revealed: int | None
|
||||
reveal_type(Derived.declared_in_body) # revealed: int | None
|
||||
|
||||
reveal_type(Derived.redeclared_with_same_type) # revealed: str | None
|
||||
reveal_type(Derived().redeclared_with_same_type) # revealed: str | None
|
||||
reveal_type(Derived().declared_in_body) # revealed: int | None
|
||||
|
||||
# TODO: It would probably be more consistent if these were `str | None`
|
||||
reveal_type(Derived.redeclared_with_narrower_type) # revealed: str
|
||||
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str
|
||||
|
||||
# TODO: It would probably be more consistent if these were `str | None`
|
||||
reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None
|
||||
reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None
|
||||
|
||||
# TODO: Both of these should be `str`
|
||||
reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None
|
||||
reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str
|
||||
|
||||
reveal_type(Derived.overwritten_in_subclass_method) # revealed: str
|
||||
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str
|
||||
|
||||
reveal_type(Derived().pure_attribute) # revealed: str | None
|
||||
|
||||
# TODO: This should be `str`
|
||||
reveal_type(Derived().pure_overwritten_in_subclass_body) # revealed: Unknown | None | str
|
||||
|
||||
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: str
|
||||
|
||||
# TODO: Both of these should be `Unknown | Literal["intermediate", "base"]`
|
||||
reveal_type(Derived.undeclared) # revealed: Unknown | Literal["intermediate"]
|
||||
reveal_type(Derived().undeclared) # revealed: Unknown | Literal["intermediate"]
|
||||
|
||||
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["intermediate", "base"]
|
||||
reveal_type(Derived().defined_in_init) # revealed: str | None
|
||||
```
|
||||
|
||||
## Accessing attributes on class objects
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
# Bidirectional type inference
|
||||
|
||||
ty partially supports bidirectional type inference. This is a mechanism for inferring the type of an
|
||||
expression "from the outside in". Normally, type inference proceeds "from the inside out". That is,
|
||||
in order to infer the type of an expression, the types of all sub-expressions must first be
|
||||
inferred. There is no reverse dependency. However, when performing complex type inference, such as
|
||||
when generics are involved, the type of an outer expression can sometimes be useful in inferring
|
||||
inner expressions. Bidirectional type inference is a mechanism that propagates such "expected types"
|
||||
to the inference of inner expressions.
|
||||
|
||||
## Propagating target type annotation
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def list1[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
l1 = list1(1)
|
||||
reveal_type(l1) # revealed: list[Literal[1]]
|
||||
l2: list[int] = list1(1)
|
||||
reveal_type(l2) # revealed: list[int]
|
||||
|
||||
# `list[Literal[1]]` and `list[int]` are incompatible, since `list[T]` is invariant in `T`.
|
||||
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
|
||||
l2 = l1
|
||||
|
||||
intermediate = list1(1)
|
||||
# TODO: the error will not occur if we can infer the type of `intermediate` to be `list[int]`
|
||||
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
|
||||
l3: list[int] = intermediate
|
||||
# TODO: it would be nice if this were `list[int]`
|
||||
reveal_type(intermediate) # revealed: list[Literal[1]]
|
||||
reveal_type(l3) # revealed: list[int]
|
||||
|
||||
l4: list[int | str] | None = list1(1)
|
||||
reveal_type(l4) # revealed: list[int | str]
|
||||
|
||||
def _(l: list[int] | None = None):
|
||||
l1 = l or list()
|
||||
reveal_type(l1) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
|
||||
|
||||
l2: list[int] = l or list()
|
||||
# it would be better if this were `list[int]`? (https://github.com/astral-sh/ty/issues/136)
|
||||
reveal_type(l2) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
|
||||
|
||||
def f[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-assignment] "Object of type `Literal[1] | list[Literal[1]]` is not assignable to `int | list[int]`"
|
||||
l5: int | list[int] = f(1, True)
|
||||
```
|
||||
|
||||
`typed_dict.py`:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
class TD(TypedDict):
|
||||
x: int
|
||||
|
||||
d1 = {"x": 1}
|
||||
d2: TD = {"x": 1}
|
||||
d3: dict[str, int] = {"x": 1}
|
||||
|
||||
reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int]
|
||||
reveal_type(d2) # revealed: TD
|
||||
reveal_type(d3) # revealed: dict[str, int]
|
||||
|
||||
def _() -> TD:
|
||||
return {"x": 1}
|
||||
|
||||
def _() -> TD:
|
||||
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
|
||||
return {}
|
||||
```
|
||||
|
||||
## Propagating return type annotation
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import overload, Callable
|
||||
|
||||
def list1[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def get_data() -> dict | None:
|
||||
return {}
|
||||
|
||||
def wrap_data() -> list[dict]:
|
||||
if not (res := get_data()):
|
||||
return list1({})
|
||||
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
|
||||
# `list[dict[Unknown, Unknown] & ~AlwaysFalsy]` and `list[dict[Unknown, Unknown]]` are incompatible,
|
||||
# but the return type check passes here because the type of `list1(res)` is inferred
|
||||
# by bidirectional type inference using the annotated return type, and the type of `res` is not used.
|
||||
return list1(res)
|
||||
|
||||
def wrap_data2() -> list[dict] | None:
|
||||
if not (res := get_data()):
|
||||
return None
|
||||
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
|
||||
return list1(res)
|
||||
|
||||
def deco[T](func: Callable[[], T]) -> Callable[[], T]:
|
||||
return func
|
||||
|
||||
def outer() -> Callable[[], list[dict]]:
|
||||
@deco
|
||||
def inner() -> list[dict]:
|
||||
if not (res := get_data()):
|
||||
return list1({})
|
||||
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
|
||||
return list1(res)
|
||||
return inner
|
||||
|
||||
@overload
|
||||
def f(x: int) -> list[int]: ...
|
||||
@overload
|
||||
def f(x: str) -> list[str]: ...
|
||||
def f(x: int | str) -> list[int] | list[str]:
|
||||
# `list[int] | list[str]` is disjoint from `list[int | str]`.
|
||||
if isinstance(x, int):
|
||||
return list1(x)
|
||||
else:
|
||||
return list1(x)
|
||||
|
||||
reveal_type(f(1)) # revealed: list[int]
|
||||
reveal_type(f("a")) # revealed: list[str]
|
||||
|
||||
async def g() -> list[int | str]:
|
||||
return list1(1)
|
||||
|
||||
def h[T](x: T, cond: bool) -> T | list[T]:
|
||||
return i(x, cond)
|
||||
|
||||
def i[T](x: T, cond: bool) -> T | list[T]:
|
||||
return x if cond else [x]
|
||||
```
|
||||
@@ -689,7 +689,7 @@ def _(
|
||||
# revealed: (obj: type) -> None
|
||||
reveal_type(e)
|
||||
|
||||
# revealed: (fget: ((Any, /) -> Any) | None = EllipsisType, fset: ((Any, Any, /) -> None) | None = EllipsisType, fdel: ((Any, /) -> None) | None = EllipsisType, doc: str | None = EllipsisType) -> property
|
||||
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown
|
||||
reveal_type(f)
|
||||
|
||||
# revealed: Overload[(self: property, instance: None, owner: type, /) -> Unknown, (self: property, instance: object, owner: type | None = None, /) -> Unknown]
|
||||
|
||||
@@ -99,6 +99,8 @@ If the arity check only matches a single overload, it should be evaluated as a r
|
||||
call should be reported directly and not as a `no-matching-overload` error.
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
from overloaded import f
|
||||
|
||||
reveal_type(f()) # revealed: None
|
||||
@@ -1208,7 +1210,11 @@ from typing_extensions import LiteralString
|
||||
|
||||
def f(a: Foo, b: list[str], c: list[LiteralString], e):
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(a.join(b)) # revealed: str
|
||||
|
||||
# TODO: we should select the second overload here and reveal `str`
|
||||
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
|
||||
reveal_type(a.join(b)) # revealed: LiteralString
|
||||
|
||||
reveal_type(a.join(c)) # revealed: LiteralString
|
||||
|
||||
# since both overloads match and they have return types that are not equivalent,
|
||||
|
||||
@@ -14,16 +14,9 @@ common usage.
|
||||
|
||||
### Explicit Super Object
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`super(pivot_class, owner)` performs attribute lookup along the MRO, starting immediately after the
|
||||
specified pivot class.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class A:
|
||||
def a(self): ...
|
||||
@@ -41,15 +34,21 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>,
|
||||
|
||||
super(C, C()).a
|
||||
super(C, C()).b
|
||||
super(C, C()).c # error: [unresolved-attribute]
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'C'>, C>` has no attribute `c`"
|
||||
super(C, C()).c
|
||||
|
||||
super(B, C()).a
|
||||
super(B, C()).b # error: [unresolved-attribute]
|
||||
super(B, C()).c # error: [unresolved-attribute]
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `b`"
|
||||
super(B, C()).b
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `c`"
|
||||
super(B, C()).c
|
||||
|
||||
super(A, C()).a # error: [unresolved-attribute]
|
||||
super(A, C()).b # error: [unresolved-attribute]
|
||||
super(A, C()).c # error: [unresolved-attribute]
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `a`"
|
||||
super(A, C()).a
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `b`"
|
||||
super(A, C()).b
|
||||
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `c`"
|
||||
super(A, C()).c
|
||||
|
||||
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||
@@ -57,80 +56,12 @@ reveal_type(super(C, C()).aa) # revealed: int
|
||||
reveal_type(super(C, C()).bb) # revealed: int
|
||||
```
|
||||
|
||||
Examples of explicit `super()` with unusual types. We allow almost any type to be passed as the
|
||||
second argument to `super()` -- the only exceptions are "pure abstract" types such as `Callable` and
|
||||
synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`object` nominal type.
|
||||
|
||||
```py
|
||||
import types
|
||||
from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||
|
||||
def f(): ...
|
||||
|
||||
class Foo[T]:
|
||||
def method(self): ...
|
||||
@property
|
||||
def some_property(self): ...
|
||||
|
||||
type Alias = int
|
||||
|
||||
class SomeTypedDict(TypedDict):
|
||||
x: int
|
||||
y: bytes
|
||||
|
||||
# revealed: <super: <class 'object'>, FunctionType>
|
||||
reveal_type(super(object, f))
|
||||
# revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||
reveal_type(super(object, types.FunctionType.__get__))
|
||||
# revealed: <super: <class 'object'>, GenericAlias>
|
||||
reveal_type(super(object, Foo[int]))
|
||||
# revealed: <super: <class 'object'>, _SpecialForm>
|
||||
reveal_type(super(object, Literal))
|
||||
# revealed: <super: <class 'object'>, TypeAliasType>
|
||||
reveal_type(super(object, Alias))
|
||||
# revealed: <super: <class 'object'>, MethodType>
|
||||
reveal_type(super(object, Foo().method))
|
||||
# revealed: <super: <class 'object'>, property>
|
||||
reveal_type(super(object, Foo.some_property))
|
||||
|
||||
def g(x: object) -> TypeIs[list[object]]:
|
||||
return isinstance(x, list)
|
||||
|
||||
def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
if hasattr(x, "bar"):
|
||||
# revealed: <Protocol with members 'bar'>
|
||||
reveal_type(x)
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super(object, x))
|
||||
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super(object, z))
|
||||
|
||||
is_list = g(x)
|
||||
# revealed: TypeIs[list[object] @ x]
|
||||
reveal_type(is_list)
|
||||
# revealed: <super: <class 'object'>, bool>
|
||||
reveal_type(super(object, is_list))
|
||||
|
||||
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||
reveal_type(super(object, y))
|
||||
```
|
||||
|
||||
### Implicit Super Object
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The implicit form `super()` is same as `super(__class__, <first argument>)`. The `__class__` refers
|
||||
to the class that contains the function where `super()` is used. The first argument refers to the
|
||||
current method’s first parameter (typically `self` or `cls`).
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -143,7 +74,6 @@ class B(A):
|
||||
def __init__(self, a: int):
|
||||
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
|
||||
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
|
||||
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
|
||||
super().__init__(a)
|
||||
|
||||
@classmethod
|
||||
@@ -156,123 +86,6 @@ super(B, B(42)).__init__(42)
|
||||
super(B, B).f()
|
||||
```
|
||||
|
||||
Some examples with unusual annotations for `self` or `cls`:
|
||||
|
||||
```py
|
||||
import enum
|
||||
from typing import Any, Self, Never, Protocol, Callable
|
||||
from ty_extensions import Intersection
|
||||
|
||||
class BuilderMeta(type):
|
||||
def __new__(
|
||||
cls: type[Any],
|
||||
name: str,
|
||||
bases: tuple[type, ...],
|
||||
dct: dict[str, Any],
|
||||
) -> BuilderMeta:
|
||||
# revealed: <super: <class 'BuilderMeta'>, Any>
|
||||
s = reveal_type(super())
|
||||
# revealed: Any
|
||||
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||
|
||||
class BuilderMeta2(type):
|
||||
def __new__(
|
||||
cls: type[BuilderMeta2],
|
||||
name: str,
|
||||
bases: tuple[type, ...],
|
||||
dct: dict[str, Any],
|
||||
) -> BuilderMeta2:
|
||||
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
|
||||
s = reveal_type(super())
|
||||
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
|
||||
# revealed: Unknown
|
||||
return reveal_type(s.__new__(cls, name, bases, dct))
|
||||
|
||||
class Foo[T]:
|
||||
x: T
|
||||
|
||||
def method(self: Any):
|
||||
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||
|
||||
if isinstance(self, Foo):
|
||||
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
|
||||
|
||||
def method2(self: Foo[T]):
|
||||
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||
reveal_type(super())
|
||||
|
||||
def method3(self: Foo):
|
||||
# revealed: <super: <class 'Foo'>, Foo[Unknown]>
|
||||
reveal_type(super())
|
||||
|
||||
def method4(self: Self):
|
||||
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
|
||||
reveal_type(super())
|
||||
|
||||
def method5[S: Foo[int]](self: S, other: S) -> S:
|
||||
# revealed: <super: <class 'Foo'>, Foo[int]>
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
|
||||
# revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
def method7[S](self: S, other: S) -> S:
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
def method8[S: int](self: S, other: S) -> S:
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
def method9[S: (int, str)](self: S, other: S) -> S:
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
def method10[S: Callable[..., str]](self: S, other: S) -> S:
|
||||
# error: [invalid-super-argument]
|
||||
# revealed: Unknown
|
||||
reveal_type(super())
|
||||
return self
|
||||
|
||||
type Alias = Bar
|
||||
|
||||
class Bar:
|
||||
def method(self: Alias):
|
||||
# revealed: <super: <class 'Bar'>, Bar>
|
||||
reveal_type(super())
|
||||
|
||||
def pls_dont_call_me(self: Never):
|
||||
# revealed: <super: <class 'Bar'>, Unknown>
|
||||
reveal_type(super())
|
||||
|
||||
def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
|
||||
# revealed: <super: <class 'Bar'>, Bar>
|
||||
reveal_type(super())
|
||||
|
||||
class P(Protocol):
|
||||
def method(self: P):
|
||||
# revealed: <super: <class 'P'>, P>
|
||||
reveal_type(super())
|
||||
|
||||
class E(enum.Enum):
|
||||
X = 1
|
||||
|
||||
def method(self: E):
|
||||
match self:
|
||||
case E.X:
|
||||
# revealed: <super: <class 'E'>, E>
|
||||
reveal_type(super())
|
||||
```
|
||||
|
||||
### Unbound Super Object
|
||||
|
||||
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
|
||||
@@ -354,19 +167,11 @@ class A:
|
||||
## Built-ins and Literals
|
||||
|
||||
```py
|
||||
from enum import Enum
|
||||
|
||||
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
|
||||
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
|
||||
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
|
||||
reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
|
||||
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
|
||||
reveal_type(super(bytes, b"")) # revealed: <super: <class 'bytes'>, bytes>
|
||||
|
||||
class E(Enum):
|
||||
X = 42
|
||||
|
||||
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
|
||||
```
|
||||
|
||||
## Descriptor Behavior with Super
|
||||
@@ -537,7 +342,7 @@ def f(x: int):
|
||||
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
|
||||
super(IntAlias, 0)
|
||||
|
||||
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
|
||||
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, Literal[""])` call"
|
||||
# revealed: Unknown
|
||||
reveal_type(super(int, str()))
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class C:
|
||||
name: str
|
||||
```
|
||||
|
||||
## Types of dataclass-transformers
|
||||
## Types of decorators
|
||||
|
||||
The examples from this section are straight from the Python documentation on
|
||||
[`typing.dataclass_transform`].
|
||||
@@ -165,7 +165,7 @@ Normal(1) < Normal(2) # error: [unsupported-operator]
|
||||
class NormalOverwritten:
|
||||
inner: int
|
||||
|
||||
reveal_type(NormalOverwritten(1) < NormalOverwritten(2)) # revealed: bool
|
||||
NormalOverwritten(1) < NormalOverwritten(2)
|
||||
|
||||
@order_default_false
|
||||
class OrderFalse:
|
||||
@@ -177,13 +177,13 @@ OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
|
||||
class OrderFalseOverwritten:
|
||||
inner: int
|
||||
|
||||
reveal_type(OrderFalseOverwritten(1) < OrderFalseOverwritten(2)) # revealed: bool
|
||||
OrderFalseOverwritten(1) < OrderFalseOverwritten(2)
|
||||
|
||||
@order_default_true
|
||||
class OrderTrue:
|
||||
inner: int
|
||||
|
||||
reveal_type(OrderTrue(1) < OrderTrue(2)) # revealed: bool
|
||||
OrderTrue(1) < OrderTrue(2)
|
||||
|
||||
@order_default_true(order=False)
|
||||
class OrderTrueOverwritten:
|
||||
@@ -193,36 +193,6 @@ class OrderTrueOverwritten:
|
||||
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
|
||||
```
|
||||
|
||||
This also works for metaclass-based transformers:
|
||||
|
||||
```py
|
||||
@dataclass_transform(order_default=True)
|
||||
class OrderedModelMeta(type): ...
|
||||
|
||||
class OrderedModel(metaclass=OrderedModelMeta): ...
|
||||
|
||||
class TestWithMeta(OrderedModel):
|
||||
inner: int
|
||||
|
||||
reveal_type(TestWithMeta(1) < TestWithMeta(2)) # revealed: bool
|
||||
```
|
||||
|
||||
And for base-class-based transformers:
|
||||
|
||||
```py
|
||||
@dataclass_transform(order_default=True)
|
||||
class OrderedModelBase: ...
|
||||
|
||||
class TestWithBase(OrderedModelBase):
|
||||
inner: int
|
||||
|
||||
# TODO: No errors here, should reveal `bool`
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [unsupported-operator]
|
||||
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `kw_only_default`
|
||||
|
||||
When provided, sets the default value for the `kw_only` parameter of `field()`.
|
||||
@@ -232,7 +202,7 @@ from typing import dataclass_transform
|
||||
from dataclasses import field
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def create_model(*, kw_only: bool = True): ...
|
||||
def create_model(*, init=True): ...
|
||||
@create_model()
|
||||
class A:
|
||||
name: str
|
||||
@@ -243,252 +213,27 @@ a = A(name="Harry")
|
||||
a = A("Harry")
|
||||
```
|
||||
|
||||
This can be overridden by setting `kw_only=False` when applying the decorator:
|
||||
TODO: This can be overridden by the call to the decorator function.
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
def create_model(*, kw_only: bool = True): ...
|
||||
@create_model(kw_only=False)
|
||||
class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
# TODO: Should not emit errors
|
||||
# error: [missing-argument]
|
||||
# error: [too-many-positional-arguments]
|
||||
c = CustomerModel(1, "Harry")
|
||||
```
|
||||
|
||||
This also works for metaclass-based transformers:
|
||||
### `field_specifiers`
|
||||
|
||||
```py
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
class ModelMeta(type): ...
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
reveal_type(TestMeta.__init__) # revealed: (self: TestMeta, *, name: str) -> None
|
||||
```
|
||||
|
||||
And for base-class-based transformers:
|
||||
|
||||
```py
|
||||
@dataclass_transform(kw_only_default=True)
|
||||
class ModelBase: ...
|
||||
|
||||
class TestBase(ModelBase):
|
||||
name: str
|
||||
|
||||
# TODO: This should be `(self: TestBase, *, name: str) -> None`
|
||||
reveal_type(TestBase.__init__) # revealed: def __init__(self) -> None
|
||||
```
|
||||
|
||||
### `frozen_default`
|
||||
|
||||
When provided, sets the default value for the `frozen` parameter of `field()`.
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def create_model(*, frozen: bool = True): ...
|
||||
@create_model()
|
||||
class ImmutableModel:
|
||||
name: str
|
||||
|
||||
i = ImmutableModel(name="test")
|
||||
i.name = "new" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
Again, this can be overridden by setting `frozen=False` when applying the decorator:
|
||||
|
||||
```py
|
||||
@create_model(frozen=False)
|
||||
class MutableModel:
|
||||
name: str
|
||||
|
||||
m = MutableModel(name="test")
|
||||
m.name = "new" # No error
|
||||
```
|
||||
|
||||
This also works for metaclass-based transformers:
|
||||
|
||||
```py
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class ModelMeta(type): ...
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
t = TestMeta(name="test")
|
||||
t.name = "new" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
And for base-class-based transformers:
|
||||
|
||||
```py
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class ModelBase: ...
|
||||
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
t = TestMeta(name="test")
|
||||
|
||||
# TODO: this should be an `invalid-assignment` error
|
||||
t.name = "new"
|
||||
```
|
||||
|
||||
### Combining parameters
|
||||
|
||||
Combining several of these parameters also works as expected:
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(eq_default=True, order_default=False, kw_only_default=True, frozen_default=True)
|
||||
def create_model(*, eq: bool = True, order: bool = False, kw_only: bool = True, frozen: bool = True): ...
|
||||
@create_model(eq=False, order=True, kw_only=False, frozen=False)
|
||||
class OverridesAllParametersModel:
|
||||
name: str
|
||||
age: int
|
||||
|
||||
# Positional arguments are allowed:
|
||||
model = OverridesAllParametersModel("test", 25)
|
||||
|
||||
# Mutation is allowed:
|
||||
model.name = "new" # No error
|
||||
|
||||
# Comparison methods are generated:
|
||||
model < model # No error
|
||||
```
|
||||
|
||||
### Overwriting of default parameters on the dataclass-like class
|
||||
|
||||
#### Using function-based transformers
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
def default_frozen_model(*, frozen: bool = True): ...
|
||||
@default_frozen_model()
|
||||
class Frozen:
|
||||
name: str
|
||||
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
|
||||
@default_frozen_model(frozen=False)
|
||||
class Mutable:
|
||||
name: str
|
||||
|
||||
m = Mutable(name="test")
|
||||
m.name = "new" # No error
|
||||
```
|
||||
|
||||
#### Using metaclass-based transformers
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class DefaultFrozenMeta(type):
|
||||
def __new__(
|
||||
cls,
|
||||
name,
|
||||
bases,
|
||||
namespace,
|
||||
*,
|
||||
frozen: bool = True,
|
||||
): ...
|
||||
|
||||
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
|
||||
|
||||
class Frozen(DefaultFrozenModel):
|
||||
name: str
|
||||
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
|
||||
class Mutable(DefaultFrozenModel, frozen=False):
|
||||
name: str
|
||||
|
||||
m = Mutable(name="test")
|
||||
# TODO: no error here
|
||||
m.name = "new" # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
#### Using base-class-based transformers
|
||||
|
||||
```py
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform(frozen_default=True)
|
||||
class DefaultFrozenModel:
|
||||
def __init_subclass__(
|
||||
cls,
|
||||
*,
|
||||
frozen: bool = True,
|
||||
): ...
|
||||
|
||||
class Frozen(DefaultFrozenModel):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
f = Frozen(name="test")
|
||||
# TODO: this should be an `invalid-assignment` error
|
||||
f.name = "new"
|
||||
|
||||
class Mutable(DefaultFrozenModel, frozen=False):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
m = Mutable(name="test")
|
||||
m.name = "new" # No error
|
||||
```
|
||||
|
||||
## `field_specifiers`
|
||||
|
||||
The `field_specifiers` argument can be used to specify a list of functions that should be treated
|
||||
similar to `dataclasses.field` for normal dataclasses.
|
||||
|
||||
The [`typing.dataclass_transform`] specification also allows classes (such as `dataclasses.Field`)
|
||||
to be listed in `field_specifiers`, but it is currently unclear how this should work, and other type
|
||||
checkers do not seem to support this either.
|
||||
|
||||
### Basic example
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field(init=False)
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
# TODO: Should be `(self: Person, name: str, *, age: int | None) -> None`
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, id: int = Any, name: str = Any, age: int | None = Any) -> None
|
||||
|
||||
# TODO: No error here
|
||||
# error: [invalid-argument-type]
|
||||
alice = Person("Alice", age=30)
|
||||
|
||||
reveal_type(alice.id) # revealed: int
|
||||
reveal_type(alice.name) # revealed: str
|
||||
reveal_type(alice.age) # revealed: int | None
|
||||
```
|
||||
To do
|
||||
|
||||
## Overloaded dataclass-like decorators
|
||||
|
||||
@@ -577,32 +322,4 @@ D1(1.2) # error: [invalid-argument-type]
|
||||
D2(1.2) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Use cases
|
||||
|
||||
#### Home Assistant
|
||||
|
||||
Home Assistant uses a pattern like this, where a `@dataclass`-decorated class inherits from a base
|
||||
class that is itself a `dataclass`-like construct via a metaclass-based dataclass transformer. Make
|
||||
sure that we recognize all fields in a hierarchy like this:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type):
|
||||
pass
|
||||
|
||||
class Sensor(metaclass=ModelMeta):
|
||||
key: int
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TemperatureSensor(Sensor):
|
||||
name: str
|
||||
|
||||
t = TemperatureSensor(key=1, name="Temperature Sensor")
|
||||
reveal_type(t.key) # revealed: int
|
||||
reveal_type(t.name) # revealed: str
|
||||
```
|
||||
|
||||
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
|
||||
|
||||
@@ -1010,6 +1010,7 @@ python-version = "3.10"
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass, field, KW_ONLY
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
@@ -1204,9 +1205,9 @@ python-version = "3.12"
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
from types import FunctionType
|
||||
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to, is_equivalent_to
|
||||
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to
|
||||
|
||||
@dataclass(order=True)
|
||||
@dataclass
|
||||
class C:
|
||||
x: int
|
||||
|
||||
@@ -1233,20 +1234,8 @@ static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType))
|
||||
static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType))
|
||||
static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType))
|
||||
|
||||
static_assert(is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||
static_assert(is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||
|
||||
static_assert(is_equivalent_to(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||
static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||
static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||
|
||||
static_assert(is_subtype_of(DunderInitType, FunctionType))
|
||||
```
|
||||
|
||||
It should be possible to mock out synthesized methods:
|
||||
|
||||
```py
|
||||
from unittest.mock import Mock
|
||||
|
||||
def test_c():
|
||||
c = C(1)
|
||||
c.__lt__ = Mock()
|
||||
```
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
# Legacy typevar creation diagnostics
|
||||
|
||||
The full tests for these features are in `generics/legacy/variables.md`.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Must have a name
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar()
|
||||
```
|
||||
|
||||
## Name can't be given more than once
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", name="T")
|
||||
```
|
||||
|
||||
## Must be directly assigned to a variable
|
||||
|
||||
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
|
||||
> part of a larger expression).
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
```
|
||||
|
||||
## `TypeVar` parameter must match variable name
|
||||
|
||||
> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("Q")
|
||||
```
|
||||
|
||||
## No variadic arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
types = (int, str)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", *types)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
S = TypeVar("S", **{"bound": int})
|
||||
```
|
||||
|
||||
## Cannot have only one constraint
|
||||
|
||||
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
|
||||
> be at least two constraints, if any; specifying a single constraint is disallowed.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
## Cannot have both bound and constraint
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
## Cannot be both covariant and contravariant
|
||||
|
||||
> To facilitate the declaration of container types where covariant or contravariant type checking is
|
||||
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
|
||||
> most one of these may be passed.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
## Boolean parameters must be unambiguous
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", covariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U = TypeVar("U", contravariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
## Invalid keyword arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
## Invalid feature for this Python version
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
@@ -61,35 +61,6 @@ class DataFrame:
|
||||
pass
|
||||
```
|
||||
|
||||
## Class from different module with the same qualified name
|
||||
|
||||
`package/__init__.py`:
|
||||
|
||||
```py
|
||||
from .foo import MyClass
|
||||
|
||||
def make_MyClass() -> MyClass:
|
||||
return MyClass()
|
||||
```
|
||||
|
||||
`package/foo.pyi`:
|
||||
|
||||
```pyi
|
||||
class MyClass: ...
|
||||
```
|
||||
|
||||
`package/foo.py`:
|
||||
|
||||
```py
|
||||
class MyClass: ...
|
||||
|
||||
def get_MyClass() -> MyClass:
|
||||
from . import make_MyClass
|
||||
|
||||
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1`, found `package.foo.MyClass @ src/package/foo.pyi:1`"
|
||||
return make_MyClass()
|
||||
```
|
||||
|
||||
## Enum from different modules
|
||||
|
||||
```py
|
||||
@@ -234,8 +205,8 @@ from typing import Protocol
|
||||
import proto_a
|
||||
import proto_b
|
||||
|
||||
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
def _(drawable_b: proto_b.Drawable):
|
||||
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
|
||||
drawable: proto_a.Drawable = drawable_b
|
||||
```
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
## Invalid syntax
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
try:
|
||||
print
|
||||
except as e: # error: [invalid-syntax]
|
||||
|
||||
@@ -108,7 +108,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp
|
||||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, Literal, TypeVar
|
||||
from typing import Generic, Literal, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -195,7 +195,7 @@ reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
|
||||
We can infer the type parameter from a type context:
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -240,7 +240,7 @@ consistent with each other.
|
||||
### `__new__` only
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -257,7 +257,7 @@ wrong_innards: C[int] = C("five")
|
||||
### `__init__` only
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -273,7 +273,7 @@ wrong_innards: C[int] = C("five")
|
||||
### Identical `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -292,7 +292,7 @@ wrong_innards: C[int] = C("five")
|
||||
### Compatible `__new__` and `__init__` signatures
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -325,7 +325,7 @@ If either method comes from a generic base class, we don't currently use its inf
|
||||
to specialize the class.
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -344,7 +344,7 @@ reveal_type(D(1)) # revealed: D[int]
|
||||
### Generic class inherits `__init__` from generic base class
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -364,7 +364,7 @@ reveal_type(D(1, "str")) # revealed: D[int, str]
|
||||
This is a specific example of the above, since it was reported specifically by a user.
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -382,7 +382,7 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher
|
||||
context. But from the user's point of view, this is another example of the above.)
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -403,7 +403,7 @@ python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar, Sequence, Never
|
||||
from typing import TypeVar, Sequence, Never
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -421,7 +421,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
|
||||
### `__init__` is itself generic
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
T = TypeVar("T")
|
||||
@@ -440,7 +440,7 @@ wrong_innards: C[int] = C("five", 1)
|
||||
### Some `__init__` overloads only apply to certain specializations
|
||||
|
||||
```py
|
||||
from typing_extensions import overload, Generic, TypeVar
|
||||
from typing import overload, Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -480,7 +480,7 @@ C[None](12)
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -494,7 +494,7 @@ reveal_type(A(x=1)) # revealed: A[int]
|
||||
### Class typevar has another typevar as a default
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=T)
|
||||
@@ -515,7 +515,7 @@ When a generic subclass fills its superclass's type parameter with one of its ow
|
||||
propagate through:
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -549,7 +549,7 @@ scope for the method.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -581,7 +581,7 @@ In a specialized generic alias, the specialization is applied to the attributes
|
||||
class.
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar, Protocol
|
||||
from typing import Generic, TypeVar, Protocol
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U")
|
||||
@@ -639,7 +639,7 @@ reveal_type(d.method3().x) # revealed: int
|
||||
When a method is overloaded, the specialization is applied to all overloads.
|
||||
|
||||
```py
|
||||
from typing_extensions import overload, Generic, TypeVar
|
||||
from typing import overload, Generic, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
@@ -667,7 +667,7 @@ A class can use itself as the type parameter of one of its superclasses. (This i
|
||||
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
|
||||
|
||||
```pyi
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -682,7 +682,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
|
||||
A similar case can work in a non-stub file, if forward references are stringified:
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -697,7 +697,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
|
||||
In a non-stub file, without stringified forward references, this raises a `NameError`:
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -710,7 +710,7 @@ class Sub(Base[Sub]): ...
|
||||
### Cyclic inheritance as a generic parameter
|
||||
|
||||
```pyi
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -722,7 +722,7 @@ class Derived(list[Derived[T]], Generic[T]): ...
|
||||
Inheritance that would result in a cyclic MRO is detected as an error.
|
||||
|
||||
```py
|
||||
from typing_extensions import Generic, TypeVar
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Literal[42, 43]
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
T = TypeVar("T", bound=int)
|
||||
|
||||
@@ -199,6 +200,7 @@ reveal_type(f("string")) # revealed: Unknown
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
T = TypeVar("T", int, None)
|
||||
|
||||
@@ -321,9 +323,6 @@ def union_param(x: T | None) -> T:
|
||||
reveal_type(union_param("a")) # revealed: Literal["a"]
|
||||
reveal_type(union_param(1)) # revealed: Literal[1]
|
||||
reveal_type(union_param(None)) # revealed: Unknown
|
||||
|
||||
def _(x: int | None):
|
||||
reveal_type(union_param(x)) # revealed: int
|
||||
```
|
||||
|
||||
```py
|
||||
|
||||
@@ -6,8 +6,6 @@ for both type variable syntaxes.
|
||||
|
||||
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
|
||||
|
||||
Diagnostics for invalid type variables are snapshotted in `diagnostics/legacy_typevars.md`.
|
||||
|
||||
## Type variables
|
||||
|
||||
### Defining legacy type variables
|
||||
@@ -26,16 +24,7 @@ reveal_type(T) # revealed: typing.TypeVar
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
The typevar name can also be provided as a keyword argument:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar(name="T")
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
```
|
||||
|
||||
### Must be directly assigned to a variable
|
||||
### Directly assigned to a variable
|
||||
|
||||
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
|
||||
> part of a larger expression).
|
||||
@@ -44,24 +33,13 @@ reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# TODO: no error
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U: TypeVar = TypeVar("U")
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
|
||||
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
|
||||
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
||||
TestList = list[TypeVar("W")]
|
||||
```
|
||||
|
||||
### `TypeVar` parameter must match variable name
|
||||
@@ -71,7 +49,7 @@ reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
|
||||
T = TypeVar("Q")
|
||||
```
|
||||
|
||||
@@ -88,22 +66,6 @@ T = TypeVar("T")
|
||||
T = TypeVar("T")
|
||||
```
|
||||
|
||||
### No variadic arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
types = (int, str)
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", *types)
|
||||
reveal_type(T) # revealed: TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
S = TypeVar("S", **{"bound": int})
|
||||
reveal_type(S) # revealed: TypeVar
|
||||
```
|
||||
|
||||
### Type variables with a default
|
||||
|
||||
Note that the `__default__` property is only available in Python ≥3.13.
|
||||
@@ -129,11 +91,6 @@ reveal_type(S.__default__) # revealed: NoDefault
|
||||
|
||||
### Using other typevars as a default
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar, Union
|
||||
|
||||
@@ -167,15 +124,6 @@ S = TypeVar("S")
|
||||
reveal_type(S.__bound__) # revealed: None
|
||||
```
|
||||
|
||||
The upper bound must be a valid type expression:
|
||||
|
||||
```py
|
||||
from typing import TypedDict
|
||||
|
||||
# error: [invalid-type-form]
|
||||
T = TypeVar("T", bound=TypedDict)
|
||||
```
|
||||
|
||||
### Type variables with constraints
|
||||
|
||||
```py
|
||||
@@ -190,16 +138,6 @@ S = TypeVar("S")
|
||||
reveal_type(S.__constraints__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
Constraints are not simplified relative to each other, even if one is a subtype of the other:
|
||||
|
||||
```py
|
||||
T = TypeVar("T", int, bool)
|
||||
reveal_type(T.__constraints__) # revealed: tuple[int, bool]
|
||||
|
||||
S = TypeVar("S", float, str)
|
||||
reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
|
||||
```
|
||||
|
||||
### Cannot have only one constraint
|
||||
|
||||
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
|
||||
@@ -208,19 +146,10 @@ reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
# TODO: error: [invalid-type-variable-constraints]
|
||||
T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
### Cannot have both bound and constraint
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
### Cannot be both covariant and contravariant
|
||||
|
||||
> To facilitate the declaration of container types where covariant or contravariant type checking is
|
||||
@@ -234,10 +163,10 @@ from typing import TypeVar
|
||||
T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
### Boolean parameters must be unambiguous
|
||||
### Variance parameters must be unambiguous
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
from typing import TypeVar
|
||||
|
||||
def cond() -> bool:
|
||||
return True
|
||||
@@ -247,73 +176,6 @@ T = TypeVar("T", covariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
U = TypeVar("U", contravariant=cond())
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
### Invalid keyword arguments
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
```pyi
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", invalid_keyword=True)
|
||||
|
||||
```
|
||||
|
||||
### Constructor signature versioning
|
||||
|
||||
#### For `typing.TypeVar`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
In a stub file, features from the latest supported Python version can be used on any version.
|
||||
There's no need to require use of `typing_extensions.TypeVar` in a stub file, when the type checker
|
||||
can understand the typevar definition perfectly well either way, and there can be no runtime error.
|
||||
(Perhaps it's arguable whether this special case is worth it, but other type checkers do it, so we
|
||||
maintain compatibility.)
|
||||
|
||||
```pyi
|
||||
from typing import TypeVar
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
But this raises an error in a non-stub file:
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
# error: [invalid-legacy-type-variable]
|
||||
T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
#### For `typing_extensions.TypeVar`
|
||||
|
||||
`typing_extensions.TypeVar` always supports the latest features, on any Python version.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T", default=int)
|
||||
# TODO: should not error, should reveal `int`
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(T.__default__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Callability
|
||||
@@ -369,96 +231,4 @@ def constrained(x: T_constrained):
|
||||
reveal_type(type(x)) # revealed: type[int] | type[str]
|
||||
```
|
||||
|
||||
## Cycles
|
||||
|
||||
### Bounds and constraints
|
||||
|
||||
A typevar's bounds and constraints cannot be generic, cyclic or otherwise:
|
||||
|
||||
```py
|
||||
from typing import Any, TypeVar
|
||||
|
||||
S = TypeVar("S")
|
||||
|
||||
# TODO: error
|
||||
T = TypeVar("T", bound=list[S])
|
||||
|
||||
# TODO: error
|
||||
U = TypeVar("U", list["T"], str)
|
||||
|
||||
# TODO: error
|
||||
V = TypeVar("V", list["V"], str)
|
||||
```
|
||||
|
||||
However, they are lazily evaluated and can cyclically refer to their own type:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T = TypeVar("T", bound=list["G"])
|
||||
|
||||
class G(Generic[T]):
|
||||
x: T
|
||||
|
||||
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
|
||||
```
|
||||
|
||||
### Defaults
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
Defaults can be generic, but can only refer to typevars from the same scope if they were defined
|
||||
earlier in that scope:
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
U = TypeVar("U", default=T)
|
||||
|
||||
class C(Generic[T, U]):
|
||||
x: T
|
||||
y: U
|
||||
|
||||
reveal_type(C[int, str]().x) # revealed: int
|
||||
reveal_type(C[int, str]().y) # revealed: str
|
||||
reveal_type(C[int]().x) # revealed: int
|
||||
reveal_type(C[int]().y) # revealed: int
|
||||
|
||||
# TODO: error
|
||||
V = TypeVar("V", default="V")
|
||||
|
||||
class D(Generic[V]):
|
||||
x: V
|
||||
|
||||
# TODO: we shouldn't leak a typevar like this in type inference
|
||||
reveal_type(D().x) # revealed: V@D
|
||||
```
|
||||
|
||||
## Regression
|
||||
|
||||
### Use of typevar with default inside a function body that binds it
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
_DataT = TypeVar("_DataT", bound=int, default=int)
|
||||
|
||||
class Event(Generic[_DataT]):
|
||||
def __init__(self, data: _DataT) -> None:
|
||||
self.data = data
|
||||
|
||||
def async_fire_internal(event_data: _DataT):
|
||||
event: Event[_DataT] | None = None
|
||||
event = Event(event_data)
|
||||
```
|
||||
|
||||
[generics]: https://typing.python.org/en/latest/spec/generics.html
|
||||
|
||||
@@ -286,9 +286,6 @@ def union_param[T](x: T | None) -> T:
|
||||
reveal_type(union_param("a")) # revealed: Literal["a"]
|
||||
reveal_type(union_param(1)) # revealed: Literal[1]
|
||||
reveal_type(union_param(None)) # revealed: Unknown
|
||||
|
||||
def _(x: int | None):
|
||||
reveal_type(union_param(x)) # revealed: int
|
||||
```
|
||||
|
||||
```py
|
||||
|
||||
140
crates/ty_python_semantic/resources/mdtest/hover.md
Normal file
140
crates/ty_python_semantic/resources/mdtest/hover.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Hover type assertions
|
||||
|
||||
You can use the `hover` assertion to test the inferred type of an expression. This exercises the
|
||||
same logic as the hover LSP action.
|
||||
|
||||
Typically, you will not need to use the `hover` action to test the behavior of our type inference
|
||||
code, since you can also use `reveal_type` to display the inferred type of an expression. Since
|
||||
`reveal_type` is part of the standard library, we prefer to use it when possible.
|
||||
|
||||
However, there are certain situations where `reveal_type` and `hover` will give different results.
|
||||
In particular, `reveal_type` is not transparent to bidirectional type checking, as seen in the
|
||||
"Different results" section below.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Basic syntax
|
||||
|
||||
The `hover` assertion operates on a specific location in the source text. We find the "inner-most"
|
||||
expression at that position, and then query the inferred type of that expression. The row to query
|
||||
is identified just like any other mdtest assertion. The column to query is identified by a down
|
||||
arrow (↓) in the assertion. (Note that the down arrow should always appear immediately before the
|
||||
`hover` keyword in the assertion.)
|
||||
|
||||
```py
|
||||
def test_basic_types(parameter: int) -> None:
|
||||
# ↓ hover: int
|
||||
parameter
|
||||
|
||||
# ↓ hover: Literal[10]
|
||||
number = 10
|
||||
|
||||
# ↓ hover: Literal["hello"]
|
||||
text = "hello"
|
||||
```
|
||||
|
||||
### Multiple hovers on the same line
|
||||
|
||||
We can have multiple hover assertions for different positions on the same line:
|
||||
|
||||
```py
|
||||
# ↓ hover: Literal[1]
|
||||
# ↓ hover: Literal[2]
|
||||
# ↓ hover: Literal[3]
|
||||
total = 1 + 2 + 3
|
||||
|
||||
# ↓ hover: Literal[5]
|
||||
# ↓ hover: Literal[3]
|
||||
result = max(5, 3)
|
||||
```
|
||||
|
||||
### Hovering works on every character in an expression
|
||||
|
||||
```py
|
||||
def _(param: bool) -> None:
|
||||
# ↓ hover: bool
|
||||
# ↓ hover: bool
|
||||
# ↓ hover: bool
|
||||
# ↓ hover: bool
|
||||
# ↓ hover: bool
|
||||
result = param
|
||||
```
|
||||
|
||||
### Hovering with unicode characters
|
||||
|
||||
```py
|
||||
def _(café: str) -> None:
|
||||
# ↓ hover: str
|
||||
# ↓ hover: str
|
||||
# ↓ hover: str
|
||||
# ↓ hover: str
|
||||
result = café
|
||||
```
|
||||
|
||||
## Different results for `reveal_type` and `hover`
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
def f(x: dict[str, int]) -> None: ...
|
||||
|
||||
# revealed: dict[Unknown, Unknown]
|
||||
f(reveal_type({}))
|
||||
|
||||
# ↓ hover: dict[str, int]
|
||||
f({})
|
||||
```
|
||||
|
||||
## Hovering on different expression types
|
||||
|
||||
### Literals
|
||||
|
||||
```py
|
||||
# ↓ hover: Literal[42]
|
||||
int_value = 42
|
||||
|
||||
# ↓ hover: Literal["test"]
|
||||
string_value = "test"
|
||||
|
||||
# ↓ hover: Literal[True]
|
||||
bool_value = True
|
||||
```
|
||||
|
||||
### Names and attributes
|
||||
|
||||
```py
|
||||
class MyClass:
|
||||
value: int
|
||||
|
||||
def test_attributes(instance: MyClass) -> None:
|
||||
# ↓ hover: MyClass
|
||||
instance
|
||||
|
||||
# ↓ hover: int
|
||||
instance.value
|
||||
```
|
||||
|
||||
### Function definitions
|
||||
|
||||
```py
|
||||
def f(x: int) -> None: ...
|
||||
|
||||
# ↓ hover: def f(x: int) -> None
|
||||
result = f
|
||||
```
|
||||
|
||||
### Binary operations
|
||||
|
||||
```py
|
||||
# ↓ hover: Literal[10]
|
||||
# ↓ hover: Literal[20]
|
||||
result = 10 + 20
|
||||
```
|
||||
|
||||
### Comprehensions
|
||||
|
||||
```py
|
||||
# List comprehension
|
||||
# ↓ hover: list[@Todo(list comprehension element type)]
|
||||
result = [x for x in range(5)]
|
||||
```
|
||||
@@ -5,6 +5,8 @@ all members available on a given type. This routine is used for autocomplete sug
|
||||
|
||||
## Basic functionality
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The `ty_extensions.all_members` and `ty_extensions.has_member` functions expose a Python-level API
|
||||
that can be used to query which attributes `ide_support::all_members` understands as being available
|
||||
on a given object. For example, all member functions of `str` are available on `"a"`. The Python API
|
||||
@@ -43,10 +45,10 @@ The full list of all members is relatively long, but `reveal_type` can be used i
|
||||
`all_members` to see them all:
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
from ty_extensions import all_members
|
||||
|
||||
# revealed: tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]
|
||||
reveal_type(all_members("a"))
|
||||
reveal_type(all_members("a")) # error: [revealed-type]
|
||||
```
|
||||
|
||||
## Kinds of types
|
||||
|
||||
@@ -42,6 +42,8 @@ async def foo():
|
||||
### No `__aiter__` method
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class NotAsyncIterable: ...
|
||||
|
||||
async def foo():
|
||||
@@ -53,6 +55,8 @@ async def foo():
|
||||
### Synchronously iterable, but not asynchronously iterable
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
async def foo():
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
@@ -70,6 +74,8 @@ async def foo():
|
||||
### No `__anext__` method
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class NoAnext: ...
|
||||
|
||||
class AsyncIterable:
|
||||
@@ -85,6 +91,8 @@ async def foo():
|
||||
### Possibly missing `__anext__` method
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
async def foo(flag: bool):
|
||||
class PossiblyUnboundAnext:
|
||||
if flag:
|
||||
@@ -103,6 +111,8 @@ async def foo(flag: bool):
|
||||
### Possibly missing `__aiter__` method
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
async def foo(flag: bool):
|
||||
class AsyncIterable:
|
||||
async def __anext__(self) -> int:
|
||||
@@ -121,6 +131,8 @@ async def foo(flag: bool):
|
||||
### Wrong signature for `__aiter__`
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class AsyncIterator:
|
||||
async def __anext__(self) -> int:
|
||||
return 42
|
||||
@@ -138,6 +150,8 @@ async def foo():
|
||||
### Wrong signature for `__anext__`
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class AsyncIterator:
|
||||
async def __anext__(self, arg: int) -> int: # wrong
|
||||
return 42
|
||||
|
||||
@@ -108,6 +108,8 @@ reveal_type(x)
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class NotIterable:
|
||||
if flag:
|
||||
@@ -245,7 +247,8 @@ class StrIterator:
|
||||
|
||||
def f(x: IntIterator | StrIterator):
|
||||
for a in x:
|
||||
reveal_type(a) # revealed: int | str
|
||||
# TODO: this should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(a) # revealed: int
|
||||
```
|
||||
|
||||
Most real-world iterable types use `Iterator` as the return annotation of their `__iter__` methods:
|
||||
@@ -257,11 +260,14 @@ def g(
|
||||
c: Literal["foo", b"bar"],
|
||||
):
|
||||
for x in a:
|
||||
reveal_type(x) # revealed: int | str
|
||||
# TODO: should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(x) # revealed: int
|
||||
for y in b:
|
||||
reveal_type(y) # revealed: str | int
|
||||
# TODO: should be `str | int` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(y) # revealed: str
|
||||
for z in c:
|
||||
reveal_type(z) # revealed: LiteralString | int
|
||||
# TODO: should be `LiteralString | int` (https://github.com/astral-sh/ty/issues/1089)
|
||||
reveal_type(z) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
@@ -269,6 +275,8 @@ def g(
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
@@ -288,6 +296,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class TestIter:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
@@ -364,6 +374,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
@@ -382,6 +394,8 @@ for x in Iterable():
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Bad:
|
||||
def __iter__(self) -> int:
|
||||
return 42
|
||||
@@ -414,6 +428,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator1:
|
||||
def __next__(self, extra_arg) -> int:
|
||||
return 42
|
||||
@@ -443,6 +459,8 @@ for y in Iterable2():
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
@@ -495,6 +513,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
@@ -518,6 +538,8 @@ def _(flag1: bool, flag2: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Bad:
|
||||
__getitem__: None = None
|
||||
|
||||
@@ -531,6 +553,8 @@ for x in Bad():
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class CustomCallable:
|
||||
if flag:
|
||||
@@ -565,6 +589,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterable:
|
||||
# invalid because it will implicitly be passed an `int`
|
||||
# by the interpreter
|
||||
@@ -604,6 +630,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> int:
|
||||
return 42
|
||||
@@ -639,6 +667,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterator1:
|
||||
if flag:
|
||||
@@ -678,6 +708,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def _(flag: bool):
|
||||
class Iterable1:
|
||||
if flag:
|
||||
@@ -709,6 +741,8 @@ def _(flag: bool):
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Iterator:
|
||||
def __next__(self) -> bytes:
|
||||
return b"foo"
|
||||
|
||||
@@ -241,6 +241,8 @@ find a union type in a class's bases, we infer the class's `__mro__` as being
|
||||
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
@@ -389,6 +391,8 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
||||
@@ -281,7 +281,7 @@ def _(x: Foo | Bar, flag: bool) -> None:
|
||||
The `TypeIs` type remains effective across generic boundaries:
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeVar
|
||||
from typing_extensions import TypeVar, reveal_type
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@@ -197,9 +197,9 @@ from typing_extensions import TypeAliasType, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
IntAndT = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
|
||||
def f(x: IntAndT[str]) -> None:
|
||||
def f(x: IntAnd[str]) -> None:
|
||||
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
|
||||
```
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol
|
||||
from typing_extensions import Protocol, reveal_type
|
||||
|
||||
# error: [call-non-callable]
|
||||
reveal_type(Protocol()) # revealed: Unknown
|
||||
@@ -381,7 +381,9 @@ And as a corollary, `type[MyProtocol]` can also be called:
|
||||
|
||||
```py
|
||||
def f(x: type[MyProtocol]):
|
||||
reveal_type(x()) # revealed: @Todo(type[T] for protocols)
|
||||
# TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||
# (which doesn't work well with snapshots)
|
||||
x()
|
||||
```
|
||||
|
||||
## Members of a protocol
|
||||
@@ -532,7 +534,7 @@ python-version = "3.9"
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing_extensions import Protocol, get_protocol_members
|
||||
from typing_extensions import Protocol, get_protocol_members, reveal_type
|
||||
|
||||
class Foo(Protocol):
|
||||
if sys.version_info >= (3, 10):
|
||||
@@ -615,10 +617,11 @@ static_assert(is_assignable_to(Foo, HasX))
|
||||
static_assert(not is_subtype_of(Foo, HasXY))
|
||||
static_assert(not is_assignable_to(Foo, HasXY))
|
||||
|
||||
static_assert(not is_subtype_of(HasXIntSub, HasX))
|
||||
static_assert(not is_assignable_to(HasXIntSub, HasX))
|
||||
static_assert(not is_subtype_of(HasX, HasXIntSub))
|
||||
static_assert(not is_assignable_to(HasX, HasXIntSub))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
|
||||
|
||||
class FooSub(Foo): ...
|
||||
|
||||
@@ -2288,9 +2291,10 @@ class MethodPUnrelated(Protocol):
|
||||
|
||||
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
|
||||
|
||||
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Subtyping between protocols with method members and protocols with non-method members
|
||||
@@ -2349,7 +2353,8 @@ And for the same reason, they are never assignable to attribute members (which a
|
||||
class Attribute(Protocol):
|
||||
f: Callable[[], bool]
|
||||
|
||||
static_assert(not is_assignable_to(Method, Attribute))
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
|
||||
@@ -2358,8 +2363,9 @@ this is not true for attribute members. The same principle also applies for prot
|
||||
members
|
||||
|
||||
```py
|
||||
static_assert(not is_assignable_to(PropertyBool, Method))
|
||||
static_assert(not is_assignable_to(Attribute, Method))
|
||||
# TODO: this should pass
|
||||
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
|
||||
@@ -2378,8 +2384,9 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
|
||||
class ClassVarAttributeBad(Protocol):
|
||||
f: ClassVar[Callable[[], str]]
|
||||
|
||||
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
|
||||
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
|
||||
# TODO: these should pass:
|
||||
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Narrowing of protocols
|
||||
@@ -2391,7 +2398,7 @@ By default, a protocol class cannot be used as the second argument to `isinstanc
|
||||
type inside these branches (this matches the behavior of other type checkers):
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol
|
||||
from typing_extensions import Protocol, reveal_type
|
||||
|
||||
class HasX(Protocol):
|
||||
x: int
|
||||
@@ -2700,8 +2707,9 @@ class RecursiveNonFullyStatic(Protocol):
|
||||
parent: RecursiveNonFullyStatic
|
||||
x: Any
|
||||
|
||||
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
|
||||
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
|
||||
# TODO: these should pass, once we take into account types of members
|
||||
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
|
||||
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
|
||||
|
||||
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
|
||||
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
|
||||
@@ -2719,7 +2727,9 @@ class RecursiveOptionalParent(Protocol):
|
||||
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
|
||||
|
||||
# Due to invariance of mutable attribute members, neither is assignable to the other
|
||||
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
|
||||
#
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
|
||||
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
|
||||
|
||||
class Other(Protocol):
|
||||
|
||||
@@ -339,7 +339,7 @@ class A: ...
|
||||
|
||||
def f(x: A):
|
||||
# TODO: no error
|
||||
# error: [invalid-assignment] "Object of type `mdtest_snippet.A @ src/mdtest_snippet.py:12 | mdtest_snippet.A @ src/mdtest_snippet.py:13` is not assignable to `mdtest_snippet.A @ src/mdtest_snippet.py:13`"
|
||||
# error: [invalid-assignment] "Object of type `mdtest_snippet.A | mdtest_snippet.A` is not assignable to `mdtest_snippet.A`"
|
||||
x = A()
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: all_members.md - List all members - Basic functionality
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from ty_extensions import static_assert, has_member
|
||||
2 |
|
||||
3 | static_assert(has_member("a", "replace"))
|
||||
4 | static_assert(has_member("a", "startswith"))
|
||||
5 | static_assert(has_member("a", "isupper"))
|
||||
6 | static_assert(has_member("a", "__add__"))
|
||||
7 | static_assert(has_member("a", "__gt__"))
|
||||
8 | static_assert(has_member("a", "__doc__"))
|
||||
9 | static_assert(has_member("a", "__repr__"))
|
||||
10 | static_assert(not has_member("a", "non_existent"))
|
||||
11 | from typing_extensions import reveal_type
|
||||
12 | from ty_extensions import all_members
|
||||
13 |
|
||||
14 | reveal_type(all_members("a")) # error: [revealed-type]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
12 | from ty_extensions import all_members
|
||||
13 |
|
||||
14 | reveal_type(all_members("a")) # error: [revealed-type]
|
||||
| ^^^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -12,27 +12,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class NotAsyncIterable: ...
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | async def foo():
|
||||
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
|
||||
5 | async for x in NotAsyncIterable():
|
||||
6 | reveal_type(x) # revealed: Unknown
|
||||
3 | class NotAsyncIterable: ...
|
||||
4 |
|
||||
5 | async def foo():
|
||||
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
|
||||
7 | async for x in NotAsyncIterable():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `NotAsyncIterable` is not async-iterable
|
||||
--> src/mdtest_snippet.py:5:20
|
||||
--> src/mdtest_snippet.py:7:20
|
||||
|
|
||||
3 | async def foo():
|
||||
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
|
||||
5 | async for x in NotAsyncIterable():
|
||||
5 | async def foo():
|
||||
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
|
||||
7 | async for x in NotAsyncIterable():
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
6 | reveal_type(x) # revealed: Unknown
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
info: It has no `__aiter__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:8:21
|
||||
|
|
||||
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
|
||||
7 | async for x in NotAsyncIterable():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,31 +12,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class NoAnext: ...
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class AsyncIterable:
|
||||
4 | def __aiter__(self) -> NoAnext:
|
||||
5 | return NoAnext()
|
||||
6 |
|
||||
7 | async def foo():
|
||||
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
9 | async for x in AsyncIterable():
|
||||
10 | reveal_type(x) # revealed: Unknown
|
||||
3 | class NoAnext: ...
|
||||
4 |
|
||||
5 | class AsyncIterable:
|
||||
6 | def __aiter__(self) -> NoAnext:
|
||||
7 | return NoAnext()
|
||||
8 |
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
12 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
|
||||
--> src/mdtest_snippet.py:9:20
|
||||
--> src/mdtest_snippet.py:11:20
|
||||
|
|
||||
7 | async def foo():
|
||||
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
9 | async for x in AsyncIterable():
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
| ^^^^^^^^^^^^^^^
|
||||
10 | reveal_type(x) # revealed: Unknown
|
||||
12 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
info: Its `__aiter__` method returns an object of type `NoAnext`, which has no `__anext__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:12:21
|
||||
|
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
12 | reveal_type(x) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | async def foo(flag: bool):
|
||||
2 | class AsyncIterable:
|
||||
3 | async def __anext__(self) -> int:
|
||||
4 | return 42
|
||||
5 |
|
||||
6 | class PossiblyUnboundAiter:
|
||||
7 | if flag:
|
||||
8 | def __aiter__(self) -> AsyncIterable:
|
||||
9 | return AsyncIterable()
|
||||
10 |
|
||||
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
|
||||
12 | async for x in PossiblyUnboundAiter():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | async def foo(flag: bool):
|
||||
4 | class AsyncIterable:
|
||||
5 | async def __anext__(self) -> int:
|
||||
6 | return 42
|
||||
7 |
|
||||
8 | class PossiblyUnboundAiter:
|
||||
9 | if flag:
|
||||
10 | def __aiter__(self) -> AsyncIterable:
|
||||
11 | return AsyncIterable()
|
||||
12 |
|
||||
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
|
||||
14 | async for x in PossiblyUnboundAiter():
|
||||
15 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `PossiblyUnboundAiter` may not be async-iterable
|
||||
--> src/mdtest_snippet.py:12:20
|
||||
--> src/mdtest_snippet.py:14:20
|
||||
|
|
||||
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
|
||||
12 | async for x in PossiblyUnboundAiter():
|
||||
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
|
||||
14 | async for x in PossiblyUnboundAiter():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
13 | reveal_type(x) # revealed: int
|
||||
15 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__aiter__` attribute (with type `bound method PossiblyUnboundAiter.__aiter__() -> AsyncIterable`) may not be callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:15:21
|
||||
|
|
||||
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
|
||||
14 | async for x in PossiblyUnboundAiter():
|
||||
15 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | async def foo(flag: bool):
|
||||
2 | class PossiblyUnboundAnext:
|
||||
3 | if flag:
|
||||
4 | async def __anext__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class AsyncIterable:
|
||||
8 | def __aiter__(self) -> PossiblyUnboundAnext:
|
||||
9 | return PossiblyUnboundAnext()
|
||||
10 |
|
||||
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
|
||||
12 | async for x in AsyncIterable():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | async def foo(flag: bool):
|
||||
4 | class PossiblyUnboundAnext:
|
||||
5 | if flag:
|
||||
6 | async def __anext__(self) -> int:
|
||||
7 | return 42
|
||||
8 |
|
||||
9 | class AsyncIterable:
|
||||
10 | def __aiter__(self) -> PossiblyUnboundAnext:
|
||||
11 | return PossiblyUnboundAnext()
|
||||
12 |
|
||||
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
|
||||
14 | async for x in AsyncIterable():
|
||||
15 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `AsyncIterable` may not be async-iterable
|
||||
--> src/mdtest_snippet.py:12:20
|
||||
--> src/mdtest_snippet.py:14:20
|
||||
|
|
||||
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
|
||||
12 | async for x in AsyncIterable():
|
||||
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
|
||||
14 | async for x in AsyncIterable():
|
||||
| ^^^^^^^^^^^^^^^
|
||||
13 | reveal_type(x) # revealed: int
|
||||
15 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__aiter__` method returns an object of type `PossiblyUnboundAnext`, which may not have a `__anext__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:15:21
|
||||
|
|
||||
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
|
||||
14 | async for x in AsyncIterable():
|
||||
15 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,32 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | async def foo():
|
||||
2 | class Iterator:
|
||||
3 | def __next__(self) -> int:
|
||||
4 | return 42
|
||||
5 |
|
||||
6 | class Iterable:
|
||||
7 | def __iter__(self) -> Iterator:
|
||||
8 | return Iterator()
|
||||
9 |
|
||||
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
|
||||
11 | async for x in Iterator():
|
||||
12 | reveal_type(x) # revealed: Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | async def foo():
|
||||
4 | class Iterator:
|
||||
5 | def __next__(self) -> int:
|
||||
6 | return 42
|
||||
7 |
|
||||
8 | class Iterable:
|
||||
9 | def __iter__(self) -> Iterator:
|
||||
10 | return Iterator()
|
||||
11 |
|
||||
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
|
||||
13 | async for x in Iterator():
|
||||
14 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterator` is not async-iterable
|
||||
--> src/mdtest_snippet.py:11:20
|
||||
--> src/mdtest_snippet.py:13:20
|
||||
|
|
||||
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
|
||||
11 | async for x in Iterator():
|
||||
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
|
||||
13 | async for x in Iterator():
|
||||
| ^^^^^^^^^^
|
||||
12 | reveal_type(x) # revealed: Unknown
|
||||
14 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
info: It has no `__aiter__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:21
|
||||
|
|
||||
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
|
||||
13 | async for x in Iterator():
|
||||
14 | reveal_type(x) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,34 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class AsyncIterator:
|
||||
2 | async def __anext__(self, arg: int) -> int: # wrong
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class AsyncIterable:
|
||||
6 | def __aiter__(self) -> AsyncIterator:
|
||||
7 | return AsyncIterator()
|
||||
8 |
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
12 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class AsyncIterator:
|
||||
4 | async def __anext__(self, arg: int) -> int: # wrong
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class AsyncIterable:
|
||||
8 | def __aiter__(self) -> AsyncIterator:
|
||||
9 | return AsyncIterator()
|
||||
10 |
|
||||
11 | async def foo():
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
14 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
|
||||
--> src/mdtest_snippet.py:11:20
|
||||
--> src/mdtest_snippet.py:13:20
|
||||
|
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
11 | async def foo():
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
| ^^^^^^^^^^^^^^^
|
||||
12 | reveal_type(x) # revealed: int
|
||||
14 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__aiter__` method returns an object of type `AsyncIterator`, which has an invalid `__anext__` method
|
||||
info: Expected signature for `__anext__` is `def __anext__(self): ...`
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:21
|
||||
|
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
14 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,34 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class AsyncIterator:
|
||||
2 | async def __anext__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class AsyncIterable:
|
||||
6 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
|
||||
7 | return AsyncIterator()
|
||||
8 |
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
12 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class AsyncIterator:
|
||||
4 | async def __anext__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class AsyncIterable:
|
||||
8 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
|
||||
9 | return AsyncIterator()
|
||||
10 |
|
||||
11 | async def foo():
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
14 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
|
||||
--> src/mdtest_snippet.py:11:20
|
||||
--> src/mdtest_snippet.py:13:20
|
||||
|
|
||||
9 | async def foo():
|
||||
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
11 | async for x in AsyncIterable():
|
||||
11 | async def foo():
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
| ^^^^^^^^^^^^^^^
|
||||
12 | reveal_type(x) # revealed: int
|
||||
14 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__aiter__` method has an invalid signature
|
||||
info: Expected signature `def __aiter__(self): ...`
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:21
|
||||
|
|
||||
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
|
||||
13 | async for x in AsyncIterable():
|
||||
14 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -13,71 +13,86 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass, field, KW_ONLY
|
||||
2 |
|
||||
3 | @dataclass
|
||||
4 | class C:
|
||||
5 | x: int
|
||||
6 | _: KW_ONLY
|
||||
7 | y: str
|
||||
8 |
|
||||
9 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
|
||||
10 |
|
||||
11 | # error: [missing-argument]
|
||||
12 | # error: [too-many-positional-arguments]
|
||||
13 | C(3, "")
|
||||
14 |
|
||||
15 | C(3, y="")
|
||||
16 | @dataclass
|
||||
17 | class Fails: # error: [duplicate-kw-only]
|
||||
18 | a: int
|
||||
19 | b: KW_ONLY
|
||||
20 | c: str
|
||||
21 | d: KW_ONLY
|
||||
22 | e: bytes
|
||||
23 |
|
||||
24 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||
25 | def flag() -> bool:
|
||||
26 | return True
|
||||
27 |
|
||||
28 | @dataclass
|
||||
29 | class D: # error: [duplicate-kw-only]
|
||||
30 | x: int
|
||||
31 | _1: KW_ONLY
|
||||
32 |
|
||||
33 | if flag():
|
||||
34 | y: str
|
||||
35 | _2: KW_ONLY
|
||||
36 | z: float
|
||||
37 | from dataclasses import dataclass, KW_ONLY
|
||||
38 |
|
||||
39 | @dataclass
|
||||
40 | class D:
|
||||
41 | x: int
|
||||
42 | _: KW_ONLY
|
||||
43 | y: str
|
||||
44 |
|
||||
45 | @dataclass
|
||||
46 | class E(D):
|
||||
47 | z: bytes
|
||||
48 |
|
||||
49 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
|
||||
50 | E(1, b"foo", y="foo")
|
||||
51 |
|
||||
52 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 |
|
||||
4 | @dataclass
|
||||
5 | class C:
|
||||
6 | x: int
|
||||
7 | _: KW_ONLY
|
||||
8 | y: str
|
||||
9 |
|
||||
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
|
||||
11 |
|
||||
12 | # error: [missing-argument]
|
||||
13 | # error: [too-many-positional-arguments]
|
||||
14 | C(3, "")
|
||||
15 |
|
||||
16 | C(3, y="")
|
||||
17 | @dataclass
|
||||
18 | class Fails: # error: [duplicate-kw-only]
|
||||
19 | a: int
|
||||
20 | b: KW_ONLY
|
||||
21 | c: str
|
||||
22 | d: KW_ONLY
|
||||
23 | e: bytes
|
||||
24 |
|
||||
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||
26 | def flag() -> bool:
|
||||
27 | return True
|
||||
28 |
|
||||
29 | @dataclass
|
||||
30 | class D: # error: [duplicate-kw-only]
|
||||
31 | x: int
|
||||
32 | _1: KW_ONLY
|
||||
33 |
|
||||
34 | if flag():
|
||||
35 | y: str
|
||||
36 | _2: KW_ONLY
|
||||
37 | z: float
|
||||
38 | from dataclasses import dataclass, KW_ONLY
|
||||
39 |
|
||||
40 | @dataclass
|
||||
41 | class D:
|
||||
42 | x: int
|
||||
43 | _: KW_ONLY
|
||||
44 | y: str
|
||||
45 |
|
||||
46 | @dataclass
|
||||
47 | class E(D):
|
||||
48 | z: bytes
|
||||
49 |
|
||||
50 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
|
||||
51 | E(1, b"foo", y="foo")
|
||||
52 |
|
||||
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[missing-argument]: No argument provided for required parameter `y`
|
||||
--> src/mdtest_snippet.py:13:1
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
|
|
||||
11 | # error: [missing-argument]
|
||||
12 | # error: [too-many-positional-arguments]
|
||||
13 | C(3, "")
|
||||
8 | y: str
|
||||
9 |
|
||||
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
|
||||
| ^^^^^^^^^^ `(self: C, x: int, *, y: str) -> None`
|
||||
11 |
|
||||
12 | # error: [missing-argument]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[missing-argument]: No argument provided for required parameter `y`
|
||||
--> src/mdtest_snippet.py:14:1
|
||||
|
|
||||
12 | # error: [missing-argument]
|
||||
13 | # error: [too-many-positional-arguments]
|
||||
14 | C(3, "")
|
||||
| ^^^^^^^^
|
||||
14 |
|
||||
15 | C(3, y="")
|
||||
15 |
|
||||
16 | C(3, y="")
|
||||
|
|
||||
info: rule `missing-argument` is enabled by default
|
||||
|
||||
@@ -85,14 +100,14 @@ info: rule `missing-argument` is enabled by default
|
||||
|
||||
```
|
||||
error[too-many-positional-arguments]: Too many positional arguments: expected 1, got 2
|
||||
--> src/mdtest_snippet.py:13:6
|
||||
--> src/mdtest_snippet.py:14:6
|
||||
|
|
||||
11 | # error: [missing-argument]
|
||||
12 | # error: [too-many-positional-arguments]
|
||||
13 | C(3, "")
|
||||
12 | # error: [missing-argument]
|
||||
13 | # error: [too-many-positional-arguments]
|
||||
14 | C(3, "")
|
||||
| ^^
|
||||
14 |
|
||||
15 | C(3, y="")
|
||||
15 |
|
||||
16 | C(3, y="")
|
||||
|
|
||||
info: rule `too-many-positional-arguments` is enabled by default
|
||||
|
||||
@@ -100,14 +115,14 @@ info: rule `too-many-positional-arguments` is enabled by default
|
||||
|
||||
```
|
||||
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
|
||||
--> src/mdtest_snippet.py:17:7
|
||||
--> src/mdtest_snippet.py:18:7
|
||||
|
|
||||
15 | C(3, y="")
|
||||
16 | @dataclass
|
||||
17 | class Fails: # error: [duplicate-kw-only]
|
||||
16 | C(3, y="")
|
||||
17 | @dataclass
|
||||
18 | class Fails: # error: [duplicate-kw-only]
|
||||
| ^^^^^
|
||||
18 | a: int
|
||||
19 | b: KW_ONLY
|
||||
19 | a: int
|
||||
20 | b: KW_ONLY
|
||||
|
|
||||
info: `KW_ONLY` fields: `b`, `d`
|
||||
info: rule `duplicate-kw-only` is enabled by default
|
||||
@@ -115,16 +130,42 @@ info: rule `duplicate-kw-only` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
|
||||
--> src/mdtest_snippet.py:29:7
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:25:13
|
||||
|
|
||||
28 | @dataclass
|
||||
29 | class D: # error: [duplicate-kw-only]
|
||||
23 | e: bytes
|
||||
24 |
|
||||
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
|
||||
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
|
||||
26 | def flag() -> bool:
|
||||
27 | return True
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
|
||||
--> src/mdtest_snippet.py:30:7
|
||||
|
|
||||
29 | @dataclass
|
||||
30 | class D: # error: [duplicate-kw-only]
|
||||
| ^
|
||||
30 | x: int
|
||||
31 | _1: KW_ONLY
|
||||
31 | x: int
|
||||
32 | _1: KW_ONLY
|
||||
|
|
||||
info: `KW_ONLY` fields: `_1`, `_2`
|
||||
info: rule `duplicate-kw-only` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:53:13
|
||||
|
|
||||
51 | E(1, b"foo", y="foo")
|
||||
52 |
|
||||
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
|
||||
| ^^^^^^^^^^ `(self: E, x: int, z: bytes, *, y: str) -> None`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,30 +12,44 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterable:
|
||||
2 | # invalid because it will implicitly be passed an `int`
|
||||
3 | # by the interpreter
|
||||
4 | def __getitem__(self, key: str) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Iterable():
|
||||
9 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterable:
|
||||
4 | # invalid because it will implicitly be passed an `int`
|
||||
5 | # by the interpreter
|
||||
6 | def __getitem__(self, key: str) -> int:
|
||||
7 | return 42
|
||||
8 |
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable` is not iterable
|
||||
--> src/mdtest_snippet.py:8:10
|
||||
|
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Iterable():
|
||||
| ^^^^^^^^^^
|
||||
9 | reveal_type(x) # revealed: int
|
||||
|
|
||||
--> src/mdtest_snippet.py:10:10
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
| ^^^^^^^^^^
|
||||
11 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
|
||||
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:11:17
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,26 +12,40 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Bad:
|
||||
2 | __getitem__: None = None
|
||||
3 |
|
||||
4 | # error: [not-iterable]
|
||||
5 | for x in Bad():
|
||||
6 | reveal_type(x) # revealed: Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Bad:
|
||||
4 | __getitem__: None = None
|
||||
5 |
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Bad` is not iterable
|
||||
--> src/mdtest_snippet.py:5:10
|
||||
--> src/mdtest_snippet.py:7:10
|
||||
|
|
||||
4 | # error: [not-iterable]
|
||||
5 | for x in Bad():
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
| ^^^^^
|
||||
6 | reveal_type(x) # revealed: Unknown
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:8:17
|
||||
|
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,46 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool):
|
||||
2 | class CustomCallable:
|
||||
3 | if flag:
|
||||
4 | def __call__(self, *args, **kwargs) -> int:
|
||||
5 | return 42
|
||||
6 | else:
|
||||
7 | __call__: None = None
|
||||
8 |
|
||||
9 | class Iterable1:
|
||||
10 | __getitem__: CustomCallable = CustomCallable()
|
||||
11 |
|
||||
12 | class Iterable2:
|
||||
13 | if flag:
|
||||
14 | def __getitem__(self, key: int) -> int:
|
||||
15 | return 42
|
||||
16 | else:
|
||||
17 | __getitem__: None = None
|
||||
18 |
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
21 | # TODO... `int` might be ideal here?
|
||||
22 | reveal_type(x) # revealed: int | Unknown
|
||||
23 |
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
26 | # TODO... `int` might be ideal here?
|
||||
27 | reveal_type(y) # revealed: int | Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class CustomCallable:
|
||||
5 | if flag:
|
||||
6 | def __call__(self, *args, **kwargs) -> int:
|
||||
7 | return 42
|
||||
8 | else:
|
||||
9 | __call__: None = None
|
||||
10 |
|
||||
11 | class Iterable1:
|
||||
12 | __getitem__: CustomCallable = CustomCallable()
|
||||
13 |
|
||||
14 | class Iterable2:
|
||||
15 | if flag:
|
||||
16 | def __getitem__(self, key: int) -> int:
|
||||
17 | return 42
|
||||
18 | else:
|
||||
19 | __getitem__: None = None
|
||||
20 |
|
||||
21 | # error: [not-iterable]
|
||||
22 | for x in Iterable1():
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
25 |
|
||||
26 | # error: [not-iterable]
|
||||
27 | for y in Iterable2():
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` may not be iterable
|
||||
--> src/mdtest_snippet.py:20:14
|
||||
--> src/mdtest_snippet.py:22:14
|
||||
|
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
21 | # error: [not-iterable]
|
||||
22 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
21 | # TODO... `int` might be ideal here?
|
||||
22 | reveal_type(x) # revealed: int | Unknown
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
|
||||
info: `__getitem__` has type `CustomCallable`, which is not callable
|
||||
@@ -60,17 +62,43 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:25:14
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:24:21
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
22 | for x in Iterable1():
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
| ^ `int | Unknown`
|
||||
25 |
|
||||
26 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:27:14
|
||||
|
|
||||
26 | # error: [not-iterable]
|
||||
27 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
26 | # TODO... `int` might be ideal here?
|
||||
27 | reveal_type(y) # revealed: int | Unknown
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
|
||||
info: `__getitem__` has type `(bound method Iterable2.__getitem__(key: int) -> int) | None`, which is not callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:29:21
|
||||
|
|
||||
27 | for y in Iterable2():
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
| ^ `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,46 +12,48 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterator:
|
||||
2 | def __next__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | def _(flag: bool):
|
||||
6 | class Iterable1:
|
||||
7 | if flag:
|
||||
8 | def __iter__(self) -> Iterator:
|
||||
9 | return Iterator()
|
||||
10 | else:
|
||||
11 | def __iter__(self, invalid_extra_arg) -> Iterator:
|
||||
12 | return Iterator()
|
||||
13 |
|
||||
14 | # error: [not-iterable]
|
||||
15 | for x in Iterable1():
|
||||
16 | reveal_type(x) # revealed: int
|
||||
17 |
|
||||
18 | class Iterable2:
|
||||
19 | if flag:
|
||||
20 | def __iter__(self) -> Iterator:
|
||||
21 | return Iterator()
|
||||
22 | else:
|
||||
23 | __iter__: None = None
|
||||
24 |
|
||||
25 | # error: [not-iterable]
|
||||
26 | for x in Iterable2():
|
||||
27 | # TODO: `int` would probably be better here:
|
||||
28 | reveal_type(x) # revealed: int | Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | def _(flag: bool):
|
||||
8 | class Iterable1:
|
||||
9 | if flag:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | else:
|
||||
13 | def __iter__(self, invalid_extra_arg) -> Iterator:
|
||||
14 | return Iterator()
|
||||
15 |
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | reveal_type(x) # revealed: int
|
||||
19 |
|
||||
20 | class Iterable2:
|
||||
21 | if flag:
|
||||
22 | def __iter__(self) -> Iterator:
|
||||
23 | return Iterator()
|
||||
24 | else:
|
||||
25 | __iter__: None = None
|
||||
26 |
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable2():
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` may not be iterable
|
||||
--> src/mdtest_snippet.py:15:14
|
||||
--> src/mdtest_snippet.py:17:14
|
||||
|
|
||||
14 | # error: [not-iterable]
|
||||
15 | for x in Iterable1():
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
16 | reveal_type(x) # revealed: int
|
||||
18 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__iter__` method may have an invalid signature
|
||||
info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`
|
||||
@@ -61,16 +63,42 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:26:14
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:18:21
|
||||
|
|
||||
25 | # error: [not-iterable]
|
||||
26 | for x in Iterable2():
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
19 |
|
||||
20 | class Iterable2:
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:28:14
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
27 | # TODO: `int` would probably be better here:
|
||||
28 | reveal_type(x) # revealed: int | Unknown
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:30:21
|
||||
|
|
||||
28 | for x in Iterable2():
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
| ^ `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,43 +12,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool):
|
||||
2 | class Iterable1:
|
||||
3 | if flag:
|
||||
4 | def __getitem__(self, item: int) -> str:
|
||||
5 | return "foo"
|
||||
6 | else:
|
||||
7 | __getitem__: None = None
|
||||
8 |
|
||||
9 | class Iterable2:
|
||||
10 | if flag:
|
||||
11 | def __getitem__(self, item: int) -> str:
|
||||
12 | return "foo"
|
||||
13 | else:
|
||||
14 | def __getitem__(self, item: str) -> int:
|
||||
15 | return 42
|
||||
16 |
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable1():
|
||||
19 | # TODO: `str` might be better
|
||||
20 | reveal_type(x) # revealed: str | Unknown
|
||||
21 |
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
24 | reveal_type(y) # revealed: str | int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterable1:
|
||||
5 | if flag:
|
||||
6 | def __getitem__(self, item: int) -> str:
|
||||
7 | return "foo"
|
||||
8 | else:
|
||||
9 | __getitem__: None = None
|
||||
10 |
|
||||
11 | class Iterable2:
|
||||
12 | if flag:
|
||||
13 | def __getitem__(self, item: int) -> str:
|
||||
14 | return "foo"
|
||||
15 | else:
|
||||
16 | def __getitem__(self, item: str) -> int:
|
||||
17 | return 42
|
||||
18 |
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
23 |
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` may not be iterable
|
||||
--> src/mdtest_snippet.py:18:14
|
||||
--> src/mdtest_snippet.py:20:14
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable1():
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
19 | # TODO: `str` might be better
|
||||
20 | reveal_type(x) # revealed: str | Unknown
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
|
||||
info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable
|
||||
@@ -57,16 +59,42 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:23:14
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:22:21
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
20 | for x in Iterable1():
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
| ^ `str | Unknown`
|
||||
23 |
|
||||
24 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:25:14
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
24 | reveal_type(y) # revealed: str | int
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
|
|
||||
info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
|
||||
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:26:21
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
| ^ `str | int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,50 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool):
|
||||
2 | class Iterator1:
|
||||
3 | if flag:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 | else:
|
||||
7 | def __next__(self, invalid_extra_arg) -> str:
|
||||
8 | return "foo"
|
||||
9 |
|
||||
10 | class Iterator2:
|
||||
11 | if flag:
|
||||
12 | def __next__(self) -> int:
|
||||
13 | return 42
|
||||
14 | else:
|
||||
15 | __next__: None = None
|
||||
16 |
|
||||
17 | class Iterable1:
|
||||
18 | def __iter__(self) -> Iterator1:
|
||||
19 | return Iterator1()
|
||||
20 |
|
||||
21 | class Iterable2:
|
||||
22 | def __iter__(self) -> Iterator2:
|
||||
23 | return Iterator2()
|
||||
24 |
|
||||
25 | # error: [not-iterable]
|
||||
26 | for x in Iterable1():
|
||||
27 | reveal_type(x) # revealed: int | str
|
||||
28 |
|
||||
29 | # error: [not-iterable]
|
||||
30 | for y in Iterable2():
|
||||
31 | # TODO: `int` would probably be better here:
|
||||
32 | reveal_type(y) # revealed: int | Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterator1:
|
||||
5 | if flag:
|
||||
6 | def __next__(self) -> int:
|
||||
7 | return 42
|
||||
8 | else:
|
||||
9 | def __next__(self, invalid_extra_arg) -> str:
|
||||
10 | return "foo"
|
||||
11 |
|
||||
12 | class Iterator2:
|
||||
13 | if flag:
|
||||
14 | def __next__(self) -> int:
|
||||
15 | return 42
|
||||
16 | else:
|
||||
17 | __next__: None = None
|
||||
18 |
|
||||
19 | class Iterable1:
|
||||
20 | def __iter__(self) -> Iterator1:
|
||||
21 | return Iterator1()
|
||||
22 |
|
||||
23 | class Iterable2:
|
||||
24 | def __iter__(self) -> Iterator2:
|
||||
25 | return Iterator2()
|
||||
26 |
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
30 |
|
||||
31 | # error: [not-iterable]
|
||||
32 | for y in Iterable2():
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` may not be iterable
|
||||
--> src/mdtest_snippet.py:26:14
|
||||
--> src/mdtest_snippet.py:28:14
|
||||
|
|
||||
25 | # error: [not-iterable]
|
||||
26 | for x in Iterable1():
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
27 | reveal_type(x) # revealed: int | str
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method
|
||||
info: Expected signature for `__next__` is `def __next__(self): ...`
|
||||
@@ -64,16 +66,42 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:30:14
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:29:21
|
||||
|
|
||||
29 | # error: [not-iterable]
|
||||
30 | for y in Iterable2():
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
| ^ `int | str`
|
||||
30 |
|
||||
31 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:32:14
|
||||
|
|
||||
31 | # error: [not-iterable]
|
||||
32 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
31 | # TODO: `int` would probably be better here:
|
||||
32 | reveal_type(y) # revealed: int | Unknown
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:34:21
|
||||
|
|
||||
32 | for y in Iterable2():
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
| ^ `int | Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,54 +12,56 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterator:
|
||||
2 | def __next__(self) -> bytes:
|
||||
3 | return b"foo"
|
||||
4 |
|
||||
5 | def _(flag: bool, flag2: bool):
|
||||
6 | class Iterable1:
|
||||
7 | if flag:
|
||||
8 | def __getitem__(self, item: int) -> str:
|
||||
9 | return "foo"
|
||||
10 | else:
|
||||
11 | __getitem__: None = None
|
||||
12 |
|
||||
13 | if flag2:
|
||||
14 | def __iter__(self) -> Iterator:
|
||||
15 | return Iterator()
|
||||
16 |
|
||||
17 | class Iterable2:
|
||||
18 | if flag:
|
||||
19 | def __getitem__(self, item: int) -> str:
|
||||
20 | return "foo"
|
||||
21 | else:
|
||||
22 | def __getitem__(self, item: str) -> int:
|
||||
23 | return 42
|
||||
24 | if flag2:
|
||||
25 | def __iter__(self) -> Iterator:
|
||||
26 | return Iterator()
|
||||
27 |
|
||||
28 | # error: [not-iterable]
|
||||
29 | for x in Iterable1():
|
||||
30 | # TODO: `bytes | str` might be better
|
||||
31 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
32 |
|
||||
33 | # error: [not-iterable]
|
||||
34 | for y in Iterable2():
|
||||
35 | reveal_type(y) # revealed: bytes | str | int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> bytes:
|
||||
5 | return b"foo"
|
||||
6 |
|
||||
7 | def _(flag: bool, flag2: bool):
|
||||
8 | class Iterable1:
|
||||
9 | if flag:
|
||||
10 | def __getitem__(self, item: int) -> str:
|
||||
11 | return "foo"
|
||||
12 | else:
|
||||
13 | __getitem__: None = None
|
||||
14 |
|
||||
15 | if flag2:
|
||||
16 | def __iter__(self) -> Iterator:
|
||||
17 | return Iterator()
|
||||
18 |
|
||||
19 | class Iterable2:
|
||||
20 | if flag:
|
||||
21 | def __getitem__(self, item: int) -> str:
|
||||
22 | return "foo"
|
||||
23 | else:
|
||||
24 | def __getitem__(self, item: str) -> int:
|
||||
25 | return 42
|
||||
26 | if flag2:
|
||||
27 | def __iter__(self) -> Iterator:
|
||||
28 | return Iterator()
|
||||
29 |
|
||||
30 | # error: [not-iterable]
|
||||
31 | for x in Iterable1():
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
34 |
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` may not be iterable
|
||||
--> src/mdtest_snippet.py:29:14
|
||||
--> src/mdtest_snippet.py:31:14
|
||||
|
|
||||
28 | # error: [not-iterable]
|
||||
29 | for x in Iterable1():
|
||||
30 | # error: [not-iterable]
|
||||
31 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
30 | # TODO: `bytes | str` might be better
|
||||
31 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
|
|
||||
info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
@@ -67,16 +69,42 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:34:14
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:33:21
|
||||
|
|
||||
33 | # error: [not-iterable]
|
||||
34 | for y in Iterable2():
|
||||
31 | for x in Iterable1():
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
| ^ `bytes | str | Unknown`
|
||||
34 |
|
||||
35 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` may not be iterable
|
||||
--> src/mdtest_snippet.py:36:14
|
||||
|
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
35 | reveal_type(y) # revealed: bytes | str | int
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
|
|
||||
info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
|
||||
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:37:21
|
||||
|
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
| ^ `bytes | str | int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,38 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool):
|
||||
2 | class Iterator:
|
||||
3 | def __next__(self) -> int:
|
||||
4 | return 42
|
||||
5 |
|
||||
6 | class Iterable:
|
||||
7 | if flag:
|
||||
8 | def __iter__(self) -> Iterator:
|
||||
9 | return Iterator()
|
||||
10 | # invalid signature because it only accepts a `str`,
|
||||
11 | # but the old-style iteration protocol will pass it an `int`
|
||||
12 | def __getitem__(self, key: str) -> bytes:
|
||||
13 | return bytes()
|
||||
14 |
|
||||
15 | # error: [not-iterable]
|
||||
16 | for x in Iterable():
|
||||
17 | reveal_type(x) # revealed: int | bytes
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class Iterator:
|
||||
5 | def __next__(self) -> int:
|
||||
6 | return 42
|
||||
7 |
|
||||
8 | class Iterable:
|
||||
9 | if flag:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | # invalid signature because it only accepts a `str`,
|
||||
13 | # but the old-style iteration protocol will pass it an `int`
|
||||
14 | def __getitem__(self, key: str) -> bytes:
|
||||
15 | return bytes()
|
||||
16 |
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable` may not be iterable
|
||||
--> src/mdtest_snippet.py:16:14
|
||||
--> src/mdtest_snippet.py:18:14
|
||||
|
|
||||
15 | # error: [not-iterable]
|
||||
16 | for x in Iterable():
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
| ^^^^^^^^^^
|
||||
17 | reveal_type(x) # revealed: int | bytes
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
|
||||
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:21
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
| ^ `int | bytes`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,36 +12,50 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterator:
|
||||
2 | def __next__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | def _(flag1: bool, flag2: bool):
|
||||
6 | class Iterable:
|
||||
7 | if flag1:
|
||||
8 | def __iter__(self) -> Iterator:
|
||||
9 | return Iterator()
|
||||
10 | if flag2:
|
||||
11 | def __getitem__(self, key: int) -> bytes:
|
||||
12 | return bytes()
|
||||
13 |
|
||||
14 | # error: [not-iterable]
|
||||
15 | for x in Iterable():
|
||||
16 | reveal_type(x) # revealed: int | bytes
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | def _(flag1: bool, flag2: bool):
|
||||
8 | class Iterable:
|
||||
9 | if flag1:
|
||||
10 | def __iter__(self) -> Iterator:
|
||||
11 | return Iterator()
|
||||
12 | if flag2:
|
||||
13 | def __getitem__(self, key: int) -> bytes:
|
||||
14 | return bytes()
|
||||
15 |
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable` may not be iterable
|
||||
--> src/mdtest_snippet.py:15:14
|
||||
--> src/mdtest_snippet.py:17:14
|
||||
|
|
||||
14 | # error: [not-iterable]
|
||||
15 | for x in Iterable():
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
| ^^^^^^^^^^
|
||||
16 | reveal_type(x) # revealed: int | bytes
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
info: It may not have an `__iter__` method or a `__getitem__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:18:21
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
| ^ `int | bytes`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,38 +12,52 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class TestIter:
|
||||
2 | def __next__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class Test:
|
||||
6 | def __iter__(self) -> TestIter:
|
||||
7 | return TestIter()
|
||||
8 |
|
||||
9 | class Test2:
|
||||
10 | def __iter__(self) -> int:
|
||||
11 | return 42
|
||||
12 |
|
||||
13 | def _(flag: bool):
|
||||
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
15 | # error: [not-iterable]
|
||||
16 | for x in Test() if flag else Test2():
|
||||
17 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class TestIter:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Test:
|
||||
8 | def __iter__(self) -> TestIter:
|
||||
9 | return TestIter()
|
||||
10 |
|
||||
11 | class Test2:
|
||||
12 | def __iter__(self) -> int:
|
||||
13 | return 42
|
||||
14 |
|
||||
15 | def _(flag: bool):
|
||||
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
19 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Test | Test2` may not be iterable
|
||||
--> src/mdtest_snippet.py:16:14
|
||||
--> src/mdtest_snippet.py:18:14
|
||||
|
|
||||
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
15 | # error: [not-iterable]
|
||||
16 | for x in Test() if flag else Test2():
|
||||
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
17 | reveal_type(x) # revealed: int
|
||||
19 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:21
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
19 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,33 +12,47 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class TestIter:
|
||||
2 | def __next__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class Test:
|
||||
6 | def __iter__(self) -> TestIter:
|
||||
7 | return TestIter()
|
||||
8 |
|
||||
9 | def _(flag: bool):
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in Test() if flag else 42:
|
||||
12 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class TestIter:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Test:
|
||||
8 | def __iter__(self) -> TestIter:
|
||||
9 | return TestIter()
|
||||
10 |
|
||||
11 | def _(flag: bool):
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
14 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable
|
||||
--> src/mdtest_snippet.py:11:14
|
||||
--> src/mdtest_snippet.py:13:14
|
||||
|
|
||||
9 | def _(flag: bool):
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in Test() if flag else 42:
|
||||
11 | def _(flag: bool):
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
12 | reveal_type(x) # revealed: int
|
||||
14 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:21
|
||||
|
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
14 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,32 +12,34 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def _(flag: bool):
|
||||
2 | class NotIterable:
|
||||
3 | if flag:
|
||||
4 | __iter__: int = 1
|
||||
5 | else:
|
||||
6 | __iter__: None = None
|
||||
7 |
|
||||
8 | # error: [not-iterable]
|
||||
9 | for x in NotIterable():
|
||||
10 | pass
|
||||
11 |
|
||||
12 | # revealed: Unknown
|
||||
13 | # error: [possibly-unresolved-reference]
|
||||
14 | reveal_type(x)
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def _(flag: bool):
|
||||
4 | class NotIterable:
|
||||
5 | if flag:
|
||||
6 | __iter__: int = 1
|
||||
7 | else:
|
||||
8 | __iter__: None = None
|
||||
9 |
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in NotIterable():
|
||||
12 | pass
|
||||
13 |
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `NotIterable` is not iterable
|
||||
--> src/mdtest_snippet.py:9:14
|
||||
--> src/mdtest_snippet.py:11:14
|
||||
|
|
||||
8 | # error: [not-iterable]
|
||||
9 | for x in NotIterable():
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in NotIterable():
|
||||
| ^^^^^^^^^^^^^
|
||||
10 | pass
|
||||
12 | pass
|
||||
|
|
||||
info: Its `__iter__` attribute has type `int | None`, which is not callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
@@ -46,13 +48,25 @@ info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
info[possibly-unresolved-reference]: Name `x` used when possibly not defined
|
||||
--> src/mdtest_snippet.py:14:17
|
||||
--> src/mdtest_snippet.py:16:17
|
||||
|
|
||||
12 | # revealed: Unknown
|
||||
13 | # error: [possibly-unresolved-reference]
|
||||
14 | reveal_type(x)
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
| ^
|
||||
|
|
||||
info: rule `possibly-unresolved-reference` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:16:17
|
||||
|
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,27 +12,41 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Bad:
|
||||
2 | def __iter__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | # error: [not-iterable]
|
||||
6 | for x in Bad():
|
||||
7 | reveal_type(x) # revealed: Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Bad:
|
||||
4 | def __iter__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Bad` is not iterable
|
||||
--> src/mdtest_snippet.py:6:10
|
||||
--> src/mdtest_snippet.py:8:10
|
||||
|
|
||||
5 | # error: [not-iterable]
|
||||
6 | for x in Bad():
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
| ^^^^^
|
||||
7 | reveal_type(x) # revealed: Unknown
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:9:17
|
||||
|
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,32 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterator:
|
||||
2 | def __next__(self) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class Iterable:
|
||||
6 | def __iter__(self, extra_arg) -> Iterator:
|
||||
7 | return Iterator()
|
||||
8 |
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | reveal_type(x) # revealed: int
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator:
|
||||
4 | def __next__(self) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Iterable:
|
||||
8 | def __iter__(self, extra_arg) -> Iterator:
|
||||
9 | return Iterator()
|
||||
10 |
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable` is not iterable
|
||||
--> src/mdtest_snippet.py:10:10
|
||||
--> src/mdtest_snippet.py:12:10
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
| ^^^^^^^^^^
|
||||
11 | reveal_type(x) # revealed: int
|
||||
13 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__iter__` method has an invalid signature
|
||||
info: Expected signature `def __iter__(self): ...`
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:13:17
|
||||
|
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
13 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,40 +12,42 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Iterator1:
|
||||
2 | def __next__(self, extra_arg) -> int:
|
||||
3 | return 42
|
||||
4 |
|
||||
5 | class Iterator2:
|
||||
6 | __next__: None = None
|
||||
7 |
|
||||
8 | class Iterable1:
|
||||
9 | def __iter__(self) -> Iterator1:
|
||||
10 | return Iterator1()
|
||||
11 |
|
||||
12 | class Iterable2:
|
||||
13 | def __iter__(self) -> Iterator2:
|
||||
14 | return Iterator2()
|
||||
15 |
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | reveal_type(x) # revealed: int
|
||||
19 |
|
||||
20 | # error: [not-iterable]
|
||||
21 | for y in Iterable2():
|
||||
22 | reveal_type(y) # revealed: Unknown
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Iterator1:
|
||||
4 | def __next__(self, extra_arg) -> int:
|
||||
5 | return 42
|
||||
6 |
|
||||
7 | class Iterator2:
|
||||
8 | __next__: None = None
|
||||
9 |
|
||||
10 | class Iterable1:
|
||||
11 | def __iter__(self) -> Iterator1:
|
||||
12 | return Iterator1()
|
||||
13 |
|
||||
14 | class Iterable2:
|
||||
15 | def __iter__(self) -> Iterator2:
|
||||
16 | return Iterator2()
|
||||
17 |
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
20 | reveal_type(x) # revealed: int
|
||||
21 |
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable1` is not iterable
|
||||
--> src/mdtest_snippet.py:17:10
|
||||
--> src/mdtest_snippet.py:19:10
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^
|
||||
18 | reveal_type(x) # revealed: int
|
||||
20 | reveal_type(x) # revealed: int
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method
|
||||
info: Expected signature for `__next__` is `def __next__(self): ...`
|
||||
@@ -54,15 +56,41 @@ info: rule `not-iterable` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` is not iterable
|
||||
--> src/mdtest_snippet.py:21:10
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:20:17
|
||||
|
|
||||
20 | # error: [not-iterable]
|
||||
21 | for y in Iterable2():
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
20 | reveal_type(x) # revealed: int
|
||||
| ^ `int`
|
||||
21 |
|
||||
22 | # error: [not-iterable]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[not-iterable]: Object of type `Iterable2` is not iterable
|
||||
--> src/mdtest_snippet.py:23:10
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^
|
||||
22 | reveal_type(y) # revealed: Unknown
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
|
|
||||
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
|
||||
info: rule `not-iterable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:24:17
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
| ^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -13,38 +13,78 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T", bound=int)
|
||||
4 |
|
||||
5 | def f(x: T) -> T:
|
||||
6 | return x
|
||||
7 |
|
||||
8 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
9 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
10 | # error: [invalid-argument-type]
|
||||
11 | reveal_type(f("string")) # revealed: Unknown
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 |
|
||||
4 | T = TypeVar("T", bound=int)
|
||||
5 |
|
||||
6 | def f(x: T) -> T:
|
||||
7 | return x
|
||||
8 |
|
||||
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:11:15
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:9:13
|
||||
|
|
||||
9 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
10 | # error: [invalid-argument-type]
|
||||
11 | reveal_type(f("string")) # revealed: Unknown
|
||||
7 | return x
|
||||
8 |
|
||||
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
| ^^^^ `Literal[1]`
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
11 | # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
|
|
||||
9 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
| ^^^^^^^ `Literal[True]`
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:12:13
|
||||
|
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^^^^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:12:15
|
||||
|
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound `int` of type variable `T`
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:3:1
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T", bound=int)
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 |
|
||||
4 | T = TypeVar("T", bound=int)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | def f(x: T) -> T:
|
||||
5 |
|
||||
6 | def f(x: T) -> T:
|
||||
|
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
|
||||
@@ -13,39 +13,93 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T", int, None)
|
||||
4 |
|
||||
5 | def f(x: T) -> T:
|
||||
6 | return x
|
||||
7 |
|
||||
8 | reveal_type(f(1)) # revealed: int
|
||||
9 | reveal_type(f(True)) # revealed: int
|
||||
10 | reveal_type(f(None)) # revealed: None
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 |
|
||||
4 | T = TypeVar("T", int, None)
|
||||
5 |
|
||||
6 | def f(x: T) -> T:
|
||||
7 | return x
|
||||
8 |
|
||||
9 | reveal_type(f(1)) # revealed: int
|
||||
10 | reveal_type(f(True)) # revealed: int
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
12 | # error: [invalid-argument-type]
|
||||
13 | reveal_type(f("string")) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:12:15
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:9:13
|
||||
|
|
||||
10 | reveal_type(f(None)) # revealed: None
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
7 | return x
|
||||
8 |
|
||||
9 | reveal_type(f(1)) # revealed: int
|
||||
| ^^^^ `int`
|
||||
10 | reveal_type(f(True)) # revealed: int
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
|
|
||||
9 | reveal_type(f(1)) # revealed: int
|
||||
10 | reveal_type(f(True)) # revealed: int
|
||||
| ^^^^^^^ `int`
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
12 | # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:11:13
|
||||
|
|
||||
9 | reveal_type(f(1)) # revealed: int
|
||||
10 | reveal_type(f(True)) # revealed: int
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
| ^^^^^^^ `None`
|
||||
12 | # error: [invalid-argument-type]
|
||||
13 | reveal_type(f("string")) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:13:13
|
||||
|
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
12 | # error: [invalid-argument-type]
|
||||
13 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^^^^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:13:15
|
||||
|
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
12 | # error: [invalid-argument-type]
|
||||
13 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints (`int`, `None`) of type variable `T`
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:3:1
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T", int, None)
|
||||
2 | from typing_extensions import reveal_type
|
||||
3 |
|
||||
4 | T = TypeVar("T", int, None)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | def f(x: T) -> T:
|
||||
5 |
|
||||
6 | def f(x: T) -> T:
|
||||
|
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
|
||||
@@ -25,6 +25,45 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:6:13
|
||||
|
|
||||
4 | return x
|
||||
5 |
|
||||
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
| ^^^^ `Literal[1]`
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:7:13
|
||||
|
|
||||
6 | reveal_type(f(1)) # revealed: Literal[1]
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
| ^^^^^^^ `Literal[True]`
|
||||
8 | # error: [invalid-argument-type]
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:9:13
|
||||
|
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type]
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^^^^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:9:15
|
||||
|
||||
@@ -26,6 +26,59 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:6:13
|
||||
|
|
||||
4 | return x
|
||||
5 |
|
||||
6 | reveal_type(f(1)) # revealed: int
|
||||
| ^^^^ `int`
|
||||
7 | reveal_type(f(True)) # revealed: int
|
||||
8 | reveal_type(f(None)) # revealed: None
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:7:13
|
||||
|
|
||||
6 | reveal_type(f(1)) # revealed: int
|
||||
7 | reveal_type(f(True)) # revealed: int
|
||||
| ^^^^^^^ `int`
|
||||
8 | reveal_type(f(None)) # revealed: None
|
||||
9 | # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:8:13
|
||||
|
|
||||
6 | reveal_type(f(1)) # revealed: int
|
||||
7 | reveal_type(f(True)) # revealed: int
|
||||
8 | reveal_type(f(None)) # revealed: None
|
||||
| ^^^^^^^ `None`
|
||||
9 | # error: [invalid-argument-type]
|
||||
10 | reveal_type(f("string")) # revealed: Unknown
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
|
|
||||
8 | reveal_type(f(None)) # revealed: None
|
||||
9 | # error: [invalid-argument-type]
|
||||
10 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^^^^ `Unknown`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
--> src/mdtest_snippet.py:10:15
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Boolean parameters must be unambiguous
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import TypeVar
|
||||
2 |
|
||||
3 | def cond() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | # error: [invalid-legacy-type-variable]
|
||||
7 | T = TypeVar("T", covariant=cond())
|
||||
8 |
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
10 | U = TypeVar("U", contravariant=cond())
|
||||
11 |
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
13 | V = TypeVar("V", infer_variance=cond())
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `covariant` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:7:28
|
||||
|
|
||||
6 | # error: [invalid-legacy-type-variable]
|
||||
7 | T = TypeVar("T", covariant=cond())
|
||||
| ^^^^^^
|
||||
8 |
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `contravariant` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:10:32
|
||||
|
|
||||
9 | # error: [invalid-legacy-type-variable]
|
||||
10 | U = TypeVar("U", contravariant=cond())
|
||||
| ^^^^^^
|
||||
11 |
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `infer_variance` parameter of `TypeVar` cannot have an ambiguous truthiness
|
||||
--> src/mdtest_snippet.py:13:33
|
||||
|
|
||||
12 | # error: [invalid-legacy-type-variable]
|
||||
13 | V = TypeVar("V", infer_variance=cond())
|
||||
| ^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot be both covariant and contravariant
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", covariant=True, contravariant=True)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot be both covariant and contravariant
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", covariant=True, contravariant=True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have both bound and constraint
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int, str, bound=bytes)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot have both a bound and constraints
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int, str, bound=bytes)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have only one constraint
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` cannot have exactly one constraint
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", int)
|
||||
| ^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid feature for this Python version
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", default=int)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `default` parameter of `typing.TypeVar` was added in Python 3.13
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", default=int)
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid keyword arguments
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", invalid_keyword=True)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Unknown keyword argument `invalid_keyword` in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", invalid_keyword=True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must be directly assigned to a variable
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | T = TypeVar("T")
|
||||
4 | # error: [invalid-legacy-type-variable]
|
||||
5 | U: TypeVar = TypeVar("U")
|
||||
6 |
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
8 | tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
|
||||
--> src/mdtest_snippet.py:5:14
|
||||
|
|
||||
3 | T = TypeVar("T")
|
||||
4 | # error: [invalid-legacy-type-variable]
|
||||
5 | U: TypeVar = TypeVar("U")
|
||||
| ^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
|
||||
--> src/mdtest_snippet.py:8:30
|
||||
|
|
||||
7 | # error: [invalid-legacy-type-variable]
|
||||
8 | tuple_with_typevar = ("foo", TypeVar("W"))
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must have a name
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar()
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` is required.
|
||||
--> src/mdtest_snippet.py:4:5
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar()
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Name can't be given more than once
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", name="T")
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` can only be provided once.
|
||||
--> src/mdtest_snippet.py:4:18
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("T", name="T")
|
||||
| ^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - No variadic arguments
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | types = (int, str)
|
||||
4 |
|
||||
5 | # error: [invalid-legacy-type-variable]
|
||||
6 | T = TypeVar("T", *types)
|
||||
7 |
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
9 | S = TypeVar("S", **{"bound": int})
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:6:18
|
||||
|
|
||||
5 | # error: [invalid-legacy-type-variable]
|
||||
6 | T = TypeVar("T", *types)
|
||||
| ^^^^^^
|
||||
7 |
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
|
||||
--> src/mdtest_snippet.py:9:18
|
||||
|
|
||||
8 | # error: [invalid-legacy-type-variable]
|
||||
9 | S = TypeVar("S", **{"bound": int})
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - `TypeVar` parameter must match variable name
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import TypeVar
|
||||
2 |
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("Q")
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-legacy-type-variable]: The name of a `TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
|
||||
3 | # error: [invalid-legacy-type-variable]
|
||||
4 | T = TypeVar("Q")
|
||||
| ^
|
||||
|
|
||||
info: rule `invalid-legacy-type-variable` is enabled by default
|
||||
|
||||
```
|
||||
@@ -12,39 +12,67 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def returns_bool() -> bool:
|
||||
2 | return True
|
||||
3 |
|
||||
4 | class A: ...
|
||||
5 | class B: ...
|
||||
6 |
|
||||
7 | if returns_bool():
|
||||
8 | x = A
|
||||
9 | else:
|
||||
10 | x = B
|
||||
11 |
|
||||
12 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | def returns_bool() -> bool:
|
||||
4 | return True
|
||||
5 |
|
||||
6 | class A: ...
|
||||
7 | class B: ...
|
||||
8 |
|
||||
9 | if returns_bool():
|
||||
10 | x = A
|
||||
11 | else:
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
15 | class Foo(x): ...
|
||||
16 |
|
||||
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
|
||||
--> src/mdtest_snippet.py:15:11
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
15 | class Foo(x): ...
|
||||
12 | x = B
|
||||
13 |
|
||||
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
|
||||
| ^ `<class 'A'> | <class 'B'>`
|
||||
15 |
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
|
||||
--> src/mdtest_snippet.py:17:11
|
||||
|
|
||||
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
||||
17 | class Foo(x): ...
|
||||
| ^
|
||||
16 |
|
||||
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
|
||||
info: Only class objects or `Any` are supported as class bases
|
||||
info: rule `unsupported-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:13
|
||||
|
|
||||
17 | class Foo(x): ...
|
||||
18 |
|
||||
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,147 +12,167 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
4 |
|
||||
5 | class Spam: ...
|
||||
6 | class Eggs: ...
|
||||
7 | class Bar: ...
|
||||
8 | class Baz: ...
|
||||
9 |
|
||||
10 | # fmt: off
|
||||
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
6 |
|
||||
7 | class Spam: ...
|
||||
8 | class Eggs: ...
|
||||
9 | class Bar: ...
|
||||
10 | class Baz: ...
|
||||
11 |
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
16 | Eggs,
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
20 | Eggs,
|
||||
21 | ): ...
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
12 | # fmt: off
|
||||
13 |
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
18 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
22 | Eggs,
|
||||
23 | ): ...
|
||||
24 |
|
||||
25 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
|
||||
25 | # fmt: on
|
||||
26 |
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
|
||||
28 |
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
31 |
|
||||
32 | # fmt: off
|
||||
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
33 |
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | Eggs,
|
||||
37 | Ham,
|
||||
38 | Spam,
|
||||
39 | Eggs,
|
||||
40 | Mushrooms,
|
||||
41 | Bar,
|
||||
42 | Eggs,
|
||||
43 | Baz,
|
||||
34 | # fmt: off
|
||||
35 |
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
38 | Eggs,
|
||||
39 | Ham,
|
||||
40 | Spam,
|
||||
41 | Eggs,
|
||||
42 | Mushrooms,
|
||||
43 | Bar,
|
||||
44 | Eggs,
|
||||
45 | ): ...
|
||||
46 |
|
||||
47 | # fmt: off
|
||||
48 | # fmt: off
|
||||
49 |
|
||||
50 | class A: ...
|
||||
45 | Baz,
|
||||
46 | Eggs,
|
||||
47 | ): ...
|
||||
48 |
|
||||
49 | # fmt: off
|
||||
50 | # fmt: off
|
||||
51 |
|
||||
52 | class B( # type: ignore[duplicate-base]
|
||||
53 | A,
|
||||
54 | A,
|
||||
55 | ): ...
|
||||
56 |
|
||||
57 | class C(
|
||||
58 | A,
|
||||
59 | A
|
||||
60 | ): # type: ignore[duplicate-base]
|
||||
61 | x: int
|
||||
62 |
|
||||
63 | # fmt: on
|
||||
64 | # fmt: off
|
||||
65 |
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | A,
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
71 | ): ...
|
||||
72 |
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | A,
|
||||
76 | A
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
80 |
|
||||
81 | # fmt: on
|
||||
52 | class A: ...
|
||||
53 |
|
||||
54 | class B( # type: ignore[duplicate-base]
|
||||
55 | A,
|
||||
56 | A,
|
||||
57 | ): ...
|
||||
58 |
|
||||
59 | class C(
|
||||
60 | A,
|
||||
61 | A
|
||||
62 | ): # type: ignore[duplicate-base]
|
||||
63 | x: int
|
||||
64 |
|
||||
65 | # fmt: on
|
||||
66 | # fmt: off
|
||||
67 |
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
70 | A,
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
73 | ): ...
|
||||
74 |
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
77 | A,
|
||||
78 | A
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
82 |
|
||||
83 | # fmt: on
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `str`
|
||||
--> src/mdtest_snippet.py:1:7
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| ^^^^^^^^^^^^^
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| ^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: The definition of class `Foo` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:1:11
|
||||
--> src/mdtest_snippet.py:3:11
|
||||
|
|
||||
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
1 | from typing_extensions import reveal_type
|
||||
2 |
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
| --- ^^^ Class `str` later repeated here
|
||||
| |
|
||||
| Class `str` first included in bases list here
|
||||
2 |
|
||||
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
4 |
|
||||
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:5:13
|
||||
|
|
||||
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
|
||||
4 |
|
||||
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
||||
6 |
|
||||
7 | class Spam: ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Spam`
|
||||
--> src/mdtest_snippet.py:14:7
|
||||
--> src/mdtest_snippet.py:16:7
|
||||
|
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
| _______^
|
||||
15 | | Spam,
|
||||
16 | | Eggs,
|
||||
17 | | Bar,
|
||||
18 | | Baz,
|
||||
19 | | Spam,
|
||||
20 | | Eggs,
|
||||
21 | | ): ...
|
||||
17 | | Spam,
|
||||
18 | | Eggs,
|
||||
19 | | Bar,
|
||||
20 | | Baz,
|
||||
21 | | Spam,
|
||||
22 | | Eggs,
|
||||
23 | | ): ...
|
||||
| |_^
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
24 |
|
||||
25 | # fmt: on
|
||||
|
|
||||
info: The definition of class `Ham` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:15:5
|
||||
--> src/mdtest_snippet.py:17:5
|
||||
|
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
| ---- Class `Spam` first included in bases list here
|
||||
16 | Eggs,
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
18 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
| ^^^^ Class `Spam` later repeated here
|
||||
20 | Eggs,
|
||||
21 | ): ...
|
||||
22 | Eggs,
|
||||
23 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
@@ -160,106 +180,134 @@ info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Eggs`
|
||||
--> src/mdtest_snippet.py:14:7
|
||||
--> src/mdtest_snippet.py:16:7
|
||||
|
|
||||
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
14 | class Ham(
|
||||
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
|
||||
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
16 | class Ham(
|
||||
| _______^
|
||||
15 | | Spam,
|
||||
16 | | Eggs,
|
||||
17 | | Bar,
|
||||
18 | | Baz,
|
||||
19 | | Spam,
|
||||
20 | | Eggs,
|
||||
21 | | ): ...
|
||||
17 | | Spam,
|
||||
18 | | Eggs,
|
||||
19 | | Bar,
|
||||
20 | | Baz,
|
||||
21 | | Spam,
|
||||
22 | | Eggs,
|
||||
23 | | ): ...
|
||||
| |_^
|
||||
22 |
|
||||
23 | # fmt: on
|
||||
24 |
|
||||
25 | # fmt: on
|
||||
|
|
||||
info: The definition of class `Ham` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:16:5
|
||||
--> src/mdtest_snippet.py:18:5
|
||||
|
|
||||
14 | class Ham(
|
||||
15 | Spam,
|
||||
16 | Eggs,
|
||||
16 | class Ham(
|
||||
17 | Spam,
|
||||
18 | Eggs,
|
||||
| ---- Class `Eggs` first included in bases list here
|
||||
17 | Bar,
|
||||
18 | Baz,
|
||||
19 | Spam,
|
||||
20 | Eggs,
|
||||
19 | Bar,
|
||||
20 | Baz,
|
||||
21 | Spam,
|
||||
22 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
21 | ): ...
|
||||
23 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:27:13
|
||||
|
|
||||
25 | # fmt: on
|
||||
26 |
|
||||
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^ `tuple[<class 'Ham'>, Unknown, <class 'object'>]`
|
||||
28 |
|
||||
29 | class Mushrooms: ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Mushrooms`
|
||||
--> src/mdtest_snippet.py:28:7
|
||||
--> src/mdtest_snippet.py:30:7
|
||||
|
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
31 |
|
||||
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: The definition of class `Omelette` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:28:28
|
||||
--> src/mdtest_snippet.py:30:28
|
||||
|
|
||||
27 | class Mushrooms: ...
|
||||
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
29 | class Mushrooms: ...
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
|
||||
| |
|
||||
| Class `Mushrooms` first included in bases list here
|
||||
29 |
|
||||
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
31 |
|
||||
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:32:13
|
||||
|
|
||||
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
|
||||
31 |
|
||||
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
||||
| ^^^^^^^^^^^^^^^^ `tuple[<class 'Omelette'>, Unknown, <class 'object'>]`
|
||||
33 |
|
||||
34 | # fmt: off
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `Eggs`
|
||||
--> src/mdtest_snippet.py:35:7
|
||||
--> src/mdtest_snippet.py:37:7
|
||||
|
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
| _______^
|
||||
36 | | Eggs,
|
||||
37 | | Ham,
|
||||
38 | | Spam,
|
||||
39 | | Eggs,
|
||||
40 | | Mushrooms,
|
||||
41 | | Bar,
|
||||
42 | | Eggs,
|
||||
43 | | Baz,
|
||||
38 | | Eggs,
|
||||
39 | | Ham,
|
||||
40 | | Spam,
|
||||
41 | | Eggs,
|
||||
42 | | Mushrooms,
|
||||
43 | | Bar,
|
||||
44 | | Eggs,
|
||||
45 | | ): ...
|
||||
45 | | Baz,
|
||||
46 | | Eggs,
|
||||
47 | | ): ...
|
||||
| |_^
|
||||
46 |
|
||||
47 | # fmt: off
|
||||
48 |
|
||||
49 | # fmt: off
|
||||
|
|
||||
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:36:5
|
||||
--> src/mdtest_snippet.py:38:5
|
||||
|
|
||||
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
35 | class VeryEggyOmelette(
|
||||
36 | Eggs,
|
||||
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
|
||||
37 | class VeryEggyOmelette(
|
||||
38 | Eggs,
|
||||
| ---- Class `Eggs` first included in bases list here
|
||||
37 | Ham,
|
||||
38 | Spam,
|
||||
39 | Eggs,
|
||||
39 | Ham,
|
||||
40 | Spam,
|
||||
41 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
40 | Mushrooms,
|
||||
41 | Bar,
|
||||
42 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
43 | Baz,
|
||||
42 | Mushrooms,
|
||||
43 | Bar,
|
||||
44 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
45 | ): ...
|
||||
45 | Baz,
|
||||
46 | Eggs,
|
||||
| ^^^^ Class `Eggs` later repeated here
|
||||
47 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
@@ -267,30 +315,30 @@ info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `A`
|
||||
--> src/mdtest_snippet.py:67:7
|
||||
--> src/mdtest_snippet.py:69:7
|
||||
|
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
| _______^
|
||||
68 | | A,
|
||||
69 | | # error: [unused-ignore-comment]
|
||||
70 | | A, # type: ignore[duplicate-base]
|
||||
71 | | ): ...
|
||||
70 | | A,
|
||||
71 | | # error: [unused-ignore-comment]
|
||||
72 | | A, # type: ignore[duplicate-base]
|
||||
73 | | ): ...
|
||||
| |_^
|
||||
72 |
|
||||
73 | # error: [duplicate-base]
|
||||
74 |
|
||||
75 | # error: [duplicate-base]
|
||||
|
|
||||
info: The definition of class `D` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:68:5
|
||||
--> src/mdtest_snippet.py:70:5
|
||||
|
|
||||
66 | # error: [duplicate-base]
|
||||
67 | class D(
|
||||
68 | A,
|
||||
68 | # error: [duplicate-base]
|
||||
69 | class D(
|
||||
70 | A,
|
||||
| - Class `A` first included in bases list here
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
| ^ Class `A` later repeated here
|
||||
71 | ): ...
|
||||
73 | ): ...
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
@@ -298,42 +346,42 @@ info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
--> src/mdtest_snippet.py:70:9
|
||||
--> src/mdtest_snippet.py:72:9
|
||||
|
|
||||
68 | A,
|
||||
69 | # error: [unused-ignore-comment]
|
||||
70 | A, # type: ignore[duplicate-base]
|
||||
70 | A,
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
71 | ): ...
|
||||
73 | ): ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[duplicate-base]: Duplicate base class `A`
|
||||
--> src/mdtest_snippet.py:74:7
|
||||
--> src/mdtest_snippet.py:76:7
|
||||
|
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
| _______^
|
||||
75 | | A,
|
||||
76 | | A
|
||||
77 | | ):
|
||||
77 | | A,
|
||||
78 | | A
|
||||
79 | | ):
|
||||
| |_^
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
|
|
||||
info: The definition of class `E` will raise `TypeError` at runtime
|
||||
--> src/mdtest_snippet.py:75:5
|
||||
--> src/mdtest_snippet.py:77:5
|
||||
|
|
||||
73 | # error: [duplicate-base]
|
||||
74 | class E(
|
||||
75 | A,
|
||||
75 | # error: [duplicate-base]
|
||||
76 | class E(
|
||||
77 | A,
|
||||
| - Class `A` first included in bases list here
|
||||
76 | A
|
||||
78 | A
|
||||
| ^ Class `A` later repeated here
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
|
|
||||
info: rule `duplicate-base` is enabled by default
|
||||
|
||||
@@ -341,14 +389,14 @@ info: rule `duplicate-base` is enabled by default
|
||||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
--> src/mdtest_snippet.py:79:13
|
||||
--> src/mdtest_snippet.py:81:13
|
||||
|
|
||||
77 | ):
|
||||
78 | # error: [unused-ignore-comment]
|
||||
79 | x: int # type: ignore[duplicate-base]
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
80 |
|
||||
81 | # fmt: on
|
||||
82 |
|
||||
83 | # fmt: on
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -136,3 +136,48 @@ info: (x: B, /, **kwargs: int) -> B
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:8:9
|
||||
|
|
||||
6 | # error: [no-matching-overload]
|
||||
7 | # revealed: Unknown
|
||||
8 | / f(
|
||||
9 | | A(),
|
||||
10 | | a1=a,
|
||||
11 | | a2=a,
|
||||
12 | | a3=a,
|
||||
13 | | a4=a,
|
||||
14 | | a5=a,
|
||||
15 | | a6=a,
|
||||
16 | | a7=a,
|
||||
17 | | a8=a,
|
||||
18 | | a9=a,
|
||||
19 | | a10=a,
|
||||
20 | | a11=a,
|
||||
21 | | a12=a,
|
||||
22 | | a13=a,
|
||||
23 | | a14=a,
|
||||
24 | | a15=a,
|
||||
25 | | a16=a,
|
||||
26 | | a17=a,
|
||||
27 | | a18=a,
|
||||
28 | | a19=a,
|
||||
29 | | a20=a,
|
||||
30 | | a21=a,
|
||||
31 | | a22=a,
|
||||
32 | | a23=a,
|
||||
33 | | a24=a,
|
||||
34 | | a25=a,
|
||||
35 | | a26=a,
|
||||
36 | | a27=a,
|
||||
37 | | a28=a,
|
||||
38 | | a29=a,
|
||||
39 | | a30=a,
|
||||
40 | | )
|
||||
| |_________^ `Unknown`
|
||||
41 | )
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | # error: [call-non-callable]
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
@@ -36,7 +36,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||
22 |
|
||||
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||
24 | def f(x: type[MyProtocol]):
|
||||
25 | reveal_type(x()) # revealed: @Todo(type[T] for protocols)
|
||||
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||
26 | # (which doesn't work well with snapshots)
|
||||
27 | x()
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -55,6 +57,19 @@ info: rule `call-non-callable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:4:13
|
||||
|
|
||||
3 | # error: [call-non-callable]
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
| ^^^^^^^^^^ `Unknown`
|
||||
5 |
|
||||
6 | class MyProtocol(Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[call-non-callable]: Cannot instantiate class `MyProtocol`
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
@@ -78,6 +93,19 @@ info: rule `call-non-callable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:13
|
||||
|
|
||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||
| ^^^^^^^^^^^^ `MyProtocol`
|
||||
11 |
|
||||
12 | class GenericProtocol[T](Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[call-non-callable]: Cannot instantiate class `GenericProtocol`
|
||||
--> src/mdtest_snippet.py:16:13
|
||||
@@ -99,3 +127,43 @@ info: Protocol classes cannot be instantiated
|
||||
info: rule `call-non-callable` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:16:13
|
||||
|
|
||||
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
|
||||
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
|
||||
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:19:13
|
||||
|
|
||||
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
18 |
|
||||
19 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
|
||||
20 |
|
||||
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:23:13
|
||||
|
|
||||
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
||||
22 |
|
||||
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
|
||||
24 | def f(x: type[MyProtocol]):
|
||||
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
4 | x: int
|
||||
@@ -69,7 +69,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
|
||||
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import Protocol
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
| ^^^^^^^^^^^^^^ `HasX` declared here
|
||||
@@ -81,6 +81,34 @@ info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:8:21
|
||||
|
|
||||
6 | def f(arg: object, arg2: type):
|
||||
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
| ^^^ `HasX`
|
||||
9 | else:
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:10:21
|
||||
|
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
9 | else:
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
| ^^^ `~HasX`
|
||||
11 |
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Class `HasX` cannot be used as the second argument to `issubclass`
|
||||
--> src/mdtest_snippet.py:12:8
|
||||
@@ -95,7 +123,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
|
||||
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import Protocol
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
| ^^^^^^^^^^^^^^ `HasX` declared here
|
||||
@@ -106,3 +134,110 @@ info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:13:21
|
||||
|
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
| ^^^^ `type[HasX]`
|
||||
14 | else:
|
||||
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:15:21
|
||||
|
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
14 | else:
|
||||
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
| ^^^^ `type & ~type[HasX]`
|
||||
16 | from typing import runtime_checkable
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:24:21
|
||||
|
|
||||
22 | def f(arg: object):
|
||||
23 | if isinstance(arg, RuntimeCheckableHasX): # no error!
|
||||
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
|
||||
| ^^^ `RuntimeCheckableHasX`
|
||||
25 | else:
|
||||
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:26:21
|
||||
|
|
||||
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
|
||||
25 | else:
|
||||
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
||||
| ^^^ `~RuntimeCheckableHasX`
|
||||
27 | @runtime_checkable
|
||||
28 | class OnlyMethodMembers(Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:33:21
|
||||
|
|
||||
31 | def f(arg1: type, arg2: type):
|
||||
32 | if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
|
||||
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
| ^^^^ `type[RuntimeCheckableHasX]`
|
||||
34 | else:
|
||||
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:35:21
|
||||
|
|
||||
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
34 | else:
|
||||
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
| ^^^^ `type & ~type[RuntimeCheckableHasX]`
|
||||
36 |
|
||||
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:38:21
|
||||
|
|
||||
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
| ^^^^ `type[OnlyMethodMembers]`
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:40:21
|
||||
|
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
| ^^^^ `type & ~type[OnlyMethodMembers]`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||
|
||||
```
|
||||
1 | import sys
|
||||
2 | from typing_extensions import Protocol, get_protocol_members
|
||||
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
|
||||
3 |
|
||||
4 | class Foo(Protocol):
|
||||
5 | if sys.version_info >= (3, 10):
|
||||
@@ -43,7 +43,7 @@ warning[ambiguous-protocol-member]: Cannot assign to undeclared variable in the
|
||||
info: Assigning to an undeclared variable in a protocol class leads to an ambiguous interface
|
||||
--> src/mdtest_snippet.py:4:7
|
||||
|
|
||||
2 | from typing_extensions import Protocol, get_protocol_members
|
||||
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
|
||||
3 |
|
||||
4 | class Foo(Protocol):
|
||||
| ^^^^^^^^^^^^^ `Foo` declared as a protocol here
|
||||
@@ -54,3 +54,15 @@ info: No declarations found for `e` in the body of `Foo` or any of its superclas
|
||||
info: rule `ambiguous-protocol-member` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info[revealed-type]: Revealed type
|
||||
--> src/mdtest_snippet.py:14:13
|
||||
|
|
||||
12 | def f(self) -> None: ...
|
||||
13 |
|
||||
14 | reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `frozenset[Literal["d", "e", "f"]]`
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: super.md - Super - Basic Usage - Explicit Super Object
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class A:
|
||||
2 | def a(self): ...
|
||||
3 | aa: int = 1
|
||||
4 |
|
||||
5 | class B(A):
|
||||
6 | def b(self): ...
|
||||
7 | bb: int = 2
|
||||
8 |
|
||||
9 | class C(B):
|
||||
10 | def c(self): ...
|
||||
11 | cc: int = 3
|
||||
12 |
|
||||
13 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
|
||||
14 |
|
||||
15 | super(C, C()).a
|
||||
16 | super(C, C()).b
|
||||
17 | super(C, C()).c # error: [unresolved-attribute]
|
||||
18 |
|
||||
19 | super(B, C()).a
|
||||
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||
22 |
|
||||
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||
26 |
|
||||
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
28 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
|
||||
29 | reveal_type(super(C, C()).aa) # revealed: int
|
||||
30 | reveal_type(super(C, C()).bb) # revealed: int
|
||||
31 | import types
|
||||
32 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
|
||||
33 |
|
||||
34 | def f(): ...
|
||||
35 |
|
||||
36 | class Foo[T]:
|
||||
37 | def method(self): ...
|
||||
38 | @property
|
||||
39 | def some_property(self): ...
|
||||
40 |
|
||||
41 | type Alias = int
|
||||
42 |
|
||||
43 | class SomeTypedDict(TypedDict):
|
||||
44 | x: int
|
||||
45 | y: bytes
|
||||
46 |
|
||||
47 | # revealed: <super: <class 'object'>, FunctionType>
|
||||
48 | reveal_type(super(object, f))
|
||||
49 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
|
||||
50 | reveal_type(super(object, types.FunctionType.__get__))
|
||||
51 | # revealed: <super: <class 'object'>, GenericAlias>
|
||||
52 | reveal_type(super(object, Foo[int]))
|
||||
53 | # revealed: <super: <class 'object'>, _SpecialForm>
|
||||
54 | reveal_type(super(object, Literal))
|
||||
55 | # revealed: <super: <class 'object'>, TypeAliasType>
|
||||
56 | reveal_type(super(object, Alias))
|
||||
57 | # revealed: <super: <class 'object'>, MethodType>
|
||||
58 | reveal_type(super(object, Foo().method))
|
||||
59 | # revealed: <super: <class 'object'>, property>
|
||||
60 | reveal_type(super(object, Foo.some_property))
|
||||
61 |
|
||||
62 | def g(x: object) -> TypeIs[list[object]]:
|
||||
63 | return isinstance(x, list)
|
||||
64 |
|
||||
65 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
|
||||
66 | if hasattr(x, "bar"):
|
||||
67 | # revealed: <Protocol with members 'bar'>
|
||||
68 | reveal_type(x)
|
||||
69 | # error: [invalid-super-argument]
|
||||
70 | # revealed: Unknown
|
||||
71 | reveal_type(super(object, x))
|
||||
72 |
|
||||
73 | # error: [invalid-super-argument]
|
||||
74 | # revealed: Unknown
|
||||
75 | reveal_type(super(object, z))
|
||||
76 |
|
||||
77 | is_list = g(x)
|
||||
78 | # revealed: TypeIs[list[object] @ x]
|
||||
79 | reveal_type(is_list)
|
||||
80 | # revealed: <super: <class 'object'>, bool>
|
||||
81 | reveal_type(super(object, is_list))
|
||||
82 |
|
||||
83 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
|
||||
84 | reveal_type(super(object, y))
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:17:1
|
||||
|
|
||||
15 | super(C, C()).a
|
||||
16 | super(C, C()).b
|
||||
17 | super(C, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
18 |
|
||||
19 | super(B, C()).a
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
|
||||
--> src/mdtest_snippet.py:20:1
|
||||
|
|
||||
19 | super(B, C()).a
|
||||
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:21:1
|
||||
|
|
||||
19 | super(B, C()).a
|
||||
20 | super(B, C()).b # error: [unresolved-attribute]
|
||||
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
22 |
|
||||
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
|
||||
--> src/mdtest_snippet.py:23:1
|
||||
|
|
||||
21 | super(B, C()).c # error: [unresolved-attribute]
|
||||
22 |
|
||||
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
|
||||
--> src/mdtest_snippet.py:24:1
|
||||
|
|
||||
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
|
||||
--> src/mdtest_snippet.py:25:1
|
||||
|
|
||||
23 | super(A, C()).a # error: [unresolved-attribute]
|
||||
24 | super(A, C()).b # error: [unresolved-attribute]
|
||||
25 | super(A, C()).c # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^
|
||||
26 |
|
||||
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
|
||||
--> src/mdtest_snippet.py:71:21
|
||||
|
|
||||
69 | # error: [invalid-super-argument]
|
||||
70 | # revealed: Unknown
|
||||
71 | reveal_type(super(object, x))
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
72 |
|
||||
73 | # error: [invalid-super-argument]
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
|
||||
--> src/mdtest_snippet.py:75:17
|
||||
|
|
||||
73 | # error: [invalid-super-argument]
|
||||
74 | # revealed: Unknown
|
||||
75 | reveal_type(super(object, z))
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
76 |
|
||||
77 | is_list = g(x)
|
||||
|
|
||||
info: rule `invalid-super-argument` is enabled by default
|
||||
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user