Compare commits
48 Commits
0.13.1
...
david/full
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048182635a | ||
|
|
09f570af92 | ||
|
|
722f1a7d7a | ||
|
|
dbc5983503 | ||
|
|
46decd4feb | ||
|
|
bf38e69870 | ||
|
|
4ed8c65d29 | ||
|
|
2c916562ba | ||
|
|
edb920b4d5 | ||
|
|
346842f003 | ||
|
|
742f8a4ee6 | ||
|
|
fd5c48c539 | ||
|
|
ef4df34652 | ||
|
|
036f3616a1 | ||
|
|
68ae9c8a15 | ||
|
|
094bf70a60 | ||
|
|
32d00cd569 | ||
|
|
00a9e65d00 | ||
|
|
0c7cfd2a8d | ||
|
|
61bb2a8245 | ||
|
|
f1aacd0f2c | ||
|
|
3033d1e5a5 | ||
|
|
d96d40ef42 | ||
|
|
79224cc53d | ||
|
|
fe01a5e032 | ||
|
|
740425d39d | ||
|
|
4fddd373aa | ||
|
|
0b1e12f086 | ||
|
|
d12324f06e | ||
|
|
2c6c3e78f6 | ||
|
|
3ffe56d19d | ||
|
|
eb354608d2 | ||
|
|
12086dfa69 | ||
|
|
5f294f9f2e | ||
|
|
44fc87f491 | ||
|
|
43cda2dfe9 | ||
|
|
bd5b3e4f6e | ||
|
|
3bf4dae452 | ||
|
|
8eeca023d6 | ||
|
|
c94ddb590f | ||
|
|
bae8ddfb8a | ||
|
|
c0fb235a70 | ||
|
|
b5a3503a58 | ||
|
|
d45209f425 | ||
|
|
5d1cd85662 | ||
|
|
902b0b4ce9 | ||
|
|
6f2b60708e | ||
|
|
bc89d0394c |
45
.github/workflows/ci.yaml
vendored
@@ -88,7 +88,6 @@ jobs:
|
||||
':!crates/ruff_python_formatter/**' \
|
||||
':!crates/ruff_formatter/**' \
|
||||
':!crates/ruff_dev/**' \
|
||||
':!crates/ruff_db/**' \
|
||||
':scripts/*' \
|
||||
':python/**' \
|
||||
':.github/workflows/ci.yaml' \
|
||||
@@ -907,10 +906,13 @@ jobs:
|
||||
run: npm run fmt:check
|
||||
working-directory: playground
|
||||
|
||||
benchmarks-instrumented:
|
||||
benchmarks-instrumented-ruff:
|
||||
name: "benchmarks instrumented (ruff)"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
(needs.determine_changes.outputs.formatter == 'true' || needs.determine_changes.outputs.linter == 'true')
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
@@ -930,7 +932,42 @@ jobs:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark formatter lexer linter parser
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
with:
|
||||
mode: instrumentation
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
benchmarks-instrumented-ty:
|
||||
name: "benchmarks instrumented (ty)"
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@b75a909f75acd358c2196fb9a5f1299a9a8868a4 # v6.7.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@67cc679904bee382389bf22082124fa963c6f6bd # v2.61.3
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
- name: "Build benchmarks"
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark ty
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@653fdc30e6c40ffd9739e40c8a0576f4f4523ca1 # v4.0.1
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@27dd66d9e397d986ef9c631119ee09556eab8af9"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@fc0f612798710b0dd69bb7528bc9b361dc60bd43"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
|
||||
5
.vscode/settings.json
vendored
@@ -3,4 +3,7 @@
|
||||
"--all-features"
|
||||
],
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
}
|
||||
"search.exclude": {
|
||||
"**/*.snap": true
|
||||
}
|
||||
}
|
||||
|
||||
370
Cargo.lock
generated
@@ -23,12 +23,6 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -104,9 +98,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-svg"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc03a770ef506fe1396c0e476120ac0e6523cf14b74218dd5f18cd6833326fa9"
|
||||
checksum = "26b9ec8c976eada1b0f9747a3d7cc4eae3bef10613e443746e7487f26c872fde"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
@@ -128,9 +122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
@@ -284,9 +278,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "boxcar"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c4925bc979b677330a8c7fe7a8c94af2dbb4a2d37b4a20a80d884400f46baa"
|
||||
checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
@@ -346,10 +340,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.31"
|
||||
version = "1.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
@@ -357,9 +352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
@@ -369,14 +364,13 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -408,9 +402,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +412,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -431,9 +425,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "75bf0b32ad2e152de789bb635ea4d3078f6b838ad7974143e99b99f45a04af4a"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -650,15 +644,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
|
||||
checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.1",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -837,9 +831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -847,9 +841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
@@ -861,9 +855,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
version = "0.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
@@ -956,7 +950,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1037,12 +1031,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1053,12 +1047,11 @@ checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6"
|
||||
|
||||
[[package]]
|
||||
name = "escargot"
|
||||
version = "0.5.14"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83f351750780493fc33fa0ce8ba3c7d61f9736cfa3b3bb9ee2342643ffe40211"
|
||||
checksum = "11c3aea32bc97b500c9ca6a72b768a26e558264303d101d3409cf6d57a9ed0cf"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1101,6 +1094,12 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
@@ -1168,9 +1167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a17a226478b2e8294ded60782c03efe54476aa8cd1371d0e5ad9d1071e74e0"
|
||||
checksum = "e3814abc7da8ab18d2fd820f5b540b5e39b6af0a32de1bdd7c47576693074843"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1179,21 +1178,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5697765925a05c9d401dd04a93dfd662d336cc25fdcc3301220385a1ffcfdde5"
|
||||
checksum = "5dfe2cec5b5ce8fb94dcdb16a1708baa4d0609cc3ce305ca5d3f6f2ffb59baed"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.23"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
||||
dependencies = [
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -1219,7 +1218,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1280,6 +1279,15 @@ dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
@@ -1321,9 +1329,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
version = "0.1.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@@ -1493,13 +1501,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.1"
|
||||
version = "2.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1508,7 +1517,7 @@ version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
|
||||
dependencies = [
|
||||
"console 0.16.0",
|
||||
"console 0.16.1",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.1",
|
||||
"unit-prefix",
|
||||
@@ -1588,9 +1597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.3.20"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83"
|
||||
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
@@ -1719,9 +1728,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"libc",
|
||||
@@ -1735,9 +1744,9 @@ checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -1812,9 +1821,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.9"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
@@ -1835,9 +1844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
@@ -2133,12 +2142,13 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.10"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dcd63f1ae4b091e314a26627c467dd8810d674ba798abc0e566679955776c63"
|
||||
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2289,9 +2299,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.16",
|
||||
@@ -2300,9 +2310,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
||||
checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -2310,9 +2320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
||||
checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@@ -2323,9 +2333,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"sha2",
|
||||
@@ -2398,9 +2408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
|
||||
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@@ -2453,9 +2463,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
@@ -2705,15 +2715,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-lite"
|
||||
version = "0.1.6"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
@@ -2994,7 +3004,7 @@ dependencies = [
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"imperative",
|
||||
"insta",
|
||||
"is-macro",
|
||||
@@ -3427,22 +3437,22 @@ checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@@ -3537,9 +3547,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@@ -3558,18 +3568,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.223"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3613,11 +3623,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.0"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3631,9 +3641,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.14.0"
|
||||
version = "3.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
|
||||
checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -3642,9 +3652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.14.0"
|
||||
version = "3.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
||||
checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -3830,7 +3840,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3844,12 +3854,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4009,9 +4019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
|
||||
checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
@@ -4024,14 +4034,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.5"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime 0.7.0",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
@@ -4039,44 +4049,39 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
version = "0.23.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
@@ -4286,6 +4291,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4303,7 +4309,7 @@ dependencies = [
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
@@ -4513,9 +4519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||
checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -4740,18 +4746,27 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -4762,9 +4777,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -4776,9 +4791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.51"
|
||||
version = "0.4.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
|
||||
checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -4789,9 +4804,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4799,9 +4814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4812,18 +4827,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.101"
|
||||
version = "0.2.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80cc7f8a4114fdaa0c58383caf973fc126cf004eba25c9dc639bccd3880d55ad"
|
||||
checksum = "aee0a0f5343de9221a0d233b04520ed8dc2e6728dce180b1dcd9288ec9d9fa3c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"minicov",
|
||||
@@ -4834,9 +4849,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.51"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ada2ab788d46d4bda04c9d567702a79c8ced14f51f221646a16ed39d0e6a5d"
|
||||
checksum = "a369369e4360c2884c3168d22bded735c43cccae97bbc147586d4b480edd138d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4845,9 +4860,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.78"
|
||||
version = "0.3.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
||||
checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4885,22 +4900,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
checksum = "57fe7168f7de578d2d8a05b07fd61870d2e73b4020e9f49aa00da8471723497c"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -4941,20 +4956,20 @@ checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5124,9 +5139,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5138,13 +5153,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
@@ -5193,18 +5205,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.26"
|
||||
version = "0.8.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5299,9 +5311,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
||||
@@ -86,7 +86,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.6.2", features = [
|
||||
get-size2 = { version = "0.7.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -95,7 +95,7 @@ get-size2 = { version = "0.6.2", features = [
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.14" }
|
||||
globwalk = { version = "0.9.1" }
|
||||
hashbrown = { version = "0.15.0", default-features = false, features = [
|
||||
hashbrown = { version = "0.16.0", default-features = false, features = [
|
||||
"raw-entry",
|
||||
"equivalent",
|
||||
"inline-more",
|
||||
@@ -203,7 +203,7 @@ wild = { version = "2" }
|
||||
zip = { version = "0.6.6", default-features = false }
|
||||
|
||||
[workspace.metadata.cargo-shear]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid"]
|
||||
ignored = ["getrandom", "ruff_options_metadata", "uuid", "get-size2"]
|
||||
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
||||
@@ -500,6 +500,35 @@ OTHER = "OTHER"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/20035>
|
||||
#[test]
|
||||
fn deduplicate_directory_and_explicit_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root = tempdir.path();
|
||||
|
||||
let main = root.join("main.py");
|
||||
fs::write(&main, "x = 1\n")?;
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(root)
|
||||
.args(["format", "--no-cache", "--check"])
|
||||
.arg(".")
|
||||
.arg("main.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
Would reformat: main.py
|
||||
1 file would be reformatted
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_error() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -271,6 +271,50 @@ OTHER = "OTHER"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/ruff/issues/20035>
|
||||
#[test]
|
||||
fn deduplicate_directory_and_explicit_file() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let root = tempdir.path();
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
exclude = ["main.py"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let main = root.join("main.py");
|
||||
fs::write(&main, "import os\n")?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.current_dir(root)
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", &ruff_toml.file_name().unwrap().to_string_lossy()])
|
||||
.arg(".")
|
||||
// Explicitly pass main.py, should be linted regardless of it being excluded by lint.exclude
|
||||
.arg("main.py"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
main.py:1:8: F401 [*] `os` imported but unused
|
||||
Found 1 error.
|
||||
[*] 1 fixable with the `--fix` option.
|
||||
|
||||
----- stderr -----
|
||||
"
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exclude_stdin() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1196px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.fg-yellow { fill: #AA5500 }
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<svg width="1196px" height="164px" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.fg { fill: #AAAAAA }
|
||||
.bg { background: #000000 }
|
||||
.bg { fill: #000000 }
|
||||
.fg-bright-blue { fill: #5555FF }
|
||||
.fg-bright-red { fill: #FF5555 }
|
||||
.container {
|
||||
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
500,
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -504,8 +504,8 @@ impl ToOwned for SystemPath {
|
||||
pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for SystemPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
|
||||
(self.0.capacity(), tracker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ impl ToOwned for VendoredPath {
|
||||
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for VendoredPathBuf {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
self.0.capacity()
|
||||
fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
|
||||
(self.0.capacity(), tracker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
104
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC240.py
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
## Valid cases:
|
||||
|
||||
def os_path_in_foo():
|
||||
file = "file.txt"
|
||||
|
||||
os.path.abspath(file) # OK
|
||||
os.path.exists(file) # OK
|
||||
os.path.split() # OK
|
||||
|
||||
async def non_io_os_path_methods():
|
||||
os.path.split() # OK
|
||||
os.path.dirname() # OK
|
||||
os.path.basename() # OK
|
||||
os.path.join() # OK
|
||||
|
||||
def pathlib_path_in_foo():
|
||||
path = Path("src/my_text.txt") # OK
|
||||
path.exists() # OK
|
||||
with path.open() as f: # OK
|
||||
...
|
||||
path = Path("src/my_text.txt").open() # OK
|
||||
|
||||
async def non_io_pathlib_path_methods():
|
||||
path = Path("src/my_text.txt")
|
||||
path.is_absolute() # OK
|
||||
path.is_relative_to() # OK
|
||||
path.as_posix() # OK
|
||||
path.relative_to() # OK
|
||||
|
||||
def inline_path_method_call():
|
||||
Path("src/my_text.txt").open() # OK
|
||||
Path("src/my_text.txt").open().flush() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def trio_path_in_foo():
|
||||
from trio import Path
|
||||
|
||||
path = Path("src/my_text.txt") # OK
|
||||
await path.absolute() # OK
|
||||
await path.exists() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def anyio_path_in_foo():
|
||||
from anyio import Path
|
||||
|
||||
path = Path("src/my_text.txt") # OK
|
||||
await path.absolute() # OK
|
||||
await path.exists() # OK
|
||||
with Path("src/my_text.txt").open() as f: # OK
|
||||
...
|
||||
|
||||
async def path_open_in_foo():
|
||||
path = Path("src/my_text.txt") # OK
|
||||
path.open() # OK, covered by ASYNC230
|
||||
|
||||
## Invalid cases:
|
||||
|
||||
async def os_path_in_foo():
|
||||
file = "file.txt"
|
||||
|
||||
os.path.abspath(file) # ASYNC240
|
||||
os.path.exists(file) # ASYNC240
|
||||
|
||||
async def pathlib_path_in_foo():
|
||||
path = Path("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def pathlib_path_in_foo():
|
||||
import pathlib
|
||||
|
||||
path = pathlib.Path("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def inline_path_method_call():
|
||||
Path("src/my_text.txt").exists() # ASYNC240
|
||||
Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
|
||||
async def aliased_path_in_foo():
|
||||
from pathlib import Path as PathAlias
|
||||
|
||||
path = PathAlias("src/my_text.txt")
|
||||
path.exists() # ASYNC240
|
||||
|
||||
global_path = Path("src/my_text.txt")
|
||||
|
||||
async def global_path_in_foo():
|
||||
global_path.exists() # ASYNC240
|
||||
|
||||
async def path_as_simple_parameter_type(path: Path):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def path_as_union_parameter_type(path: Path | None):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
path.exists() # ASYNC240
|
||||
|
||||
|
||||
@@ -52,3 +52,21 @@ class A:
|
||||
|
||||
assert hasattr(A(), "__call__")
|
||||
assert callable(A()) is False
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20440
|
||||
def test_invalid_hasattr_calls():
|
||||
hasattr(0, "__call__", 0) # 3 args - invalid
|
||||
hasattr(0, "__call__", x=0) # keyword arg - invalid
|
||||
hasattr(0, "__call__", 0, x=0) # 3 args + keyword - invalid
|
||||
hasattr() # no args - invalid
|
||||
hasattr(0) # 1 arg - invalid
|
||||
hasattr(*(), "__call__", "extra") # unpacking - invalid
|
||||
hasattr(*()) # unpacking - invalid
|
||||
|
||||
def test_invalid_getattr_calls():
|
||||
getattr(0, "__call__", None, "extra") # 4 args - invalid
|
||||
getattr(0, "__call__", default=None) # keyword arg - invalid
|
||||
getattr() # no args - invalid
|
||||
getattr(0) # 1 arg - invalid
|
||||
getattr(*(), "__call__", None, "extra") # unpacking - invalid
|
||||
getattr(*()) # unpacking - invalid
|
||||
|
||||
33
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
from itertools import count, cycle, repeat
|
||||
|
||||
# Errors
|
||||
map(lambda x: x, [1, 2, 3])
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
||||
# Errors (limited iterators).
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
|
||||
import builtins
|
||||
# Still an error even though it uses the qualified name
|
||||
builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
|
||||
# OK
|
||||
map(lambda x: x, [1, 2, 3], strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=True)
|
||||
map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True), strict=True)
|
||||
|
||||
# OK (single iterable - no strict required)
|
||||
map(lambda x: x, [1, 2, 3])
|
||||
|
||||
# OK (infinite iterators)
|
||||
map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3]))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1))
|
||||
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None))
|
||||
map(lambda x, y: x + y, [1, 2, 3], count())
|
||||
@@ -19,3 +19,18 @@ class MyClass:
|
||||
|
||||
def attribute_usage(self) -> id:
|
||||
pass
|
||||
|
||||
|
||||
class C:
|
||||
@staticmethod
|
||||
def property(f):
|
||||
return f
|
||||
|
||||
id = 1
|
||||
|
||||
@[property][0]
|
||||
def f(self, x=[id]):
|
||||
return x
|
||||
|
||||
bin = 2
|
||||
foo = [bin]
|
||||
|
||||
@@ -42,3 +42,6 @@ tuple(
|
||||
x for x in [1,2,3]
|
||||
}
|
||||
)
|
||||
|
||||
t9 = tuple([1],)
|
||||
t10 = tuple([1, 2],)
|
||||
|
||||
@@ -170,3 +170,4 @@ print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
# leading/trailing whitespace should not count towards maxsplit
|
||||
" a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
" a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
"a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
||||
@@ -287,3 +287,19 @@ class C(B):
|
||||
def f(self):
|
||||
C = B # Local variable C shadows the class name
|
||||
return super(C, self).f() # Should NOT trigger UP008
|
||||
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/20491
|
||||
# UP008 should not apply when __class__ is a local variable
|
||||
class A:
|
||||
def f(self):
|
||||
return 1
|
||||
|
||||
class B(A):
|
||||
def f(self):
|
||||
return 2
|
||||
|
||||
class C(B):
|
||||
def f(self):
|
||||
__class__ = B # Local variable __class__ shadows the implicit __class__
|
||||
return super(__class__, self).f() # Should NOT trigger UP008
|
||||
|
||||
39
crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import logging
|
||||
|
||||
# %s + str()
|
||||
logging.info("Hello %s", str("World!"))
|
||||
logging.log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
||||
# %s + repr()
|
||||
logging.info("Hello %s", repr("World!"))
|
||||
logging.log(logging.INFO, "Hello %s", repr("World!"))
|
||||
|
||||
# %r + str()
|
||||
logging.info("Hello %r", str("World!"))
|
||||
logging.log(logging.INFO, "Hello %r", str("World!"))
|
||||
|
||||
# %r + repr()
|
||||
logging.info("Hello %r", repr("World!"))
|
||||
logging.log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
||||
from logging import info, log
|
||||
|
||||
# %s + str()
|
||||
info("Hello %s", str("World!"))
|
||||
log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
||||
# %s + repr()
|
||||
info("Hello %s", repr("World!"))
|
||||
log(logging.INFO, "Hello %s", repr("World!"))
|
||||
|
||||
# %r + str()
|
||||
info("Hello %r", str("World!"))
|
||||
log(logging.INFO, "Hello %r", str("World!"))
|
||||
|
||||
# %r + repr()
|
||||
info("Hello %r", repr("World!"))
|
||||
log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
||||
def str(s): return f"str = {s}"
|
||||
# Don't flag this
|
||||
logging.info("Hello %s", str("World!"))
|
||||
@@ -669,6 +669,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::BlockingOpenCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_open_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingPathMethodInAsyncFunction) {
|
||||
flake8_async::rules::blocking_os_path(checker, call);
|
||||
}
|
||||
if checker.any_rule_enabled(&[
|
||||
Rule::CreateSubprocessInAsyncFunction,
|
||||
Rule::RunProcessInAsyncFunction,
|
||||
@@ -717,7 +720,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_bugbear::rules::re_sub_positional_args(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UnreliableCallableCheck) {
|
||||
flake8_bugbear::rules::unreliable_callable_check(checker, expr, func, args);
|
||||
flake8_bugbear::rules::unreliable_callable_check(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StripWithMultiCharacters) {
|
||||
flake8_bugbear::rules::strip_with_multi_characters(checker, expr, func, args);
|
||||
@@ -741,6 +746,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_bugbear::rules::zip_without_explicit_strict(checker, call);
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::MapWithoutExplicitStrict) {
|
||||
if checker.target_version() >= PythonVersion::PY314 {
|
||||
flake8_bugbear::rules::map_without_explicit_strict(checker, call);
|
||||
}
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::NoExplicitStacklevel) {
|
||||
flake8_bugbear::rules::no_explicit_stacklevel(checker, call);
|
||||
}
|
||||
@@ -1279,6 +1289,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::UnnecessaryEmptyIterableWithinDequeCall) {
|
||||
ruff::rules::unnecessary_literal_within_deque_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::LoggingEagerConversion) {
|
||||
ruff::rules::logging_eager_conversion(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StarmapZip) {
|
||||
ruff::rules::starmap_zip(checker, call);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ use ruff_python_semantic::{
|
||||
Import, Module, ModuleKind, ModuleSource, NodeId, ScopeId, ScopeKind, SemanticModel,
|
||||
SemanticModelFlags, StarImport, SubmoduleImport,
|
||||
};
|
||||
use ruff_python_stdlib::builtins::{MAGIC_GLOBALS, python_builtins};
|
||||
use ruff_python_stdlib::builtins::{python_builtins, python_magic_globals};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::{OneIndexed, SourceFile, SourceFileBuilder, SourceRow};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
@@ -2550,7 +2550,7 @@ impl<'a> Checker<'a> {
|
||||
for builtin in standard_builtins {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in MAGIC_GLOBALS {
|
||||
for builtin in python_magic_globals(target_version.minor) {
|
||||
bind_builtin(builtin);
|
||||
}
|
||||
for builtin in &settings.builtins {
|
||||
|
||||
@@ -341,6 +341,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
|
||||
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
|
||||
(Flake8Async, "230") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingOpenCallInAsyncFunction),
|
||||
(Flake8Async, "240") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingPathMethodInAsyncFunction),
|
||||
(Flake8Async, "250") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingInputInAsyncFunction),
|
||||
(Flake8Async, "251") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingSleepInAsyncFunction),
|
||||
|
||||
@@ -394,6 +395,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
|
||||
(Flake8Bugbear, "911") => (RuleGroup::Stable, rules::flake8_bugbear::rules::BatchedWithoutExplicitStrict),
|
||||
(Flake8Bugbear, "912") => (RuleGroup::Preview, rules::flake8_bugbear::rules::MapWithoutExplicitStrict),
|
||||
|
||||
// flake8-blind-except
|
||||
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
||||
@@ -1051,6 +1053,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
|
||||
(Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
|
||||
(Ruff, "065") => (RuleGroup::Preview, rules::ruff::rules::LoggingEagerConversion),
|
||||
|
||||
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
|
||||
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
|
||||
(Ruff, "102") => (RuleGroup::Preview, rules::ruff::rules::InvalidRuleCode),
|
||||
|
||||
@@ -65,7 +65,7 @@ pub(crate) fn remove_imports<'a>(
|
||||
if member == "*" {
|
||||
found_star = true;
|
||||
} else {
|
||||
bail!("Expected \"*\" for unused import (got: \"{}\")", member);
|
||||
bail!("Expected \"*\" for unused import (got: \"{member}\")");
|
||||
}
|
||||
}
|
||||
if !found_star {
|
||||
|
||||
@@ -228,3 +228,10 @@ pub(crate) const fn is_sim910_expanded_key_support_enabled(settings: &LinterSett
|
||||
pub(crate) const fn is_fix_builtin_open_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20178
|
||||
pub(crate) const fn is_a003_class_scope_shadowing_expansion_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ mod tests {
|
||||
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::BlockingOpenCallInAsyncFunction, Path::new("ASYNC230.py"))]
|
||||
#[test_case(Rule::BlockingPathMethodInAsyncFunction, Path::new("ASYNC240.py"))]
|
||||
#[test_case(Rule::BlockingInputInAsyncFunction, Path::new("ASYNC250.py"))]
|
||||
#[test_case(Rule::BlockingSleepInAsyncFunction, Path::new("ASYNC251.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_union_and_optional};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not call blocking `os.path` or `pathlib.Path`
|
||||
/// methods.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Calling some `os.path` or `pathlib.Path` methods in an async function will block
|
||||
/// the entire event loop, preventing it from executing other tasks while waiting
|
||||
/// for the operation. This negates the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead, use the methods' async equivalents from `trio.Path` or `anyio.Path`.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import os
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = "my_file.txt"
|
||||
/// file_exists = os.path.exists(path)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import trio
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = trio.Path("my_file.txt")
|
||||
/// file_exists = await path.exists()
|
||||
/// ```
|
||||
///
|
||||
/// Non-blocking methods are OK to use:
|
||||
/// ```python
|
||||
/// import pathlib
|
||||
///
|
||||
///
|
||||
/// async def func():
|
||||
/// path = pathlib.Path("my_file.txt")
|
||||
/// file_dirname = path.dirname()
|
||||
/// new_path = os.path.join("/tmp/src/", path)
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingPathMethodInAsyncFunction {
|
||||
path_library: String,
|
||||
}
|
||||
|
||||
impl Violation for BlockingPathMethodInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Async functions should not use {path_library} methods, use trio.Path or anyio.path",
|
||||
path_library = self.path_library
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC240
|
||||
pub(crate) fn blocking_os_path(checker: &Checker, call: &ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
if !semantic.in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an expression is calling I/O related os.path method.
|
||||
// Just initializing pathlib.Path object is OK, we can return
|
||||
// early in that scenario.
|
||||
if let Some(qualified_name) = semantic.resolve_qualified_name(call.func.as_ref()) {
|
||||
let segments = qualified_name.segments();
|
||||
if !matches!(segments, ["os", "path", _]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(os_path_method) = segments.last() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if maybe_calling_io_operation(os_path_method) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "os.path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = call.func.as_attribute_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !maybe_calling_io_operation(attr.id.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an expression is a pathlib.Path constructor that directly
|
||||
// calls an I/O method.
|
||||
if PathlibPathChecker::match_initializer(value, semantic) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "pathlib.Path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lastly, check if a variable is a pathlib.Path instance and it's
|
||||
// calling an I/O method.
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if check_type::<PathlibPathChecker>(binding, semantic) {
|
||||
checker.report_diagnostic(
|
||||
BlockingPathMethodInAsyncFunction {
|
||||
path_library: "pathlib.Path".to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
struct PathlibPathChecker;
|
||||
|
||||
impl PathlibPathChecker {
|
||||
fn is_pathlib_path_constructor(
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
expr: &Expr,
|
||||
) -> bool {
|
||||
let Some(qualified_name) = semantic.resolve_qualified_name(expr) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
[
|
||||
"pathlib",
|
||||
"Path"
|
||||
| "PosixPath"
|
||||
| "PurePath"
|
||||
| "PurePosixPath"
|
||||
| "PureWindowsPath"
|
||||
| "WindowsPath"
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeChecker for PathlibPathChecker {
|
||||
fn match_annotation(annotation: &Expr, semantic: &ruff_python_semantic::SemanticModel) -> bool {
|
||||
if Self::is_pathlib_path_constructor(semantic, annotation) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut found = false;
|
||||
traverse_union_and_optional(
|
||||
&mut |inner_expr, _| {
|
||||
if Self::is_pathlib_path_constructor(semantic, inner_expr) {
|
||||
found = true;
|
||||
}
|
||||
},
|
||||
semantic,
|
||||
annotation,
|
||||
);
|
||||
found
|
||||
}
|
||||
|
||||
fn match_initializer(
|
||||
initializer: &Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = initializer else {
|
||||
return false;
|
||||
};
|
||||
|
||||
Self::is_pathlib_path_constructor(semantic, func)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_calling_io_operation(attr: &str) -> bool {
|
||||
// ".open()" is added to the allow list to let ASYNC 230 handle
|
||||
// that case.
|
||||
!matches!(
|
||||
attr,
|
||||
"ALLOW_MISSING"
|
||||
| "altsep"
|
||||
| "anchor"
|
||||
| "as_posix"
|
||||
| "as_uri"
|
||||
| "basename"
|
||||
| "commonpath"
|
||||
| "commonprefix"
|
||||
| "curdir"
|
||||
| "defpath"
|
||||
| "devnull"
|
||||
| "dirname"
|
||||
| "drive"
|
||||
| "expandvars"
|
||||
| "extsep"
|
||||
| "genericpath"
|
||||
| "is_absolute"
|
||||
| "is_relative_to"
|
||||
| "is_reserved"
|
||||
| "isabs"
|
||||
| "join"
|
||||
| "joinpath"
|
||||
| "match"
|
||||
| "name"
|
||||
| "normcase"
|
||||
| "os"
|
||||
| "open"
|
||||
| "pardir"
|
||||
| "parent"
|
||||
| "parents"
|
||||
| "parts"
|
||||
| "pathsep"
|
||||
| "relative_to"
|
||||
| "root"
|
||||
| "samestat"
|
||||
| "sep"
|
||||
| "split"
|
||||
| "splitdrive"
|
||||
| "splitext"
|
||||
| "splitroot"
|
||||
| "stem"
|
||||
| "suffix"
|
||||
| "suffixes"
|
||||
| "supports_unicode_filenames"
|
||||
| "sys"
|
||||
| "with_name"
|
||||
| "with_segments"
|
||||
| "with_stem"
|
||||
| "with_suffix"
|
||||
)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub(crate) use blocking_http_call::*;
|
||||
pub(crate) use blocking_http_call_httpx::*;
|
||||
pub(crate) use blocking_input::*;
|
||||
pub(crate) use blocking_open_call::*;
|
||||
pub(crate) use blocking_path_methods::*;
|
||||
pub(crate) use blocking_process_invocation::*;
|
||||
pub(crate) use blocking_sleep::*;
|
||||
pub(crate) use cancel_scope_no_checkpoint::*;
|
||||
@@ -18,6 +19,7 @@ mod blocking_http_call;
|
||||
mod blocking_http_call_httpx;
|
||||
mod blocking_input;
|
||||
mod blocking_open_call;
|
||||
mod blocking_path_methods;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
mod cancel_scope_no_checkpoint;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:67:5
|
||||
|
|
||||
65 | file = "file.txt"
|
||||
66 |
|
||||
67 | os.path.abspath(file) # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^
|
||||
68 | os.path.exists(file) # ASYNC240
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use os.path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:68:5
|
||||
|
|
||||
67 | os.path.abspath(file) # ASYNC240
|
||||
68 | os.path.exists(file) # ASYNC240
|
||||
| ^^^^^^^^^^^^^^
|
||||
69 |
|
||||
70 | async def pathlib_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:72:5
|
||||
|
|
||||
70 | async def pathlib_path_in_foo():
|
||||
71 | path = Path("src/my_text.txt")
|
||||
72 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
73 |
|
||||
74 | async def pathlib_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:78:5
|
||||
|
|
||||
77 | path = pathlib.Path("src/my_text.txt")
|
||||
78 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
79 |
|
||||
80 | async def inline_path_method_call():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:81:5
|
||||
|
|
||||
80 | async def inline_path_method_call():
|
||||
81 | Path("src/my_text.txt").exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 | Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:82:5
|
||||
|
|
||||
80 | async def inline_path_method_call():
|
||||
81 | Path("src/my_text.txt").exists() # ASYNC240
|
||||
82 | Path("src/my_text.txt").absolute().exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
83 |
|
||||
84 | async def aliased_path_in_foo():
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:88:5
|
||||
|
|
||||
87 | path = PathAlias("src/my_text.txt")
|
||||
88 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
89 |
|
||||
90 | global_path = Path("src/my_text.txt")
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:93:5
|
||||
|
|
||||
92 | async def global_path_in_foo():
|
||||
93 | global_path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
94 |
|
||||
95 | async def path_as_simple_parameter_type(path: Path):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:96:5
|
||||
|
|
||||
95 | async def path_as_simple_parameter_type(path: Path):
|
||||
96 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
97 |
|
||||
98 | async def path_as_union_parameter_type(path: Path | None):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:99:5
|
||||
|
|
||||
98 | async def path_as_union_parameter_type(path: Path | None):
|
||||
99 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
100 |
|
||||
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
|
|
||||
|
||||
ASYNC240 Async functions should not use pathlib.Path methods, use trio.Path or anyio.path
|
||||
--> ASYNC240.py:102:5
|
||||
|
|
||||
101 | async def path_as_optional_parameter_type(path: Optional[Path]):
|
||||
102 | path.exists() # ASYNC240
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
@@ -4,6 +4,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
|
||||
/// Return `true` if the statement containing the current expression is the last
|
||||
/// top-level expression in the cell. This assumes that the source is a Jupyter
|
||||
@@ -27,3 +28,54 @@ pub(super) fn at_last_top_level_expression_in_cell(
|
||||
.all(|token| token.kind() == SimpleTokenKind::Semi || token.kind().is_trivia())
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||
/// `itertools.cycle` or similar).
|
||||
pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| match qualified_name.segments() {
|
||||
["itertools", "cycle" | "count"] => true,
|
||||
["itertools", "repeat"] => {
|
||||
// Ex) `itertools.repeat(1)`
|
||||
if keywords.is_empty() && args.len() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times")
|
||||
&& keyword.value.is_none_literal_expr()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if any expression in the iterator appears to be an infinite iterator.
|
||||
pub(crate) fn any_infinite_iterables<'a>(
|
||||
iter: impl IntoIterator<Item = &'a Expr>,
|
||||
semantic: &SemanticModel,
|
||||
) -> bool {
|
||||
iter.into_iter()
|
||||
.any(|arg| is_infinite_iterable(arg, semantic))
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ mod tests {
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
#[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))]
|
||||
@@ -81,11 +82,35 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
unresolved_target_version: PythonVersion::PY314.into(),
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::ClassAsDataStructure,
|
||||
Path::new("class_as_data_structure.py"),
|
||||
PythonVersion::PY39
|
||||
)]
|
||||
#[test_case(
|
||||
Rule::MapWithoutExplicitStrict,
|
||||
Path::new("B912.py"),
|
||||
PythonVersion::PY313
|
||||
)]
|
||||
fn rules_with_target_version(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::ExprCall;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_bugbear::rules::is_infinite_iterable;
|
||||
use crate::rules::flake8_bugbear::helpers::is_infinite_iterable;
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::add_argument;
|
||||
use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables.
|
||||
///
|
||||
/// This rule applies to Python 3.14 and later, where `map` accepts a `strict` keyword
|
||||
/// argument. For details, see: [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// By default, if the iterables passed to `map` are of different lengths, the
|
||||
/// resulting iterator will be silently truncated to the length of the shortest
|
||||
/// iterable. This can lead to subtle bugs.
|
||||
///
|
||||
/// Pass `strict=True` to raise a `ValueError` if the iterables are of
|
||||
/// non-uniform length. Alternatively, if the iterables are deliberately of
|
||||
/// different lengths, pass `strict=False` to make the intention explicit.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// map(f, a, b)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// map(f, a, b, strict=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe for `map` calls that contain
|
||||
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead
|
||||
/// to a duplicate keyword argument error.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map)
|
||||
/// - [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MapWithoutExplicitStrict;
|
||||
|
||||
impl AlwaysFixableViolation for MapWithoutExplicitStrict {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`map()` without an explicit `strict=` parameter".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add explicit value for parameter `strict=`".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// B912
|
||||
pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "map")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& call.arguments.args.len() >= 3 // function + at least 2 iterables
|
||||
&& !any_infinite_iterables(call.arguments.args.iter().skip(1), semantic)
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
// If the function call contains `**kwargs`, mark the fix as unsafe.
|
||||
if call
|
||||
.arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub(crate) use getattr_with_constant::*;
|
||||
pub(crate) use jump_statement_in_finally::*;
|
||||
pub(crate) use loop_iterator_mutation::*;
|
||||
pub(crate) use loop_variable_overrides_iterator::*;
|
||||
pub(crate) use map_without_explicit_strict::*;
|
||||
pub(crate) use mutable_argument_default::*;
|
||||
pub(crate) use mutable_contextvar_default::*;
|
||||
pub(crate) use no_explicit_stacklevel::*;
|
||||
@@ -56,6 +57,7 @@ mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_iterator_mutation;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod map_without_explicit_strict;
|
||||
mod mutable_argument_default;
|
||||
mod mutable_contextvar_default;
|
||||
mod no_explicit_stacklevel;
|
||||
|
||||
@@ -90,7 +90,11 @@ pub(crate) fn unreliable_callable_check(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[ast::Keyword],
|
||||
) {
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
@@ -103,7 +107,21 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Some(builtins_function) = checker.semantic().resolve_builtin_symbol(func) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(builtins_function, "hasattr" | "getattr") {
|
||||
|
||||
// Validate function arguments based on function name
|
||||
let valid_args = match builtins_function {
|
||||
"hasattr" => {
|
||||
// hasattr should have exactly 2 positional arguments and no keywords
|
||||
args.len() == 2
|
||||
}
|
||||
"getattr" => {
|
||||
// getattr should have 2 or 3 positional arguments and no keywords
|
||||
args.len() == 2 || args.len() == 3
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !valid_args {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::add_argument;
|
||||
use crate::rules::flake8_bugbear::helpers::any_infinite_iterables;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Fix};
|
||||
|
||||
/// ## What it does
|
||||
@@ -57,11 +57,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
|
||||
if semantic.match_builtin_expr(&call.func, "zip")
|
||||
&& call.arguments.find_keyword("strict").is_none()
|
||||
&& !call
|
||||
.arguments
|
||||
.args
|
||||
.iter()
|
||||
.any(|arg| is_infinite_iterable(arg, semantic))
|
||||
&& !any_infinite_iterables(call.arguments.args.iter(), semantic)
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(ZipWithoutExplicitStrict, call.range())
|
||||
@@ -86,47 +82,3 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||
/// `itertools.cycle` or similar).
|
||||
pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||
let Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments: Arguments { args, keywords, .. },
|
||||
..
|
||||
}) = &arg
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| {
|
||||
match qualified_name.segments() {
|
||||
["itertools", "cycle" | "count"] => true,
|
||||
["itertools", "repeat"] => {
|
||||
// Ex) `itertools.repeat(1)`
|
||||
if keywords.is_empty() && args.len() == 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `itertools.repeat(1, None)`
|
||||
if args.len() == 2 && args[1].is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `iterools.repeat(1, times=None)`
|
||||
for keyword in keywords {
|
||||
if keyword.arg.as_ref().is_some_and(|name| name == "times") {
|
||||
if keyword.value.is_none_literal_expr() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,4 +156,6 @@ help: Replace with `callable()`
|
||||
- assert hasattr(A(), "__call__")
|
||||
53 + assert callable(A())
|
||||
54 | assert callable(A()) is False
|
||||
55 |
|
||||
56 | # https://github.com/astral-sh/ruff/issues/20440
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:5:1
|
||||
|
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
2 |
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
5 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:6:1
|
||||
|
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
3 | # Errors
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
- map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
6 + map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9], strict=False)
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:7:1
|
||||
|
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
4 | map(lambda x: x, [1, 2, 3])
|
||||
5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
8 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:9:1
|
||||
|
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
|
||||
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
|
||||
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
|
||||
- map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
9 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True), strict=False)
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:12:1
|
||||
|
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
- map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
12 + map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1), strict=False)
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
14 |
|
||||
15 | import builtins
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:13:1
|
||||
|
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
14 |
|
||||
15 | import builtins
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
10 |
|
||||
11 | # Errors (limited iterators).
|
||||
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
|
||||
- map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
|
||||
13 + map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4), strict=False)
|
||||
14 |
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
|
||||
B912 [*] `map()` without an explicit `strict=` parameter
|
||||
--> B912.py:17:1
|
||||
|
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
17 | builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
18 |
|
||||
19 | # OK
|
||||
|
|
||||
help: Add explicit value for parameter `strict=`
|
||||
14 |
|
||||
15 | import builtins
|
||||
16 | # Still an error even though it uses the qualified name
|
||||
- builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6])
|
||||
17 + builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False)
|
||||
18 |
|
||||
19 | # OK
|
||||
20 | map(lambda x: x, [1, 2, 3], strict=True)
|
||||
@@ -14,6 +14,7 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_builtins;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::{test_path, test_resource_path};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
@@ -63,6 +64,28 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_builtins").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
flake8_builtins: flake8_builtins::settings::Settings {
|
||||
strict_checking: true,
|
||||
..Default::default()
|
||||
},
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::StdlibModuleShadowing,
|
||||
Path::new("A005/modules/utils/logging.py"),
|
||||
|
||||
@@ -6,6 +6,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_a003_class_scope_shadowing_expansion_enabled;
|
||||
use crate::rules::flake8_builtins::helpers::shadows_builtin;
|
||||
|
||||
/// ## What it does
|
||||
@@ -123,16 +124,26 @@ pub(crate) fn builtin_attribute_shadowing(
|
||||
// def repeat(value: int, times: int) -> list[int]:
|
||||
// return [value] * times
|
||||
// ```
|
||||
// In stable, only consider references whose first non-type parent scope is the class
|
||||
// scope (e.g., decorators, default args, and attribute initializers).
|
||||
// In preview, also consider references from within the class scope.
|
||||
let consider_reference = |reference_scope_id: ScopeId| {
|
||||
if is_a003_class_scope_shadowing_expansion_enabled(checker.settings()) {
|
||||
if reference_scope_id == scope_id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
checker
|
||||
.semantic()
|
||||
.first_non_type_parent_scope_id(reference_scope_id)
|
||||
== Some(scope_id)
|
||||
};
|
||||
|
||||
for reference in binding
|
||||
.references
|
||||
.iter()
|
||||
.map(|reference_id| checker.semantic().reference(*reference_id))
|
||||
.filter(|reference| {
|
||||
checker
|
||||
.semantic()
|
||||
.first_non_type_parent_scope_id(reference.scope_id())
|
||||
== Some(scope_id)
|
||||
})
|
||||
.filter(|reference| consider_reference(reference.scope_id()))
|
||||
{
|
||||
checker.report_diagnostic(
|
||||
BuiltinAttributeShadowing {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A003 Python builtin is shadowed by method `str` from line 14
|
||||
--> A003.py:17:31
|
||||
|
|
||||
15 | pass
|
||||
16 |
|
||||
17 | def method_usage(self) -> str:
|
||||
| ^^^
|
||||
18 | pass
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `id` from line 3
|
||||
--> A003.py:20:34
|
||||
|
|
||||
18 | pass
|
||||
19 |
|
||||
20 | def attribute_usage(self) -> id:
|
||||
| ^^
|
||||
21 | pass
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by method `property` from line 26
|
||||
--> A003.py:31:7
|
||||
|
|
||||
29 | id = 1
|
||||
30 |
|
||||
31 | @[property][0]
|
||||
| ^^^^^^^^
|
||||
32 | def f(self, x=[id]):
|
||||
33 | return x
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `id` from line 29
|
||||
--> A003.py:32:20
|
||||
|
|
||||
31 | @[property][0]
|
||||
32 | def f(self, x=[id]):
|
||||
| ^^
|
||||
33 | return x
|
||||
|
|
||||
|
||||
A003 Python builtin is shadowed by class attribute `bin` from line 35
|
||||
--> A003.py:36:12
|
||||
|
|
||||
35 | bin = 2
|
||||
36 | foo = [bin]
|
||||
| ^^^
|
||||
|
|
||||
@@ -124,7 +124,7 @@ pub(crate) fn unnecessary_literal_within_tuple_call(
|
||||
let needs_trailing_comma = if let [item] = elts.as_slice() {
|
||||
SimpleTokenizer::new(
|
||||
checker.locator().contents(),
|
||||
TextRange::new(item.end(), call.end()),
|
||||
TextRange::new(item.end(), argument.end()),
|
||||
)
|
||||
.all(|token| token.kind != SimpleTokenKind::Comma)
|
||||
} else {
|
||||
|
||||
@@ -247,3 +247,36 @@ help: Rewrite as a tuple literal
|
||||
28 | tuple([x for x in range(5)])
|
||||
29 | tuple({x for x in range(10)})
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:46:6
|
||||
|
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
| ^^^^^^^^^^^
|
||||
47 | t10 = tuple([1, 2],)
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
43 | }
|
||||
44 | )
|
||||
45 |
|
||||
- t9 = tuple([1],)
|
||||
46 + t9 = (1,)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:47:7
|
||||
|
|
||||
46 | t9 = tuple([1],)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
- t10 = tuple([1, 2],)
|
||||
47 + t10 = (1, 2)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -344,3 +344,36 @@ help: Rewrite as a generator
|
||||
42 | x for x in [1,2,3]
|
||||
43 | }
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:46:6
|
||||
|
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
| ^^^^^^^^^^^
|
||||
47 | t10 = tuple([1, 2],)
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
43 | }
|
||||
44 | )
|
||||
45 |
|
||||
- t9 = tuple([1],)
|
||||
46 + t9 = (1,)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C409 [*] Unnecessary list literal passed to `tuple()` (rewrite as a tuple literal)
|
||||
--> C409.py:47:7
|
||||
|
|
||||
46 | t9 = tuple([1],)
|
||||
47 | t10 = tuple([1, 2],)
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Rewrite as a tuple literal
|
||||
44 | )
|
||||
45 |
|
||||
46 | t9 = tuple([1],)
|
||||
- t10 = tuple([1, 2],)
|
||||
47 + t10 = (1, 2)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use ruff_python_ast::InterpolatedStringElement;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
|
||||
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_f_string_logging_enabled;
|
||||
@@ -198,7 +199,7 @@ fn check_log_record_attr_clash(checker: &Checker, extra: &Keyword) {
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum LoggingCallType {
|
||||
pub(crate) enum LoggingCallType {
|
||||
/// Logging call with a level method, e.g., `logging.info`.
|
||||
LevelCall(LoggingLevel),
|
||||
/// Logging call with an integer level as an argument, e.g., `logger.log(level, ...)`.
|
||||
@@ -215,39 +216,41 @@ impl LoggingCallType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check logging calls for violations.
|
||||
pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
pub(crate) fn find_logging_call(
|
||||
checker: &Checker,
|
||||
call: &ast::ExprCall,
|
||||
) -> Option<(LoggingCallType, TextRange)> {
|
||||
// Determine the call type (e.g., `info` vs. `exception`) and the range of the attribute.
|
||||
let (logging_call_type, range) = match call.func.as_ref() {
|
||||
match call.func.as_ref() {
|
||||
Expr::Attribute(ast::ExprAttribute { value: _, attr, .. }) => {
|
||||
let Some(call_type) = LoggingCallType::from_attribute(attr.as_str()) else {
|
||||
return;
|
||||
};
|
||||
let call_type = LoggingCallType::from_attribute(attr.as_str())?;
|
||||
if !logging::is_logger_candidate(
|
||||
&call.func,
|
||||
checker.semantic(),
|
||||
&checker.settings().logger_objects,
|
||||
) {
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
(call_type, attr.range())
|
||||
Some((call_type, attr.range()))
|
||||
}
|
||||
Expr::Name(_) => {
|
||||
let Some(qualified_name) = checker
|
||||
let qualified_name = checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(call.func.as_ref())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
.resolve_qualified_name(call.func.as_ref())?;
|
||||
let ["logging", attribute] = qualified_name.segments() else {
|
||||
return;
|
||||
return None;
|
||||
};
|
||||
let Some(call_type) = LoggingCallType::from_attribute(attribute) else {
|
||||
return;
|
||||
};
|
||||
(call_type, call.func.range())
|
||||
let call_type = LoggingCallType::from_attribute(attribute)?;
|
||||
Some((call_type, call.func.range()))
|
||||
}
|
||||
_ => return,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check logging calls for violations.
|
||||
pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
let Some((logging_call_type, range)) = find_logging_call(checker, call) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// G001, G002, G003, G004
|
||||
|
||||
@@ -58,8 +58,8 @@ enum Method {
|
||||
}
|
||||
|
||||
impl Method {
|
||||
fn is_split(self) -> bool {
|
||||
matches!(self, Method::Split)
|
||||
fn is_rsplit(self) -> bool {
|
||||
matches!(self, Method::RSplit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,65 +201,19 @@ fn split_default(
|
||||
method: Method,
|
||||
settings: &LinterSettings,
|
||||
) -> Option<Expr> {
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
// > no empty strings at the start or end if the string has leading or trailing whitespace.
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
let string_val = str_value.to_str();
|
||||
match max_split.cmp(&0) {
|
||||
Ordering::Greater => {
|
||||
if !is_maxsplit_without_separator_fix_enabled(settings) {
|
||||
return None;
|
||||
}
|
||||
Ordering::Greater if !is_maxsplit_without_separator_fix_enabled(settings) => None,
|
||||
Ordering::Greater | Ordering::Equal => {
|
||||
let Ok(max_split) = usize::try_from(max_split) else {
|
||||
return None;
|
||||
};
|
||||
let list_items: Vec<&str> = if method.is_split() {
|
||||
string_val
|
||||
.trim_start_matches(py_unicode_is_whitespace)
|
||||
.splitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect()
|
||||
} else {
|
||||
let mut items: Vec<&str> = string_val
|
||||
.trim_end_matches(py_unicode_is_whitespace)
|
||||
.rsplitn(max_split + 1, py_unicode_is_whitespace)
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
items.reverse();
|
||||
items
|
||||
};
|
||||
let list_items = split_whitespace_with_maxsplit(string_val, max_split, method);
|
||||
Some(construct_replacement(
|
||||
&list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Behavior for maxsplit = 0 when sep is None:
|
||||
// - If the string is empty or all whitespace, result is [].
|
||||
// - Otherwise:
|
||||
// - " x ".split(maxsplit=0) -> ['x ']
|
||||
// - " x ".rsplit(maxsplit=0) -> [' x']
|
||||
// - "".split(maxsplit=0) -> []
|
||||
// - " ".split(maxsplit=0) -> []
|
||||
let processed_str = if method.is_split() {
|
||||
string_val.trim_start_matches(py_unicode_is_whitespace)
|
||||
} else {
|
||||
string_val.trim_end_matches(py_unicode_is_whitespace)
|
||||
};
|
||||
let list_items: &[_] = if processed_str.is_empty() {
|
||||
&[]
|
||||
} else {
|
||||
&[processed_str]
|
||||
};
|
||||
Some(construct_replacement(
|
||||
list_items,
|
||||
str_value.first_literal_flags(),
|
||||
))
|
||||
}
|
||||
Ordering::Less => {
|
||||
let list_items: Vec<&str> = string_val
|
||||
.split(py_unicode_is_whitespace)
|
||||
@@ -367,3 +321,107 @@ const fn py_unicode_is_whitespace(ch: char) -> bool {
|
||||
| '\u{3000}'
|
||||
)
|
||||
}
|
||||
|
||||
struct WhitespaceMaxSplitIterator<'a> {
|
||||
remaining: &'a str,
|
||||
max_split: usize,
|
||||
splits: usize,
|
||||
method: Method,
|
||||
}
|
||||
|
||||
impl<'a> WhitespaceMaxSplitIterator<'a> {
|
||||
fn new(s: &'a str, max_split: usize, method: Method) -> Self {
|
||||
let remaining = match method {
|
||||
Method::Split => s.trim_start_matches(py_unicode_is_whitespace),
|
||||
Method::RSplit => s.trim_end_matches(py_unicode_is_whitespace),
|
||||
};
|
||||
|
||||
Self {
|
||||
remaining,
|
||||
max_split,
|
||||
splits: 0,
|
||||
method,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WhitespaceMaxSplitIterator<'a> {
|
||||
type Item = &'a str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.remaining.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.splits >= self.max_split {
|
||||
let result = self.remaining;
|
||||
self.remaining = "";
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
self.splits += 1;
|
||||
match self.method {
|
||||
Method::Split => match self.remaining.split_once(py_unicode_is_whitespace) {
|
||||
Some((s, remaining)) => {
|
||||
self.remaining = remaining.trim_start_matches(py_unicode_is_whitespace);
|
||||
Some(s)
|
||||
}
|
||||
None => Some(std::mem::take(&mut self.remaining)),
|
||||
},
|
||||
Method::RSplit => match self.remaining.rsplit_once(py_unicode_is_whitespace) {
|
||||
Some((remaining, s)) => {
|
||||
self.remaining = remaining.trim_end_matches(py_unicode_is_whitespace);
|
||||
Some(s)
|
||||
}
|
||||
None => Some(std::mem::take(&mut self.remaining)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// From the Python documentation:
|
||||
// > If sep is not specified or is None, a different splitting algorithm is applied: runs of
|
||||
// > consecutive whitespace are regarded as a single separator, and the result will contain
|
||||
// > no empty strings at the start or end if the string has leading or trailing whitespace.
|
||||
// > Consequently, splitting an empty string or a string consisting of just whitespace with
|
||||
// > a None separator returns [].
|
||||
// https://docs.python.org/3/library/stdtypes.html#str.split
|
||||
fn split_whitespace_with_maxsplit(s: &str, max_split: usize, method: Method) -> Vec<&str> {
|
||||
let mut result: Vec<_> = WhitespaceMaxSplitIterator::new(s, max_split, method).collect();
|
||||
if method.is_rsplit() {
|
||||
result.reverse();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Method, split_whitespace_with_maxsplit};
|
||||
use test_case::test_case;
|
||||
|
||||
#[test_case(" ", 1, &[])]
|
||||
#[test_case("a b", 1, &["a", "b"])]
|
||||
#[test_case("a b", 2, &["a", "b"])]
|
||||
#[test_case(" a b c d ", 2, &["a", "b", "c d "])]
|
||||
#[test_case(" a b c ", 1, &["a", "b c "])]
|
||||
#[test_case(" x ", 0, &["x "])]
|
||||
#[test_case(" ", 0, &[])]
|
||||
#[test_case("a\u{3000}b", 1, &["a", "b"])]
|
||||
fn test_split_whitespace_with_maxsplit(s: &str, max_split: usize, expected: &[&str]) {
|
||||
let parts = split_whitespace_with_maxsplit(s, max_split, Method::Split);
|
||||
assert_eq!(parts, expected);
|
||||
}
|
||||
|
||||
#[test_case(" ", 1, &[])]
|
||||
#[test_case("a b", 1, &["a", "b"])]
|
||||
#[test_case("a b", 2, &["a", "b"])]
|
||||
#[test_case(" a b c d ", 2, &[" a b", "c", "d"])]
|
||||
#[test_case(" a b c ", 1, &[" a b", "c"])]
|
||||
#[test_case(" x ", 0, &[" x"])]
|
||||
#[test_case(" ", 0, &[])]
|
||||
#[test_case("a\u{3000}b", 1, &["a", "b"])]
|
||||
fn test_rsplit_whitespace_with_maxsplit(s: &str, max_split: usize, expected: &[&str]) {
|
||||
let parts = split_whitespace_with_maxsplit(s, max_split, Method::RSplit);
|
||||
assert_eq!(parts, expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1372,6 +1372,7 @@ SIM905 Consider using a list literal instead of `str.split`
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
||||
@@ -1382,5 +1383,16 @@ SIM905 Consider using a list literal instead of `str.rsplit`
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
||||
SIM905 Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:173:1
|
||||
|
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace with list literal
|
||||
|
||||
@@ -1420,6 +1420,7 @@ SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
|
||||
help: Replace with list literal
|
||||
168 | print("<\x1c\x1d\x1e\x1f".rsplit(maxsplit=0))
|
||||
@@ -1428,6 +1429,7 @@ help: Replace with list literal
|
||||
- " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
171 + ["a", "b", "c d "] # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
||||
SIM905 [*] Consider using a list literal instead of `str.rsplit`
|
||||
--> SIM905.py:172:1
|
||||
@@ -1436,6 +1438,7 @@ SIM905 [*] Consider using a list literal instead of `str.rsplit`
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
|
||||
help: Replace with list literal
|
||||
169 |
|
||||
@@ -1443,3 +1446,19 @@ help: Replace with list literal
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
- " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
172 + [" a b", "c", "d"] # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
|
||||
SIM905 [*] Consider using a list literal instead of `str.split`
|
||||
--> SIM905.py:173:1
|
||||
|
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
173 | "a b".split(maxsplit=1) # ["a", "b"]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Replace with list literal
|
||||
170 | # leading/trailing whitespace should not count towards maxsplit
|
||||
171 | " a b c d ".split(maxsplit=2) # ["a", "b", "c d "]
|
||||
172 | " a b c d ".rsplit(maxsplit=2) # [" a b", "c", "d"]
|
||||
- "a b".split(maxsplit=1) # ["a", "b"]
|
||||
173 + ["a", "b"] # ["a", "b"]
|
||||
|
||||
@@ -920,6 +920,42 @@ mod tests {
|
||||
flakes("__annotations__", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_warningregistry() {
|
||||
// Using __warningregistry__ should not be considered undefined.
|
||||
flakes("__warningregistry__", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_annotate_py314_available() {
|
||||
// __annotate__ is available starting in Python 3.14.
|
||||
let diagnostics = crate::test::test_snippet(
|
||||
"__annotate__",
|
||||
&crate::settings::LinterSettings {
|
||||
unresolved_target_version: ruff_python_ast::PythonVersion::PY314.into(),
|
||||
..crate::settings::LinterSettings::for_rules(vec![
|
||||
crate::codes::Rule::UndefinedName,
|
||||
])
|
||||
},
|
||||
);
|
||||
assert!(diagnostics.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_annotate_pre_py314_undefined() {
|
||||
// __annotate__ is not available before Python 3.14.
|
||||
let diagnostics = crate::test::test_snippet(
|
||||
"__annotate__",
|
||||
&crate::settings::LinterSettings {
|
||||
unresolved_target_version: ruff_python_ast::PythonVersion::PY313.into(),
|
||||
..crate::settings::LinterSettings::for_rules(vec![
|
||||
crate::codes::Rule::UndefinedName,
|
||||
])
|
||||
},
|
||||
);
|
||||
assert_eq!(diagnostics.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn magic_globals_file() {
|
||||
// Use of the C{__file__} magic global should not emit an undefined name
|
||||
|
||||
@@ -139,11 +139,11 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
|
||||
return;
|
||||
};
|
||||
|
||||
if !((first_arg_id == "__class__"
|
||||
|| (first_arg_id == parent_name.as_str()
|
||||
// If the first argument matches the class name, check if it's a local variable
|
||||
// that shadows the class name. If so, don't apply UP008.
|
||||
&& !checker.semantic().current_scope().has(first_arg_id)))
|
||||
// The `super(__class__, self)` and `super(ParentClass, self)` patterns are redundant in Python 3
|
||||
// when the first argument refers to the implicit `__class__` cell or to the enclosing class.
|
||||
// Avoid triggering if a local variable shadows either name.
|
||||
if !(((first_arg_id == "__class__") || (first_arg_id == parent_name.as_str()))
|
||||
&& !checker.semantic().current_scope().has(first_arg_id)
|
||||
&& second_arg_id == parent_arg.name().as_str())
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -293,7 +293,19 @@ fn handle_non_finite_float_special_case(
|
||||
return None;
|
||||
};
|
||||
let normalized = as_non_finite_float_string_literal(float_arg)?;
|
||||
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
|
||||
let replacement_text = format!(
|
||||
r#"{constructor_name}("{}")"#,
|
||||
// `Decimal.from_float(float(" -nan")) == Decimal("nan")`
|
||||
if normalized == "-nan" {
|
||||
// Here we do not attempt to remove just the '-' character.
|
||||
// It may have been encoded (e.g. as '\N{hyphen-minus}')
|
||||
// in the original source slice, and the added complexity
|
||||
// does not make sense for this edge case.
|
||||
"nan"
|
||||
} else {
|
||||
normalized
|
||||
}
|
||||
);
|
||||
Some(Edit::range_replacement(replacement_text, call.range()))
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +363,7 @@ help: Replace with `Decimal` constructor
|
||||
21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 | _ = Decimal.from_float(float("nan"))
|
||||
- _ = Decimal.from_float(float("-NaN "))
|
||||
23 + _ = Decimal("-nan")
|
||||
23 + _ = Decimal("nan")
|
||||
24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
@@ -655,7 +655,7 @@ help: Replace with `Decimal` constructor
|
||||
62 |
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
- _ = Decimal.from_float(float("-nan"))
|
||||
64 + _ = Decimal("-nan")
|
||||
64 + _ = Decimal("nan")
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
@@ -673,7 +673,7 @@ help: Replace with `Decimal` constructor
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
- _ = Decimal.from_float(float("\x2dnan"))
|
||||
65 + _ = Decimal("-nan")
|
||||
65 + _ = Decimal("nan")
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
@@ -689,4 +689,4 @@ help: Replace with `Decimal` constructor
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
- _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
66 + _ = Decimal("-nan")
|
||||
66 + _ = Decimal("nan")
|
||||
|
||||
@@ -112,6 +112,7 @@ mod tests {
|
||||
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_warns.py"))]
|
||||
#[test_case(Rule::LegacyFormPytestRaises, Path::new("RUF061_deprecated_call.py"))]
|
||||
#[test_case(Rule::NonOctalPermissions, Path::new("RUF064.py"))]
|
||||
#[test_case(Rule::LoggingEagerConversion, Path::new("RUF065.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_0.py"))]
|
||||
#[test_case(Rule::RedirectedNOQA, Path::new("RUF101_1.py"))]
|
||||
#[test_case(Rule::InvalidRuleCode, Path::new("RUF102.py"))]
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_literal::cformat::{CFormatPart, CFormatString, CFormatType};
|
||||
use ruff_python_literal::format::FormatConversion;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_logging_format::rules::{LoggingCallType, find_logging_call};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for eager string conversion of arguments to `logging` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Arguments to `logging` calls will be formatted as strings automatically, so it
|
||||
/// is unnecessary and less efficient to eagerly format the arguments before passing
|
||||
/// them in.
|
||||
///
|
||||
/// ## Known problems
|
||||
///
|
||||
/// This rule detects uses of the `logging` module via a heuristic.
|
||||
/// Specifically, it matches against:
|
||||
///
|
||||
/// - Uses of the `logging` module itself (e.g., `import logging; logging.info(...)`).
|
||||
/// - Uses of `flask.current_app.logger` (e.g., `from flask import current_app; current_app.logger.info(...)`).
|
||||
/// - Objects whose name starts with `log` or ends with `logger` or `logging`,
|
||||
/// when used in the same file in which they are defined (e.g., `logger = logging.getLogger(); logger.info(...)`).
|
||||
/// - Imported objects marked as loggers via the [`lint.logger-objects`] setting, which can be
|
||||
/// used to enforce these rules against shared logger objects (e.g., `from module import logger; logger.info(...)`,
|
||||
/// when [`lint.logger-objects`] is set to `["module.logger"]`).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
///
|
||||
/// user = "Maria"
|
||||
///
|
||||
/// logging.info("%s - Something happened", str(user))
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
///
|
||||
/// user = "Maria"
|
||||
///
|
||||
/// logging.info("%s - Something happened", user)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.logger-objects`
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `logging`](https://docs.python.org/3/library/logging.html)
|
||||
/// - [Python documentation: Optimization](https://docs.python.org/3/howto/logging.html#optimization)
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct LoggingEagerConversion {
|
||||
pub(crate) format_conversion: FormatConversion,
|
||||
}
|
||||
|
||||
impl Violation for LoggingEagerConversion {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let LoggingEagerConversion { format_conversion } = self;
|
||||
let (format_str, call_arg) = match format_conversion {
|
||||
FormatConversion::Str => ("%s", "str()"),
|
||||
FormatConversion::Repr => ("%r", "repr()"),
|
||||
FormatConversion::Ascii => ("%a", "ascii()"),
|
||||
FormatConversion::Bytes => ("%b", "bytes()"),
|
||||
};
|
||||
format!("Unnecessary `{call_arg}` conversion when formatting with `{format_str}`")
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF065
|
||||
pub(crate) fn logging_eager_conversion(checker: &Checker, call: &ast::ExprCall) {
|
||||
let Some((logging_call_type, _range)) = find_logging_call(checker, call) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let msg_pos = match logging_call_type {
|
||||
LoggingCallType::LevelCall(_) => 0,
|
||||
LoggingCallType::LogCall => 1,
|
||||
};
|
||||
|
||||
// Extract a format string from the logging statement msg argument
|
||||
let Some(Expr::StringLiteral(string_literal)) =
|
||||
call.arguments.find_argument_value("msg", msg_pos)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Ok(format_string) = CFormatString::from_str(string_literal.value.to_str()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Iterate over % placeholders in format string and zip with logging statement arguments
|
||||
for (spec, arg) in format_string
|
||||
.iter()
|
||||
.filter_map(|(_, part)| {
|
||||
if let CFormatPart::Spec(spec) = part {
|
||||
Some(spec)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.zip(call.arguments.args.iter().skip(msg_pos + 1))
|
||||
{
|
||||
// Check if the argument is a call to eagerly format a value
|
||||
if let Expr::Call(ast::ExprCall { func, .. }) = arg {
|
||||
let CFormatType::String(format_conversion) = spec.format_type else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Check for use of %s with str() or %r with repr()
|
||||
if checker.semantic().match_builtin_expr(func.as_ref(), "str")
|
||||
&& matches!(format_conversion, FormatConversion::Str)
|
||||
|| checker.semantic().match_builtin_expr(func.as_ref(), "repr")
|
||||
&& matches!(format_conversion, FormatConversion::Repr)
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(LoggingEagerConversion { format_conversion }, arg.range());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ pub(crate) use invalid_index_type::*;
|
||||
pub(crate) use invalid_pyproject_toml::*;
|
||||
pub(crate) use invalid_rule_code::*;
|
||||
pub(crate) use legacy_form_pytest_raises::*;
|
||||
pub(crate) use logging_eager_conversion::*;
|
||||
pub(crate) use map_int_version_parsing::*;
|
||||
pub(crate) use missing_fstring_syntax::*;
|
||||
pub(crate) use mutable_class_default::*;
|
||||
@@ -86,6 +87,7 @@ mod invalid_index_type;
|
||||
mod invalid_pyproject_toml;
|
||||
mod invalid_rule_code;
|
||||
mod legacy_form_pytest_raises;
|
||||
mod logging_eager_conversion;
|
||||
mod map_int_version_parsing;
|
||||
mod missing_fstring_syntax;
|
||||
mod mutable_class_default;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
assertion_line: 124
|
||||
---
|
||||
RUF065 Unnecessary `str()` conversion when formatting with `%s`
|
||||
--> RUF065.py:4:26
|
||||
|
|
||||
3 | # %s + str()
|
||||
4 | logging.info("Hello %s", str("World!"))
|
||||
| ^^^^^^^^^^^^^
|
||||
5 | logging.log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `str()` conversion when formatting with `%s`
|
||||
--> RUF065.py:5:39
|
||||
|
|
||||
3 | # %s + str()
|
||||
4 | logging.info("Hello %s", str("World!"))
|
||||
5 | logging.log(logging.INFO, "Hello %s", str("World!"))
|
||||
| ^^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | # %s + repr()
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `repr()` conversion when formatting with `%r`
|
||||
--> RUF065.py:16:26
|
||||
|
|
||||
15 | # %r + repr()
|
||||
16 | logging.info("Hello %r", repr("World!"))
|
||||
| ^^^^^^^^^^^^^^
|
||||
17 | logging.log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `repr()` conversion when formatting with `%r`
|
||||
--> RUF065.py:17:39
|
||||
|
|
||||
15 | # %r + repr()
|
||||
16 | logging.info("Hello %r", repr("World!"))
|
||||
17 | logging.log(logging.INFO, "Hello %r", repr("World!"))
|
||||
| ^^^^^^^^^^^^^^
|
||||
18 |
|
||||
19 | from logging import info, log
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `str()` conversion when formatting with `%s`
|
||||
--> RUF065.py:22:18
|
||||
|
|
||||
21 | # %s + str()
|
||||
22 | info("Hello %s", str("World!"))
|
||||
| ^^^^^^^^^^^^^
|
||||
23 | log(logging.INFO, "Hello %s", str("World!"))
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `str()` conversion when formatting with `%s`
|
||||
--> RUF065.py:23:31
|
||||
|
|
||||
21 | # %s + str()
|
||||
22 | info("Hello %s", str("World!"))
|
||||
23 | log(logging.INFO, "Hello %s", str("World!"))
|
||||
| ^^^^^^^^^^^^^
|
||||
24 |
|
||||
25 | # %s + repr()
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `repr()` conversion when formatting with `%r`
|
||||
--> RUF065.py:34:18
|
||||
|
|
||||
33 | # %r + repr()
|
||||
34 | info("Hello %r", repr("World!"))
|
||||
| ^^^^^^^^^^^^^^
|
||||
35 | log(logging.INFO, "Hello %r", repr("World!"))
|
||||
|
|
||||
|
||||
RUF065 Unnecessary `repr()` conversion when formatting with `%r`
|
||||
--> RUF065.py:35:31
|
||||
|
|
||||
33 | # %r + repr()
|
||||
34 | info("Hello %r", repr("World!"))
|
||||
35 | log(logging.INFO, "Hello %r", repr("World!"))
|
||||
| ^^^^^^^^^^^^^^
|
||||
36 |
|
||||
37 | def str(s): return f"str = {s}"
|
||||
|
|
||||
@@ -30,7 +30,7 @@ fn generate_inline_tests() -> Result<()> {
|
||||
test_files += install_tests(&tests.err, "crates/ruff_python_parser/resources/inline/err")?;
|
||||
|
||||
if !test_files.is_empty() {
|
||||
anyhow::bail!("{}", test_files);
|
||||
anyhow::bail!("{test_files}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -20,9 +20,15 @@ pub const MAGIC_GLOBALS: &[&str] = &[
|
||||
"__annotations__",
|
||||
"__builtins__",
|
||||
"__cached__",
|
||||
"__warningregistry__",
|
||||
"__file__",
|
||||
];
|
||||
|
||||
/// Magic globals that are only available starting in specific Python versions.
|
||||
///
|
||||
/// `__annotate__` was introduced in Python 3.14.
|
||||
static PY314_PLUS_MAGIC_GLOBALS: &[&str] = &["__annotate__"];
|
||||
|
||||
static ALWAYS_AVAILABLE_BUILTINS: &[&str] = &[
|
||||
"ArithmeticError",
|
||||
"AssertionError",
|
||||
@@ -216,6 +222,21 @@ pub fn python_builtins(minor_version: u8, is_notebook: bool) -> impl Iterator<It
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Return the list of magic globals for the given Python minor version.
|
||||
pub fn python_magic_globals(minor_version: u8) -> impl Iterator<Item = &'static str> {
|
||||
let py314_magic_globals = if minor_version >= 14 {
|
||||
Some(PY314_PLUS_MAGIC_GLOBALS)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
py314_magic_globals
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(MAGIC_GLOBALS)
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Returns `true` if the given name is that of a Python builtin.
|
||||
///
|
||||
/// Intended to be kept in sync with [`python_builtins`].
|
||||
|
||||
@@ -287,7 +287,7 @@ impl UvFormatCommand {
|
||||
"The installed version of uv does not support `uv format`; upgrade to a newer version"
|
||||
);
|
||||
}
|
||||
anyhow::bail!("Failed to format document: {}", stderr);
|
||||
anyhow::bail!("Failed to format document: {stderr}");
|
||||
}
|
||||
|
||||
let formatted = String::from_utf8(result.stdout)
|
||||
|
||||
@@ -187,12 +187,9 @@ impl Index {
|
||||
anyhow!("Failed to convert workspace URL to file path: {workspace_url}")
|
||||
})?;
|
||||
|
||||
self.settings.remove(&workspace_path).ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Tried to remove non-existent workspace URI {}",
|
||||
workspace_url
|
||||
)
|
||||
})?;
|
||||
self.settings
|
||||
.remove(&workspace_path)
|
||||
.ok_or_else(|| anyhow!("Tried to remove non-existent workspace URI {workspace_url}"))?;
|
||||
|
||||
// O(n) complexity, which isn't ideal... but this is an uncommon operation.
|
||||
self.documents
|
||||
@@ -330,7 +327,7 @@ impl Index {
|
||||
};
|
||||
|
||||
let Some(_) = self.documents.remove(&url) else {
|
||||
anyhow::bail!("tried to close document that didn't exist at {}", url)
|
||||
anyhow::bail!("tried to close document that didn't exist at {url}")
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
@@ -351,7 +348,7 @@ impl Index {
|
||||
anyhow::bail!("Tried to open unavailable document `{key}`");
|
||||
};
|
||||
let Some(controller) = self.documents.get_mut(&url) else {
|
||||
anyhow::bail!("Document controller not available at `{}`", url);
|
||||
anyhow::bail!("Document controller not available at `{url}`");
|
||||
};
|
||||
Ok(controller)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ There are multiple versions for the different wasm-pack targets. See [here](http
|
||||
This example uses the wasm-pack web target and is known to work with Vite.
|
||||
|
||||
```ts
|
||||
import init, { Workspace, type Diagnostic } from '@astral-sh/ruff-api';
|
||||
import init, { Workspace, type Diagnostic } from '@astral-sh/ruff-wasm-web';
|
||||
|
||||
const exampleDocument = `print('hello'); print("world")`
|
||||
|
||||
|
||||
@@ -158,9 +158,7 @@ impl Configuration {
|
||||
.expect("RUFF_PKG_VERSION is not a valid PEP 440 version specifier");
|
||||
if !required_version.contains(&ruff_pkg_version) {
|
||||
return Err(anyhow!(
|
||||
"Required version `{}` does not match the running version `{}`",
|
||||
required_version,
|
||||
RUFF_PKG_VERSION
|
||||
"Required version `{required_version}` does not match the running version `{RUFF_PKG_VERSION}`"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,10 +523,42 @@ impl<'config> WalkPythonFilesState<'config> {
|
||||
let (files, error) = self.merged.into_inner().unwrap();
|
||||
error?;
|
||||
|
||||
Ok((files, self.resolver.into_inner().unwrap()))
|
||||
let deduplicated_files = deduplicate_files(files);
|
||||
|
||||
Ok((deduplicated_files, self.resolver.into_inner().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduplicate files by path, prioritizing `Root` files over `Nested` files.
|
||||
///
|
||||
/// When the same path appears both as a directly specified input (`Root`)
|
||||
/// and via directory traversal (`Nested`), keep the `Root` entry and drop
|
||||
/// the `Nested` entry.
|
||||
///
|
||||
/// Dropping the root entry means that the explicitly passed path may be
|
||||
/// unintentionally ignored, since it is treated as nested and can be excluded
|
||||
/// despite being requested.
|
||||
///
|
||||
/// Concretely, with `lint.exclude = ["foo.py"]` and `ruff check . foo.py`,
|
||||
/// we must keep `Root(foo.py)` and drop `Nested(foo.py)` so `foo.py` is
|
||||
/// linted as the user requested.
|
||||
fn deduplicate_files(mut files: ResolvedFiles) -> ResolvedFiles {
|
||||
// Sort by path; for identical paths, prefer Root over Nested; place errors after files
|
||||
files.sort_by(|a, b| match (a, b) {
|
||||
(Ok(a_file), Ok(b_file)) => a_file.cmp(b_file),
|
||||
(Ok(_), Err(_)) => Ordering::Less,
|
||||
(Err(_), Ok(_)) => Ordering::Greater,
|
||||
(Err(_), Err(_)) => Ordering::Equal,
|
||||
});
|
||||
|
||||
files.dedup_by(|a, b| match (a, b) {
|
||||
(Ok(a_file), Ok(b_file)) => a_file.path() == b_file.path(),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
struct PythonFilesVisitorBuilder<'s, 'config> {
|
||||
state: &'s WalkPythonFilesState<'config>,
|
||||
transformer: &'s (dyn ConfigurationTransformer + Sync),
|
||||
@@ -682,7 +714,7 @@ impl Drop for PythonFilesVisitor<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub enum ResolvedFile {
|
||||
/// File explicitly passed to the CLI
|
||||
Root(PathBuf),
|
||||
@@ -715,18 +747,6 @@ impl ResolvedFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ResolvedFile {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ResolvedFile {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.path().cmp(other.path())
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the Python file at [`Path`] is _not_ excluded.
|
||||
pub fn python_file_at_path(
|
||||
path: &Path,
|
||||
|
||||
@@ -42,6 +42,13 @@ Used to determine if an active Conda environment is the base environment or not.
|
||||
Used to detect an activated Conda environment location.
|
||||
If both `VIRTUAL_ENV` and `CONDA_PREFIX` are present, `VIRTUAL_ENV` will be preferred.
|
||||
|
||||
### `PYTHONPATH`
|
||||
|
||||
Adds additional directories to ty's search paths.
|
||||
The format is the same as the shell’s PATH:
|
||||
one or more directory pathnames separated by os appropriate pathsep
|
||||
(e.g. colons on Unix or semicolons on Windows).
|
||||
|
||||
### `RAYON_NUM_THREADS`
|
||||
|
||||
Specifies an upper limit for the number of threads ty uses when performing work in parallel.
|
||||
|
||||
@@ -31,7 +31,13 @@ mypy_primer \
|
||||
```
|
||||
|
||||
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch. To run the
|
||||
diff for all projects we currently enable in CI, use `--project-selector "/($(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt))\$"`.
|
||||
diff for all projects we currently enable in CI, run
|
||||
|
||||
```sh
|
||||
SELECTOR="$(paste -s -d'|' "crates/ty_python_semantic/resources/primer/good.txt" | sed -e 's@(@\\(@g' -e 's@)@\\)@g')"
|
||||
```
|
||||
|
||||
and then use `--project-selector "$SELECTOR"`.
|
||||
|
||||
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `ty_paths` configuration
|
||||
option to work correctly.
|
||||
|
||||
177
crates/ty/docs/rules.md
generated
@@ -36,7 +36,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L113)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L114)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L157)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L158)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -88,7 +88,7 @@ f(int) # error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L184)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -117,7 +117,7 @@ a = 1
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L208)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L209)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -147,7 +147,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L234)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L235)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -177,7 +177,7 @@ class B(A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L299)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L300)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -202,7 +202,7 @@ class B(A, A): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L320)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L321)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L524)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -334,7 +334,7 @@ class C(A, B): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L548)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L352)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L353)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -445,7 +445,7 @@ an atypical memory layout.
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L593)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L633)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -496,7 +496,7 @@ a: int = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1666)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1688)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -562,7 +562,7 @@ asyncio.run(main())
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L685)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L735)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L736)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -609,7 +609,7 @@ with 1:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L756)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L757)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -636,7 +636,7 @@ a: str
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L780)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -678,7 +678,7 @@ except ZeroDivisionError:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L816)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L568)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L842)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L891)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -803,7 +803,7 @@ class B(metaclass=f): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L497)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L498)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -833,7 +833,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L917)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L918)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -881,7 +881,7 @@ def foo(x: int) -> int: ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L961)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -905,7 +905,7 @@ def f(a: int = ''): ...
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L434)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L435)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -937,7 +937,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L980)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981)
|
||||
</small>
|
||||
|
||||
Checks for `raise` statements that raise non-exceptions or use invalid
|
||||
@@ -984,7 +984,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L614)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1007,7 +1007,7 @@ def func() -> int:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1023)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1024)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1061,7 +1061,7 @@ TODO #14889
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L869)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L870)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1086,7 +1086,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1114,7 +1114,7 @@ TYPE_CHECKING = ''
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1086)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1142,7 +1142,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1138)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1139)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1174,7 +1174,7 @@ f(10) # Error
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1110)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1111)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1206,7 +1206,7 @@ class C:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1166)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1167)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1239,7 +1239,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1195)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1196)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1262,7 +1262,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1787)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1293,7 +1293,7 @@ alice["age"] # KeyError
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1214)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1215)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1320,7 +1320,7 @@ func("string") # error: [no-matching-overload]
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1237)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1238)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1342,7 +1342,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1255)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1366,7 +1366,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1306)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1307)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1386,6 +1386,31 @@ def f(x: int) -> int: ...
|
||||
f(1, x=2) # Error raised here
|
||||
```
|
||||
|
||||
## `positional-only-parameter-as-kwarg`
|
||||
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1542)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for keyword arguments in calls that match positional-only parameters of the callable.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Providing a positional-only parameter as a keyword argument will raise `TypeError` at runtime.
|
||||
|
||||
**Example**
|
||||
|
||||
|
||||
```python
|
||||
def f(x: int, /) -> int: ...
|
||||
|
||||
f(x=1) # Error raised here
|
||||
```
|
||||
|
||||
## `raw-string-type-annotation`
|
||||
|
||||
<small>
|
||||
@@ -1420,7 +1445,7 @@ def test(): -> "int":
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1642)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1448,7 +1473,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1398)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1475,7 +1500,7 @@ class B(A): ... # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1442)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1443)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1500,7 +1525,7 @@ f("foo") # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1420)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1421)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1526,7 +1551,7 @@ def _(x: int):
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1464)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1570,7 +1595,7 @@ class A:
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1520)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1521)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1595,7 +1620,7 @@ f(x=1, y=2) # Error raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1621,7 +1646,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1585)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1644,7 +1669,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1582)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1604)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1667,7 +1692,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1275)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1702,7 +1727,7 @@ b1 < b2 < b1 # exception raised here
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1601)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1728,7 +1753,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
<small>
|
||||
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1645)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1751,7 +1776,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L462)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L463)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1790,7 +1815,7 @@ class SubProto(BaseProto, Protocol):
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L279)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1838,21 +1863,21 @@ Use instead:
|
||||
a = 20 / 0 # type: ignore
|
||||
```
|
||||
|
||||
## `possibly-unbound-attribute`
|
||||
## `possibly-missing-attribute`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327)
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1328)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for possibly unbound attributes.
|
||||
Checks for possibly missing attributes.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Attempting to access an unbound attribute will raise an `AttributeError` at runtime.
|
||||
Attempting to access a missing attribute will raise an `AttributeError` at runtime.
|
||||
|
||||
**Examples**
|
||||
|
||||
@@ -1864,23 +1889,23 @@ class A:
|
||||
A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
```
|
||||
|
||||
## `possibly-unbound-implicit-call`
|
||||
## `possibly-missing-implicit-call`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L131)
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L132)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for implicit calls to possibly unbound methods.
|
||||
Checks for implicit calls to possibly missing methods.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Expressions such as `x[y]` and `x * y` call methods
|
||||
under the hood (`__getitem__` and `__mul__` respectively).
|
||||
Calling an unbound method will raise an `AttributeError` at runtime.
|
||||
Calling a missing method will raise an `AttributeError` at runtime.
|
||||
|
||||
**Examples**
|
||||
|
||||
@@ -1894,21 +1919,21 @@ class A:
|
||||
A()[0] # TypeError: 'A' object is not subscriptable
|
||||
```
|
||||
|
||||
## `possibly-unbound-import`
|
||||
## `possibly-missing-import`
|
||||
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1349)
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
Checks for imports of symbols that may be unbound.
|
||||
Checks for imports of symbols that may be missing.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Importing an unbound module or name will raise a `ModuleNotFoundError`
|
||||
Importing a missing module or name will raise a `ModuleNotFoundError`
|
||||
or `ImportError` at runtime.
|
||||
|
||||
**Examples**
|
||||
@@ -1929,7 +1954,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1694)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -1954,7 +1979,7 @@ cast(int, f()) # Redundant
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1502)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1503)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2005,7 +2030,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1715)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1737)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2059,7 +2084,7 @@ def g():
|
||||
<small>
|
||||
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2096,7 +2121,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
@@ -2118,7 +2143,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
<small>
|
||||
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
|
||||
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1375)
|
||||
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1376)
|
||||
</small>
|
||||
|
||||
**What it does**
|
||||
|
||||
@@ -1875,3 +1875,131 @@ fn default_root_python_package_pyi() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pythonpath_is_respected() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
("baz-dir/baz.py", "it = 42"),
|
||||
(
|
||||
"src/foo.py",
|
||||
r#"
|
||||
import baz
|
||||
print(f"{baz.it}")
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(),
|
||||
@r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-import]: Cannot resolve imported module `baz`
|
||||
--> src/foo.py:2:8
|
||||
|
|
||||
2 | import baz
|
||||
| ^^^
|
||||
3 | print(f"{baz.it}")
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. <temp_dir>/src (first-party code)
|
||||
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
assert_cmd_snapshot!(case.command()
|
||||
.env("PYTHONPATH", case.root().join("baz-dir")),
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pythonpath_multiple_dirs_is_respected() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
("baz-dir/baz.py", "it = 42"),
|
||||
("foo-dir/foo.py", "it = 42"),
|
||||
(
|
||||
"src/main.py",
|
||||
r#"
|
||||
import baz
|
||||
import foo
|
||||
|
||||
print(f"{baz.it}")
|
||||
print(f"{foo.it}")
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(),
|
||||
@r#"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-import]: Cannot resolve imported module `baz`
|
||||
--> src/main.py:2:8
|
||||
|
|
||||
2 | import baz
|
||||
| ^^^
|
||||
3 | import foo
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. <temp_dir>/src (first-party code)
|
||||
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
error[unresolved-import]: Cannot resolve imported module `foo`
|
||||
--> src/main.py:3:8
|
||||
|
|
||||
2 | import baz
|
||||
3 | import foo
|
||||
| ^^^
|
||||
4 |
|
||||
5 | print(f"{baz.it}")
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/ (first-party code)
|
||||
info: 2. <temp_dir>/src (first-party code)
|
||||
info: 3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
let pythonpath =
|
||||
std::env::join_paths([case.root().join("baz-dir"), case.root().join("foo-dir")])?;
|
||||
assert_cmd_snapshot!(case.command()
|
||||
.env("PYTHONPATH", pythonpath),
|
||||
@r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
WARN ty is pre-release software and not ready for production use. Expect to encounter bugs, missing features, and fatal errors.
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1713,4 +1713,348 @@ import numpy as np
|
||||
(Bar)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_imports_new_import() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
if os.getenv(\"WHATEVER\"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("quux", "MAGIC"), @r#"
|
||||
import quux
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(quux.MAGIC)
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("quux", "MAGIC"), @r#"
|
||||
import quux
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(quux.MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
// FIXME: This test (and the one below it) aren't
|
||||
// quite right. Namely, because we aren't handling
|
||||
// multiple binding sites correctly, we don't see the
|
||||
// existing `MAGIC` symbol.
|
||||
#[test]
|
||||
fn conditional_imports_existing_import1() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
if os.getenv(\"WHATEVER\"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("foo", "MAGIC"), @r#"
|
||||
import foo
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(foo.MAGIC)
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("foo", "MAGIC"), @r#"
|
||||
import foo
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(foo.MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_imports_existing_import2() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
if os.getenv(\"WHATEVER\"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("bar", "MAGIC"), @r#"
|
||||
import bar
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(bar.MAGIC)
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("bar", "MAGIC"), @r#"
|
||||
import bar
|
||||
if os.getenv("WHATEVER"):
|
||||
from foo import MAGIC
|
||||
else:
|
||||
from bar import MAGIC
|
||||
|
||||
(bar.MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
// FIXME: This test (and the one below it) aren't quite right. We
|
||||
// don't recognize the multiple declaration sites for `fubar`.
|
||||
//
|
||||
// In this case, it's not totally clear what we should do. Since we
|
||||
// are trying to import `MAGIC` from `foo`, we could add a `from
|
||||
// foo import MAGIC` within the first `if` block. Or we could try
|
||||
// and "infer" something about the code assuming that we know
|
||||
// `MAGIC` is in both `foo` and `bar`.
|
||||
#[test]
|
||||
fn conditional_imports_existing_module1() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
if os.getenv(\"WHATEVER\"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("foo", "MAGIC"), @r#"
|
||||
import foo
|
||||
if os.getenv("WHATEVER"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(foo.MAGIC)
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("foo", "MAGIC"), @r#"
|
||||
from foo import MAGIC
|
||||
if os.getenv("WHATEVER"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_imports_existing_module2() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
if os.getenv(\"WHATEVER\"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("bar", "MAGIC"), @r#"
|
||||
import bar
|
||||
if os.getenv("WHATEVER"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(bar.MAGIC)
|
||||
"#);
|
||||
assert_snapshot!(
|
||||
test.import_from("bar", "MAGIC"), @r#"
|
||||
from bar import MAGIC
|
||||
if os.getenv("WHATEVER"):
|
||||
import foo as fubar
|
||||
else:
|
||||
import bar as fubar
|
||||
|
||||
(MAGIC)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_imports_new_import() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("quux", "MAGIC"), @r"
|
||||
import quux
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(quux.MAGIC)
|
||||
");
|
||||
assert_snapshot!(
|
||||
test.import_from("quux", "MAGIC"), @r"
|
||||
import quux
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(quux.MAGIC)
|
||||
");
|
||||
}
|
||||
|
||||
// FIXME: This test (and the one below it) aren't
|
||||
// quite right. Namely, because we aren't handling
|
||||
// multiple binding sites correctly, we don't see the
|
||||
// existing `MAGIC` symbol.
|
||||
#[test]
|
||||
fn try_imports_existing_import1() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("foo", "MAGIC"), @r"
|
||||
import foo
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(foo.MAGIC)
|
||||
");
|
||||
assert_snapshot!(
|
||||
test.import_from("foo", "MAGIC"), @r"
|
||||
import foo
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(foo.MAGIC)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_imports_existing_import2() {
|
||||
let test = CursorTest::builder()
|
||||
.source("foo.py", "MAGIC = 1")
|
||||
.source("bar.py", "MAGIC = 2")
|
||||
.source("quux.py", "MAGIC = 3")
|
||||
.source(
|
||||
"main.py",
|
||||
"\
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(<CURSOR>)
|
||||
",
|
||||
)
|
||||
.build();
|
||||
|
||||
assert_snapshot!(
|
||||
test.import("bar", "MAGIC"), @r"
|
||||
import bar
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(bar.MAGIC)
|
||||
");
|
||||
assert_snapshot!(
|
||||
test.import_from("bar", "MAGIC"), @r"
|
||||
import bar
|
||||
try:
|
||||
from foo import MAGIC
|
||||
except ImportError:
|
||||
from bar import MAGIC
|
||||
|
||||
(bar.MAGIC)
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,15 @@ impl SourceOrderVisitor<'_> for InlayHintVisitor<'_, '_> {
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
Expr::Attribute(attribute) => {
|
||||
if self.in_assignment {
|
||||
if attribute.ctx.is_store() {
|
||||
let ty = expr.inferred_type(&self.model);
|
||||
self.add_type_hint(expr.range().end(), ty);
|
||||
}
|
||||
}
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
Expr::Call(call) => {
|
||||
let argument_names =
|
||||
inlay_hint_function_argument_details(self.db, &self.model, call)
|
||||
@@ -436,6 +445,31 @@ mod tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_assign_attribute_of_instance() {
|
||||
let test = inlay_hint_test(
|
||||
"
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x = 1
|
||||
self.y = y
|
||||
|
||||
a = A(2)
|
||||
a.y = 3
|
||||
",
|
||||
);
|
||||
|
||||
assert_snapshot!(test.inlay_hints(), @r"
|
||||
class A:
|
||||
def __init__(self, y):
|
||||
self.x[: Literal[1]] = 1
|
||||
self.y[: Unknown] = y
|
||||
|
||||
a[: A] = A([y=]2)
|
||||
a.y[: Literal[3]] = 3
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disabled_variable_types() {
|
||||
let test = inlay_hint_test("x = 1");
|
||||
|
||||
@@ -22,6 +22,7 @@ ruff_python_formatter = { workspace = true, optional = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ty_combine = { workspace = true }
|
||||
ty_python_semantic = { workspace = true, features = ["serde"] }
|
||||
ty_static = { workspace = true }
|
||||
ty_vendored = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -34,6 +34,7 @@ use ty_python_semantic::{
|
||||
PythonVersionSource, PythonVersionWithSource, SearchPathSettings, SearchPathValidationError,
|
||||
SearchPaths, SitePackagesPaths, SysPrefixPathOrigin,
|
||||
};
|
||||
use ty_static::EnvVars;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -295,14 +296,51 @@ impl Options {
|
||||
roots
|
||||
};
|
||||
|
||||
// collect the existing site packages
|
||||
let mut extra_paths: Vec<SystemPathBuf> = environment
|
||||
.extra_paths
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|path| path.absolute(project_root, system))
|
||||
.collect();
|
||||
|
||||
// read all the paths off the PYTHONPATH environment variable, check
|
||||
// they exist as a directory, and add them to the vec of extra_paths
|
||||
// as they should be checked before site-packages just like python
|
||||
// interpreter does
|
||||
if let Ok(python_path) = system.env_var(EnvVars::PYTHONPATH) {
|
||||
for path in std::env::split_paths(python_path.as_str()) {
|
||||
let path = match SystemPathBuf::from_path_buf(path) {
|
||||
Ok(path) => path,
|
||||
Err(path) => {
|
||||
tracing::debug!(
|
||||
"Skipping `{path}` listed in `PYTHONPATH` because the path is not valid UTF-8",
|
||||
path = path.display()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let abspath = SystemPath::absolute(path, system.current_directory());
|
||||
|
||||
if !system.is_directory(&abspath) {
|
||||
tracing::debug!(
|
||||
"Skipping `{abspath}` listed in `PYTHONPATH` because the path doesn't exist or isn't a directory"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::debug!(
|
||||
"Adding `{abspath}` from the `PYTHONPATH` environment variable to `extra_paths`"
|
||||
);
|
||||
|
||||
extra_paths.push(abspath);
|
||||
}
|
||||
}
|
||||
|
||||
let settings = SearchPathSettings {
|
||||
extra_paths: environment
|
||||
.extra_paths
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|path| path.absolute(project_root, system))
|
||||
.collect(),
|
||||
extra_paths,
|
||||
src_roots,
|
||||
custom_typeshed: environment
|
||||
.typeshed
|
||||
|
||||
@@ -131,12 +131,12 @@ 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[int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
# 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[str]` is not assignable to `list[LiteralString]`"
|
||||
# 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]
|
||||
```
|
||||
@@ -144,10 +144,10 @@ reveal_type(o) # revealed: list[LiteralString]
|
||||
## Incorrect collection literal assignments are complained aobut
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Object of type `list[int]` is not assignable to `list[str]`"
|
||||
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
|
||||
a: list[str] = [1, 2, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `set[int | str]` is not assignable to `set[int]`"
|
||||
# error: [invalid-assignment] "Object of type `set[Unknown | int | str]` is not assignable to `set[int]`"
|
||||
b: set[int] = {1, 2, "3"}
|
||||
```
|
||||
|
||||
@@ -234,3 +234,47 @@ reveal_type(x) # revealed: Foo
|
||||
x: int = 1
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Annotations influence generic call inference
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
a = f("a")
|
||||
reveal_type(a) # revealed: list[Literal["a"]]
|
||||
|
||||
b: list[int | Literal["a"]] = f("a")
|
||||
reveal_type(b) # revealed: list[int | Literal["a"]]
|
||||
|
||||
c: list[int | str] = f("a")
|
||||
reveal_type(c) # revealed: list[int | str]
|
||||
|
||||
d: list[int | tuple[int, int]] = f((1, 2))
|
||||
reveal_type(d) # revealed: list[int | tuple[int, int]]
|
||||
|
||||
e: list[int] = f(True)
|
||||
reveal_type(e) # revealed: list[int]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `list[int]`"
|
||||
g: list[int] = f("a")
|
||||
|
||||
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `tuple[int]`"
|
||||
h: tuple[int] = f("a")
|
||||
|
||||
def f2[T: int](x: T) -> T:
|
||||
return x
|
||||
|
||||
i: int = f2(True)
|
||||
reveal_type(i) # revealed: int
|
||||
|
||||
j: int | str = f2(True)
|
||||
reveal_type(j) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
@@ -914,7 +914,7 @@ def _(flag: bool):
|
||||
reveal_type(C3.attr2) # revealed: Literal["metaclass value", "class value"]
|
||||
```
|
||||
|
||||
If the *metaclass* attribute is only partially defined, we emit a `possibly-unbound-attribute`
|
||||
If the *metaclass* attribute is only partially defined, we emit a `possibly-missing-attribute`
|
||||
diagnostic:
|
||||
|
||||
```py
|
||||
@@ -924,12 +924,12 @@ def _(flag: bool):
|
||||
attr1: str = "metaclass value"
|
||||
|
||||
class C4(metaclass=Meta4): ...
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(C4.attr1) # revealed: str
|
||||
```
|
||||
|
||||
Finally, if both the metaclass attribute and the class-level attribute are only partially defined,
|
||||
we union them and emit a `possibly-unbound-attribute` diagnostic:
|
||||
we union them and emit a `possibly-missing-attribute` diagnostic:
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
@@ -941,7 +941,7 @@ def _(flag1: bool, flag2: bool):
|
||||
if flag2:
|
||||
attr1 = "class value"
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(C5.attr1) # revealed: Unknown | Literal["metaclass value", "class value"]
|
||||
```
|
||||
|
||||
@@ -1180,13 +1180,13 @@ def _(flag1: bool, flag2: bool):
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>`"
|
||||
C.x = 100
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 3]
|
||||
|
||||
# error: [invalid-assignment] "Object of type `Literal[100]` is not assignable to attribute `x` on type `C1 | C2 | C3`"
|
||||
@@ -1212,18 +1212,18 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `<class 'C1'> | <class 'C2'> | <class 'C3'>` may be missing"
|
||||
reveal_type(C.x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
C.x = 100
|
||||
|
||||
# Note: we might want to consider ignoring possibly-unbound diagnostics for instance attributes eventually,
|
||||
# Note: we might want to consider ignoring possibly-missing diagnostics for instance attributes eventually,
|
||||
# see the "Possibly unbound/undeclared instance attribute" section below.
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `C1 | C2 | C3` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `x` on type `C1 | C2 | C3` may be missing"
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1, 2, 3]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
C().x = 100
|
||||
```
|
||||
|
||||
@@ -1287,16 +1287,16 @@ def _(flag: bool):
|
||||
if flag:
|
||||
x = 2
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(Bar.x) # revealed: Unknown | Literal[2, 1]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
Bar.x = 3
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(Bar().x) # revealed: Unknown | Literal[2, 1]
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
Bar().x = 3
|
||||
```
|
||||
|
||||
@@ -1304,7 +1304,7 @@ def _(flag: bool):
|
||||
|
||||
We currently treat implicit instance attributes to be bound, even if they are only conditionally
|
||||
defined within a method. If the class-level definition or the whole method is only conditionally
|
||||
available, we emit a `possibly-unbound-attribute` diagnostic.
|
||||
available, we emit a `possibly-missing-attribute` diagnostic.
|
||||
|
||||
#### Possibly unbound and undeclared
|
||||
|
||||
@@ -1484,17 +1484,17 @@ def _(flag: bool):
|
||||
class B1: ...
|
||||
|
||||
def inner1(a_and_b: Intersection[A1, B1]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner1_class(a_and_b: Intersection[type[A1], type[B1]]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
a_and_b.x = R()
|
||||
|
||||
class A2:
|
||||
@@ -1509,7 +1509,7 @@ def _(flag: bool):
|
||||
|
||||
# TODO: this should not be an error, we need better intersection
|
||||
# handling in `validate_attribute_assignment` for this
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner2_class(a_and_b: Intersection[type[A2], type[B1]]):
|
||||
@@ -1524,17 +1524,17 @@ def _(flag: bool):
|
||||
x: Q = Q()
|
||||
|
||||
def inner3(a_and_b: Intersection[A3, B3]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
a_and_b.x = R()
|
||||
# Same for class objects
|
||||
def inner3_class(a_and_b: Intersection[type[A3], type[B3]]):
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(a_and_b.x) # revealed: P & Q
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
a_and_b.x = R()
|
||||
|
||||
class A4: ...
|
||||
@@ -1649,7 +1649,7 @@ If an attribute is defined on the class, it takes precedence over the `__getattr
|
||||
reveal_type(c.class_attr) # revealed: int
|
||||
```
|
||||
|
||||
If the class attribute is possibly unbound, we union the type of the attribute with the fallback
|
||||
If the class attribute is possibly missing, we union the type of the attribute with the fallback
|
||||
type of the `__getattr__` method:
|
||||
|
||||
```py
|
||||
|
||||
@@ -26,7 +26,7 @@ In particular, we should raise errors in the "possibly-undeclared-and-unbound" a
|
||||
| **Diagnostic** | declared | possibly-undeclared | undeclared |
|
||||
| ---------------- | -------- | ------------------------- | ------------------- |
|
||||
| bound | | | |
|
||||
| possibly-unbound | | `possibly-unbound-import` | ? |
|
||||
| possibly-unbound | | `possibly-missing-import` | ? |
|
||||
| unbound | | ? | `unresolved-import` |
|
||||
|
||||
## Declared
|
||||
@@ -158,7 +158,7 @@ a = None
|
||||
|
||||
If a symbol is possibly undeclared and possibly unbound, we also use the union of the declared and
|
||||
inferred types. This case is interesting because the "possibly declared" definition might not be the
|
||||
same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-unbound-import`
|
||||
same as the "possibly bound" definition (symbol `b`). Note that we raise a `possibly-missing-import`
|
||||
error for both `a` and `b`:
|
||||
|
||||
`mod.py`:
|
||||
@@ -177,8 +177,8 @@ else:
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-missing-import] "Member `a` of module `mod` may be missing"
|
||||
# error: [possibly-missing-import] "Member `b` of module `mod` may be missing"
|
||||
from mod import a, b
|
||||
|
||||
reveal_type(a) # revealed: Literal[1] | Any
|
||||
@@ -332,8 +332,8 @@ if flag():
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-missing-import]
|
||||
# error: [possibly-missing-import]
|
||||
from mod import MyInt, C
|
||||
|
||||
reveal_type(MyInt) # revealed: <class 'int'>
|
||||
|
||||
@@ -19,7 +19,7 @@ b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly unbound `__call__` method
|
||||
## Possibly missing `__call__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
@@ -29,7 +29,7 @@ def _(flag: bool):
|
||||
return 1
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly missing `__call__` method)"
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
@@ -105,7 +105,7 @@ reveal_type(c()) # revealed: int
|
||||
|
||||
## Union over callables
|
||||
|
||||
### Possibly unbound `__call__`
|
||||
### Possibly missing `__call__`
|
||||
|
||||
```py
|
||||
def outer(cond1: bool):
|
||||
@@ -122,6 +122,6 @@ def outer(cond1: bool):
|
||||
else:
|
||||
a = Other()
|
||||
|
||||
# error: [call-non-callable] "Object of type `Test` is not callable (possibly unbound `__call__` method)"
|
||||
# error: [call-non-callable] "Object of type `Test` is not callable (possibly missing `__call__` method)"
|
||||
a()
|
||||
```
|
||||
|
||||
@@ -158,15 +158,15 @@ def _(flag: bool) -> None:
|
||||
def __new__(cls):
|
||||
return object.__new__(cls)
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [possibly-missing-implicit-call]
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# error: [possibly-missing-implicit-call]
|
||||
# error: [too-many-positional-arguments]
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
```
|
||||
|
||||
#### Possibly unbound `__call__` on `__new__` callable
|
||||
#### Possibly missing `__call__` on `__new__` callable
|
||||
|
||||
```py
|
||||
def _(flag: bool) -> None:
|
||||
@@ -178,11 +178,11 @@ def _(flag: bool) -> None:
|
||||
class Foo:
|
||||
__new__ = Callable()
|
||||
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)"
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
# but we currently infer the signature of `__call__` as unknown, so it accepts any arguments
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)"
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
```
|
||||
|
||||
@@ -294,11 +294,11 @@ def _(flag: bool) -> None:
|
||||
class Foo:
|
||||
__init__ = Callable()
|
||||
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)"
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
|
||||
reveal_type(Foo(1)) # revealed: Foo
|
||||
# TODO should be - error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
|
||||
# but we currently infer the signature of `__call__` as unknown, so it accepts any arguments
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly unbound `__call__` method)"
|
||||
# error: [call-non-callable] "Object of type `Callable` is not callable (possibly missing `__call__` method)"
|
||||
reveal_type(Foo()) # revealed: Foo
|
||||
```
|
||||
|
||||
|
||||
@@ -114,7 +114,11 @@ def _(flag: bool):
|
||||
|
||||
this_fails = ThisFails()
|
||||
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
# TODO: this would be a friendlier diagnostic if we propagated the error up the stack
|
||||
# and transformed it into a `[not-subscriptable]` error with a subdiagnostic explaining
|
||||
# that the cause of the error was a possibly missing `__getitem__` method
|
||||
#
|
||||
# error: [possibly-missing-implicit-call] "Method `__getitem__` of type `ThisFails` may be missing"
|
||||
reveal_type(this_fails[0]) # revealed: Unknown | str
|
||||
```
|
||||
|
||||
@@ -270,6 +274,11 @@ def _(flag: bool):
|
||||
return str(key)
|
||||
|
||||
c = C()
|
||||
# error: [possibly-unbound-implicit-call]
|
||||
|
||||
# TODO: this would be a friendlier diagnostic if we propagated the error up the stack
|
||||
# and transformed it into a `[not-subscriptable]` error with a subdiagnostic explaining
|
||||
# that the cause of the error was a possibly missing `__getitem__` method
|
||||
#
|
||||
# error: [possibly-missing-implicit-call] "Method `__getitem__` of type `C` may be missing"
|
||||
reveal_type(c[0]) # revealed: str
|
||||
```
|
||||
|
||||
@@ -90,8 +90,7 @@ still continue to use the old convention, so it is supported by ty as well.
|
||||
def f(__x: int): ...
|
||||
|
||||
f(1)
|
||||
# error: [missing-argument]
|
||||
# error: [unknown-argument]
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
f(__x=1)
|
||||
```
|
||||
|
||||
@@ -131,11 +130,9 @@ class C:
|
||||
@staticmethod
|
||||
def static_method(self, __x: int): ...
|
||||
|
||||
# error: [missing-argument]
|
||||
# error: [unknown-argument]
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
C().method(__x=1)
|
||||
# error: [missing-argument]
|
||||
# error: [unknown-argument]
|
||||
# error: [positional-only-parameter-as-kwarg]
|
||||
C.class_method(__x="1")
|
||||
C.static_method("x", __x=42) # fine
|
||||
```
|
||||
@@ -871,3 +868,296 @@ is_subtype_of(int, int, int)
|
||||
# error: [too-many-positional-arguments]
|
||||
is_subtype_of(int, int, int, int)
|
||||
```
|
||||
|
||||
## Keywords argument
|
||||
|
||||
A double-starred argument (`**kwargs`) can be used to pass an argument that implements the mapping
|
||||
protocol. This is matched against any of the *unmatched* standard (positional or keyword),
|
||||
keyword-only, and keywords (`**kwargs`) parameters.
|
||||
|
||||
### Empty
|
||||
|
||||
```py
|
||||
def empty() -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
empty(**kwargs)
|
||||
|
||||
empty(**{})
|
||||
empty(**dict())
|
||||
```
|
||||
|
||||
### Single parameter
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
f(**{"foo": 1})
|
||||
f(**dict(foo=1))
|
||||
f(**Foo(a=1, b=2))
|
||||
```
|
||||
|
||||
### Positional-only and variadic parameters
|
||||
|
||||
```py
|
||||
def f1(a: int, b: int, /) -> None: ...
|
||||
def f2(*args: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
# error: [missing-argument] "No arguments provided for required parameters `a`, `b` of function `f1`"
|
||||
f1(**kwargs)
|
||||
|
||||
# This doesn't raise an error because `*args` is an optional parameter and `**kwargs` can be empty.
|
||||
f2(**kwargs)
|
||||
```
|
||||
|
||||
### Standard parameters
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
def f(a: int, b: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
f(**{"a": 1, "b": 2})
|
||||
f(**dict(a=1, b=2))
|
||||
f(**Foo(a=1, b=2))
|
||||
```
|
||||
|
||||
### Keyword-only parameters
|
||||
|
||||
```py
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: int
|
||||
|
||||
def f(*, a: int, b: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
f(**kwargs)
|
||||
|
||||
f(**{"a": 1, "b": 2})
|
||||
f(**dict(a=1, b=2))
|
||||
f(**Foo(a=1, b=2))
|
||||
```
|
||||
|
||||
### Multiple keywords argument
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> None: ...
|
||||
def _(kwargs1: dict[str, int], kwargs2: dict[str, int], kwargs3: dict[str, str], kwargs4: dict[int, list]) -> None:
|
||||
f(**kwargs1, **kwargs2)
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
f(**kwargs1, **kwargs3)
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `list[Unknown]`"
|
||||
f(**kwargs3, **kwargs4)
|
||||
```
|
||||
|
||||
### Keyword-only after keywords
|
||||
|
||||
```py
|
||||
class B: ...
|
||||
|
||||
def f(*, a: int, b: B, **kwargs: int) -> None: ...
|
||||
def _(kwargs: dict[str, int]):
|
||||
# Make sure that the `b` argument is not being matched against `kwargs` by passing an integer
|
||||
# instead of the annotated type which should raise an
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `B`, found `Literal[2]`"
|
||||
f(a=1, **kwargs, b=2)
|
||||
```
|
||||
|
||||
### Mixed parameter kind
|
||||
|
||||
```py
|
||||
def f1(*, a: int, b: int, **kwargs: int) -> None: ...
|
||||
def f2(a: int, *, b: int, **kwargs: int) -> None: ...
|
||||
def f3(a: int, /, *args: int, b: int, **kwargs: int) -> None: ...
|
||||
def _(kwargs1: dict[str, int], kwargs2: dict[str, str]):
|
||||
f1(**kwargs1)
|
||||
f2(**kwargs1)
|
||||
f3(1, **kwargs1)
|
||||
```
|
||||
|
||||
### TypedDict
|
||||
|
||||
```py
|
||||
from typing_extensions import NotRequired, TypedDict
|
||||
|
||||
class Foo1(TypedDict):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
class Foo2(TypedDict):
|
||||
a: int
|
||||
b: NotRequired[str]
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
f(**Foo1(a=1, b="b"))
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
|
||||
f(**Foo2(a=1))
|
||||
```
|
||||
|
||||
### Keys must be strings
|
||||
|
||||
The keys of the mapping passed to a double-starred argument must be strings.
|
||||
|
||||
```py
|
||||
from collections.abc import Mapping
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
class DictSubclass(dict[int, int]): ...
|
||||
class MappingSubclass(Mapping[int, int]): ...
|
||||
|
||||
class MappingProtocol:
|
||||
def keys(self) -> list[int]:
|
||||
return [1]
|
||||
|
||||
def __getitem__(self, key: int) -> int:
|
||||
return 1
|
||||
|
||||
def _(kwargs: dict[int, int]) -> None:
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**kwargs)
|
||||
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**DictSubclass())
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**MappingSubclass())
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
|
||||
f(**MappingProtocol())
|
||||
```
|
||||
|
||||
The key can also be a custom type that inherits from `str`.
|
||||
|
||||
```py
|
||||
class SubStr(str): ...
|
||||
class SubInt(int): ...
|
||||
|
||||
def _(kwargs1: dict[SubStr, int], kwargs2: dict[SubInt, int]) -> None:
|
||||
f(**kwargs1)
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `SubInt`"
|
||||
f(**kwargs2)
|
||||
```
|
||||
|
||||
Or, it can be a type that is assignable to `str`.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
|
||||
f(**kwargs1)
|
||||
f(**kwargs2)
|
||||
```
|
||||
|
||||
### Invalid value type
|
||||
|
||||
```py
|
||||
from collections.abc import Mapping
|
||||
|
||||
def f(**kwargs: str) -> None: ...
|
||||
|
||||
class DictSubclass(dict[str, int]): ...
|
||||
class MappingSubclass(Mapping[str, int]): ...
|
||||
|
||||
class MappingProtocol:
|
||||
def keys(self) -> list[str]:
|
||||
return ["foo"]
|
||||
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 1
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
|
||||
f(**kwargs)
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
|
||||
f(**DictSubclass())
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
|
||||
f(**MappingSubclass())
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
|
||||
f(**MappingProtocol())
|
||||
```
|
||||
|
||||
### `Unknown` type
|
||||
|
||||
```py
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def f(**kwargs: int) -> None: ...
|
||||
def _(kwargs: Unknown):
|
||||
f(**kwargs)
|
||||
```
|
||||
|
||||
### Not a mapping
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> None: ...
|
||||
|
||||
class A: ...
|
||||
|
||||
class InvalidMapping:
|
||||
def keys(self) -> A:
|
||||
return A()
|
||||
|
||||
def __getitem__(self, key: str) -> int:
|
||||
return 1
|
||||
|
||||
def _(kwargs: dict[str, int] | int):
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `dict[str, int] | int`"
|
||||
f(**kwargs)
|
||||
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `InvalidMapping`"
|
||||
f(**InvalidMapping())
|
||||
```
|
||||
|
||||
### Generic
|
||||
|
||||
For a generic keywords parameter, the type variable should be specialized to the value type of the
|
||||
mapping.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
def f(**kwargs: _T) -> _T:
|
||||
return kwargs["a"]
|
||||
|
||||
def _(kwargs: dict[str, int]) -> None:
|
||||
reveal_type(f(**kwargs)) # revealed: int
|
||||
```
|
||||
|
||||
For a `TypedDict`, the type variable should be specialized to the union of all value types.
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class Foo(TypedDict):
|
||||
a: int
|
||||
b: str
|
||||
|
||||
def f(**kwargs: _T) -> _T:
|
||||
return kwargs["a"]
|
||||
|
||||
reveal_type(f(**Foo(a=1, b="b"))) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -325,7 +325,7 @@ class D(metaclass=Meta):
|
||||
reveal_type(D.f(1)) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
If the class method is possibly unbound, we union the return types:
|
||||
If the class method is possibly missing, we union the return types:
|
||||
|
||||
```py
|
||||
def flag() -> bool:
|
||||
|
||||
@@ -219,7 +219,7 @@ def f(x: C | D):
|
||||
s = super(A, x)
|
||||
reveal_type(s) # revealed: <super: <class 'A'>, C> | <super: <class 'A'>, D>
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `b` on type `<super: <class 'A'>, C> | <super: <class 'A'>, D>` may be missing"
|
||||
s.b
|
||||
|
||||
def f(flag: bool):
|
||||
@@ -259,7 +259,7 @@ def f(flag: bool):
|
||||
reveal_type(s.x) # revealed: Unknown | Literal[1, 2]
|
||||
reveal_type(s.y) # revealed: int | str
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `a` on type `<super: <class 'B'>, B> | <super: <class 'D'>, D>` may be missing"
|
||||
reveal_type(s.a) # revealed: str
|
||||
```
|
||||
|
||||
|
||||
@@ -351,7 +351,7 @@ reveal_type(C4.meta_attribute) # revealed: Literal["value on metaclass"]
|
||||
reveal_type(C4.meta_non_data_descriptor) # revealed: Literal["non-data"]
|
||||
```
|
||||
|
||||
When a metaclass data descriptor is possibly unbound, we union the result type of its `__get__`
|
||||
When a metaclass data descriptor is possibly missing, we union the result type of its `__get__`
|
||||
method with an underlying class level attribute, if present:
|
||||
|
||||
```py
|
||||
@@ -365,7 +365,7 @@ def _(flag: bool):
|
||||
meta_data_descriptor1: Literal["value on class"] = "value on class"
|
||||
|
||||
reveal_type(C5.meta_data_descriptor1) # revealed: Literal["data", "value on class"]
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(C5.meta_data_descriptor2) # revealed: Literal["data"]
|
||||
|
||||
# TODO: We currently emit two diagnostics here, corresponding to the two states of `flag`. The diagnostics are not
|
||||
@@ -375,11 +375,11 @@ def _(flag: bool):
|
||||
# error: [invalid-assignment] "Object of type `None` is not assignable to attribute `meta_data_descriptor1` of type `Literal["value on class"]`"
|
||||
C5.meta_data_descriptor1 = None
|
||||
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
C5.meta_data_descriptor2 = 1
|
||||
```
|
||||
|
||||
When a class-level attribute is possibly unbound, we union its (descriptor protocol) type with the
|
||||
When a class-level attribute is possibly missing, we union its (descriptor protocol) type with the
|
||||
metaclass attribute (unless it's a data descriptor, which always takes precedence):
|
||||
|
||||
```py
|
||||
@@ -401,7 +401,7 @@ def _(flag: bool):
|
||||
reveal_type(C6.attribute1) # revealed: Literal["data"]
|
||||
reveal_type(C6.attribute2) # revealed: Literal["non-data", "value on class"]
|
||||
reveal_type(C6.attribute3) # revealed: Literal["value on metaclass", "value on class"]
|
||||
# error: [possibly-unbound-attribute]
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(C6.attribute4) # revealed: Literal["value on class"]
|
||||
```
|
||||
|
||||
@@ -756,16 +756,16 @@ def _(flag: bool):
|
||||
non_data: NonDataDescriptor = NonDataDescriptor()
|
||||
data: DataDescriptor = DataDescriptor()
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `non_data` on type `<class 'PossiblyUnbound'>` may be missing"
|
||||
reveal_type(PossiblyUnbound.non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `non_data` on type `PossiblyUnbound` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `non_data` on type `PossiblyUnbound` may be missing"
|
||||
reveal_type(PossiblyUnbound().non_data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `data` on type `<class 'PossiblyUnbound'>` may be missing"
|
||||
reveal_type(PossiblyUnbound.data) # revealed: int
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `data` on type `PossiblyUnbound` is possibly unbound"
|
||||
# error: [possibly-missing-attribute] "Attribute `data` on type `PossiblyUnbound` may be missing"
|
||||
reveal_type(PossiblyUnbound().data) # revealed: int
|
||||
```
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ instance = C()
|
||||
instance.non_existent = 1 # error: [unresolved-attribute]
|
||||
```
|
||||
|
||||
## Possibly-unbound attributes
|
||||
## Possibly-missing attributes
|
||||
|
||||
When trying to set an attribute that is not defined in all branches, we emit errors:
|
||||
|
||||
@@ -79,10 +79,10 @@ def _(flag: bool) -> None:
|
||||
if flag:
|
||||
attr: int = 0
|
||||
|
||||
C.attr = 1 # error: [possibly-unbound-attribute]
|
||||
C.attr = 1 # error: [possibly-missing-attribute]
|
||||
|
||||
instance = C()
|
||||
instance.attr = 1 # error: [possibly-unbound-attribute]
|
||||
instance.attr = 1 # error: [possibly-missing-attribute]
|
||||
```
|
||||
|
||||
## Data descriptors
|
||||
|
||||
@@ -23,7 +23,7 @@ async def main() -> None:
|
||||
await MissingAwait() # error: [invalid-await]
|
||||
```
|
||||
|
||||
## Custom type with possibly unbound `__await__`
|
||||
## Custom type with possibly missing `__await__`
|
||||
|
||||
This diagnostic also points to the method definition if available.
|
||||
|
||||
|
||||