Compare commits
65 Commits
extend-AIR
...
david/inte
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0986debd3 | ||
|
|
fafecc97e4 | ||
|
|
f0e9cce52a | ||
|
|
b85a56db5b | ||
|
|
0d6425ae4e | ||
|
|
03818856eb | ||
|
|
df3536969f | ||
|
|
3dde382236 | ||
|
|
2dfe6820f0 | ||
|
|
025804e548 | ||
|
|
2c104c6955 | ||
|
|
0b70c1c159 | ||
|
|
89fefe0164 | ||
|
|
c0e7c08705 | ||
|
|
402f2792ea | ||
|
|
4353730913 | ||
|
|
f9f461540c | ||
|
|
a4d805f86c | ||
|
|
22e345abc9 | ||
|
|
c9eb782d57 | ||
|
|
47bb571b4d | ||
|
|
090563e4a6 | ||
|
|
b37f095f6d | ||
|
|
91fa462fba | ||
|
|
167337b647 | ||
|
|
c3d3437846 | ||
|
|
8dd5cc5b3e | ||
|
|
32390f3710 | ||
|
|
0c6c0ee529 | ||
|
|
2beabc61c2 | ||
|
|
c5e32d937e | ||
|
|
2bbd586725 | ||
|
|
6c64ae0c05 | ||
|
|
45d544b601 | ||
|
|
55330c4ed7 | ||
|
|
8b60946875 | ||
|
|
d249801bf2 | ||
|
|
c1748f07d6 | ||
|
|
189f2ef3cc | ||
|
|
81bfc5227b | ||
|
|
9a1d1ea33b | ||
|
|
12f139df87 | ||
|
|
8a93a9a55a | ||
|
|
2e6f757456 | ||
|
|
853e171ed1 | ||
|
|
afe1572d7f | ||
|
|
79b582c584 | ||
|
|
5df51f26cc | ||
|
|
4f6fba2cab | ||
|
|
3a1dbc182f | ||
|
|
90e639bd20 | ||
|
|
e044fde784 | ||
|
|
a37dac1b41 | ||
|
|
27b64a0c66 | ||
|
|
00e698b346 | ||
|
|
e871ea19e7 | ||
|
|
2b12c496ff | ||
|
|
c28bcdbc20 | ||
|
|
dc9fbaaef1 | ||
|
|
3d85c7d09c | ||
|
|
76e277b02a | ||
|
|
2042c687b1 | ||
|
|
7b79a56ea6 | ||
|
|
34be5b6b00 | ||
|
|
e8cfb341f2 |
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -349,7 +349,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -613,7 +613,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v5
|
||||
uses: astral-sh/setup-uv@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
@@ -23,7 +23,7 @@ repos:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/executablebooks/mdformat
|
||||
rev: 0.7.21
|
||||
rev: 0.7.19
|
||||
hooks:
|
||||
- id: mdformat
|
||||
additional_dependencies:
|
||||
@@ -59,7 +59,7 @@ repos:
|
||||
- black==24.10.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.28.4
|
||||
rev: v1.28.3
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -73,7 +73,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.4
|
||||
rev: v0.8.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -91,7 +91,7 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v0.10.0
|
||||
rev: v0.9.2
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
@@ -103,7 +103,7 @@ repos:
|
||||
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
|
||||
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.5
|
||||
rev: v1.7.4
|
||||
hooks:
|
||||
- id: actionlint
|
||||
stages:
|
||||
|
||||
@@ -467,7 +467,7 @@ cargo build --release && hyperfine --warmup 10 \
|
||||
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ --no-cache -e --select W505,E501"
|
||||
```
|
||||
|
||||
You can run `uv venv --project ./scripts/benchmarks`, activate the venv and then run `uv sync --project ./scripts/benchmarks` to create a working environment for the
|
||||
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
|
||||
above. All reported benchmarks were computed using the versions specified by
|
||||
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
|
||||
|
||||
|
||||
146
Cargo.lock
generated
146
Cargo.lock
generated
@@ -117,9 +117,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
|
||||
[[package]]
|
||||
name = "append-only-vec"
|
||||
@@ -413,7 +413,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -470,14 +470,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.1"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
@@ -693,7 +693,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -704,7 +704,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -774,7 +774,7 @@ dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -826,7 +826,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -877,9 +877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.6"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
||||
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1019,9 +1019,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
@@ -1246,7 +1246,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1420,7 +1420,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1521,9 +1521,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1547,7 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1607,14 +1607,13 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "lsp-server"
|
||||
version = "0.7.8"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9462c4dc73e17f971ec1f171d44bfffb72e65a130117233388a0ebc7ec5656f9"
|
||||
checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
@@ -2015,7 +2014,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2162,7 +2161,7 @@ dependencies = [
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2197,9 +2196,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2307,7 +2306,6 @@ dependencies = [
|
||||
"ruff_python_literal",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.1.0",
|
||||
@@ -2317,7 +2315,7 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -2414,7 +2412,7 @@ dependencies = [
|
||||
"rustc-hash 2.1.0",
|
||||
"salsa",
|
||||
"serde",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"toml",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2567,7 +2565,7 @@ dependencies = [
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"tracing",
|
||||
@@ -2637,7 +2635,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
@@ -2789,7 +2787,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
@@ -2806,7 +2804,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2823,7 +2821,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"test-case",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2895,7 +2893,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3028,7 +3026,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.9",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -3227,7 +3225,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3261,7 +3259,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3284,9 +3282,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3304,13 +3302,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3321,14 +3319,14 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.134"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -3344,7 +3342,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3367,9 +3365,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.12.0"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -3378,14 +3376,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.12.0"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
|
||||
checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3499,7 +3497,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3521,9 +3519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.93"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3538,7 +3536,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3601,7 +3599,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3612,7 +3610,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -3627,11 +3625,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.9"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
|
||||
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.9",
|
||||
"thiserror-impl 2.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3642,18 +3640,18 @@ checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.9"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
|
||||
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3775,7 +3773,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4045,7 +4043,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4140,7 +4138,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -4175,7 +4173,7 @@ checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -4209,7 +4207,7 @@ checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4512,7 +4510,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4533,7 +4531,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4553,7 +4551,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4582,7 +4580,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.93",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -20,7 +20,6 @@ ruff_python_stdlib = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
ruff_python_literal = { workspace = true }
|
||||
ruff_python_trivia = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@@ -31,10 +31,10 @@ class C:
|
||||
def __lt__(self, other) -> C: ...
|
||||
|
||||
x = A() < B() < C()
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
y = 0 < 1 < A() < 3
|
||||
reveal_type(y) # revealed: Literal[False] | A
|
||||
reveal_type(y) # revealed: bool | A
|
||||
|
||||
z = 10 < 0 < A() < B() < C()
|
||||
reveal_type(z) # revealed: Literal[False]
|
||||
|
||||
@@ -10,8 +10,8 @@ def _(foo: str):
|
||||
reveal_type(False or "z") # revealed: Literal["z"]
|
||||
reveal_type(False or True) # revealed: Literal[True]
|
||||
reveal_type(False or False) # revealed: Literal[False]
|
||||
reveal_type(foo or False) # revealed: str & ~AlwaysFalsy | Literal[False]
|
||||
reveal_type(foo or True) # revealed: str & ~AlwaysFalsy | Literal[True]
|
||||
reveal_type(foo or False) # revealed: str | Literal[False]
|
||||
reveal_type(foo or True) # revealed: str | Literal[True]
|
||||
```
|
||||
|
||||
## AND
|
||||
@@ -20,8 +20,8 @@ def _(foo: str):
|
||||
def _(foo: str):
|
||||
reveal_type(True and False) # revealed: Literal[False]
|
||||
reveal_type(False and True) # revealed: Literal[False]
|
||||
reveal_type(foo and False) # revealed: str & ~AlwaysTruthy | Literal[False]
|
||||
reveal_type(foo and True) # revealed: str & ~AlwaysTruthy | Literal[True]
|
||||
reveal_type(foo and False) # revealed: str | Literal[False]
|
||||
reveal_type(foo and True) # revealed: str | Literal[True]
|
||||
reveal_type("x" and "y" and "z") # revealed: Literal["z"]
|
||||
reveal_type("x" and "y" and "") # revealed: Literal[""]
|
||||
reveal_type("" and "y") # revealed: Literal[""]
|
||||
|
||||
@@ -219,97 +219,3 @@ else:
|
||||
# TODO: It should be A. We should improve UnionBuilder or IntersectionBuilder. (issue #15023)
|
||||
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
|
||||
```
|
||||
|
||||
## Truthiness of classes
|
||||
|
||||
```py
|
||||
class MetaAmbiguous(type):
|
||||
def __bool__(self) -> bool: ...
|
||||
|
||||
class MetaFalsy(type):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
class MetaTruthy(type):
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
|
||||
class MetaDeferred(type):
|
||||
def __bool__(self) -> MetaAmbiguous: ...
|
||||
|
||||
class AmbiguousClass(metaclass=MetaAmbiguous): ...
|
||||
class FalsyClass(metaclass=MetaFalsy): ...
|
||||
class TruthyClass(metaclass=MetaTruthy): ...
|
||||
class DeferredClass(metaclass=MetaDeferred): ...
|
||||
|
||||
def _(
|
||||
a: type[AmbiguousClass],
|
||||
t: type[TruthyClass],
|
||||
f: type[FalsyClass],
|
||||
d: type[DeferredClass],
|
||||
ta: type[TruthyClass | AmbiguousClass],
|
||||
af: type[AmbiguousClass] | type[FalsyClass],
|
||||
flag: bool,
|
||||
):
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass]
|
||||
if ta:
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass] & ~AlwaysFalsy
|
||||
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] | type[FalsyClass]
|
||||
if af:
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
|
||||
|
||||
# TODO: Emit a diagnostic (`d` is not valid in boolean context)
|
||||
if d:
|
||||
# TODO: Should be `Unknown`
|
||||
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy
|
||||
|
||||
tf = TruthyClass if flag else FalsyClass
|
||||
reveal_type(tf) # revealed: Literal[TruthyClass, FalsyClass]
|
||||
|
||||
if tf:
|
||||
reveal_type(tf) # revealed: Literal[TruthyClass]
|
||||
else:
|
||||
reveal_type(tf) # revealed: Literal[FalsyClass]
|
||||
```
|
||||
|
||||
## Narrowing in chained boolean expressions
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class A: ...
|
||||
|
||||
def _(x: Literal[0, 1]):
|
||||
reveal_type(x or A()) # revealed: Literal[1] | A
|
||||
reveal_type(x and A()) # revealed: Literal[0] | A
|
||||
|
||||
def _(x: str):
|
||||
reveal_type(x or A()) # revealed: str & ~AlwaysFalsy | A
|
||||
reveal_type(x and A()) # revealed: str & ~AlwaysTruthy | A
|
||||
|
||||
def _(x: bool | str):
|
||||
reveal_type(x or A()) # revealed: Literal[True] | str & ~AlwaysFalsy | A
|
||||
reveal_type(x and A()) # revealed: Literal[False] | str & ~AlwaysTruthy | A
|
||||
|
||||
class Falsy:
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
class Truthy:
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
|
||||
def _(x: Falsy | Truthy):
|
||||
reveal_type(x or A()) # revealed: Truthy | A
|
||||
reveal_type(x and A()) # revealed: Falsy | A
|
||||
|
||||
class MetaFalsy(type):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
class MetaTruthy(type):
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
|
||||
class FalsyClass(metaclass=MetaFalsy): ...
|
||||
class TruthyClass(metaclass=MetaTruthy): ...
|
||||
|
||||
def _(x: type[FalsyClass] | type[TruthyClass]):
|
||||
reveal_type(x or A()) # revealed: type[TruthyClass] | A
|
||||
reveal_type(x and A()) # revealed: type[FalsyClass] | A
|
||||
```
|
||||
|
||||
@@ -25,29 +25,3 @@ def f(): ...
|
||||
|
||||
f: int = 1
|
||||
```
|
||||
|
||||
## Explicit shadowing involving `def` statements
|
||||
|
||||
Since a `def` statement is a declaration, one `def` can shadow another `def`, or shadow a previous
|
||||
non-`def` declaration, without error.
|
||||
|
||||
```py
|
||||
f = 1
|
||||
reveal_type(f) # revealed: Literal[1]
|
||||
|
||||
def f(): ...
|
||||
|
||||
reveal_type(f) # revealed: Literal[f]
|
||||
|
||||
def f(x: int) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
reveal_type(f) # revealed: Literal[f]
|
||||
|
||||
f: int = 1
|
||||
reveal_type(f) # revealed: Literal[1]
|
||||
|
||||
def f(): ...
|
||||
|
||||
reveal_type(f) # revealed: Literal[f]
|
||||
```
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
# `__slots__`
|
||||
|
||||
## Not specified and empty
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
class B:
|
||||
__slots__ = ()
|
||||
|
||||
class C:
|
||||
__slots__ = ("lorem", "ipsum")
|
||||
|
||||
class AB(A, B): ... # fine
|
||||
class AC(A, C): ... # fine
|
||||
class BC(B, C): ... # fine
|
||||
class ABC(A, B, C): ... # fine
|
||||
```
|
||||
|
||||
## Incompatible tuples
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
class C(
|
||||
A, # error: [incompatible-slots]
|
||||
B, # error: [incompatible-slots]
|
||||
): ...
|
||||
```
|
||||
|
||||
## Same value
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class B:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class C(
|
||||
A, # error: [incompatible-slots]
|
||||
B, # error: [incompatible-slots]
|
||||
): ...
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = "abc"
|
||||
|
||||
class B:
|
||||
__slots__ = ("abc",)
|
||||
|
||||
class AB(
|
||||
A, # error: [incompatible-slots]
|
||||
B, # error: [incompatible-slots]
|
||||
): ...
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
TODO: Emit diagnostics
|
||||
|
||||
```py
|
||||
class NonString1:
|
||||
__slots__ = 42
|
||||
|
||||
class NonString2:
|
||||
__slots__ = b"ar"
|
||||
|
||||
class NonIdentifier1:
|
||||
__slots__ = "42"
|
||||
|
||||
class NonIdentifier2:
|
||||
__slots__ = ("lorem", "42")
|
||||
|
||||
class NonIdentifier3:
|
||||
__slots__ = (e for e in ("lorem", "42"))
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class B(A): ...
|
||||
|
||||
class C:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
class D(C): ...
|
||||
class E(
|
||||
B, # error: [incompatible-slots]
|
||||
D, # error: [incompatible-slots]
|
||||
): ...
|
||||
```
|
||||
|
||||
## Single solid base
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class B(A): ...
|
||||
class C(A): ...
|
||||
class D(B, A): ... # fine
|
||||
class E(B, C, A): ... # fine
|
||||
```
|
||||
|
||||
## False negatives
|
||||
|
||||
### Possibly unbound
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A:
|
||||
if flag:
|
||||
__slots__ = ("a", "b")
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# Might or might not be fine at runtime
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Bound but with different types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A:
|
||||
if flag:
|
||||
__slots__ = ("a", "b")
|
||||
else:
|
||||
__slots__ = ()
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# Might or might not be fine at runtime
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Non-tuples
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ["a", "b"] # This is treated as "dynamic"
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# False negative: [incompatible-slots]
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Post-hoc modifications
|
||||
|
||||
```py
|
||||
class A:
|
||||
__slots__ = ()
|
||||
__slots__ += ("a", "b")
|
||||
|
||||
reveal_type(A.__slots__) # revealed: @Todo(Support for more binary expressions)
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# False negative: [incompatible-slots]
|
||||
class C(A, B): ...
|
||||
```
|
||||
|
||||
### Built-ins with implicit layouts
|
||||
|
||||
```py
|
||||
# False negative: [incompatible-slots]
|
||||
class A(int, str): ...
|
||||
```
|
||||
@@ -1487,37 +1487,6 @@ if True:
|
||||
from module import symbol
|
||||
```
|
||||
|
||||
## Known limitations
|
||||
|
||||
We currently have a limitation in the complexity (depth) of the visibility constraints that are
|
||||
supported. This is to avoid pathological cases that would require us to recurse deeply.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or (x := 2) # fmt: skip
|
||||
|
||||
# This still works fine:
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
|
||||
y = 1
|
||||
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or False or \
|
||||
False or False or False or (y := 2) # fmt: skip
|
||||
|
||||
# TODO: This should ideally be `Literal[2]` as well:
|
||||
reveal_type(y) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## Unsupported features
|
||||
|
||||
We do not support full unreachable code analysis yet. We also raise diagnostics from
|
||||
|
||||
@@ -1,182 +0,0 @@
|
||||
# Suppressing errors with `knot: ignore`
|
||||
|
||||
Type check errors can be suppressed by a `knot: ignore` comment on the same line as the violation.
|
||||
|
||||
## Simple `knot: ignore`
|
||||
|
||||
```py
|
||||
a = 4 + test # knot: ignore
|
||||
```
|
||||
|
||||
## Suppressing a specific code
|
||||
|
||||
```py
|
||||
a = 4 + test # knot: ignore[unresolved-reference]
|
||||
```
|
||||
|
||||
## Unused suppression
|
||||
|
||||
```py
|
||||
test = 10
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
||||
```
|
||||
|
||||
## Unused suppression if the error codes don't match
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
|
||||
a = test + 3 # knot: ignore[possibly-unresolved-reference]
|
||||
```
|
||||
|
||||
## Suppressed unused comment
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment]
|
||||
a = 10 / 2 # knot: ignore[division-by-zero]
|
||||
a = 10 / 2 # knot: ignore[division-by-zero, unused-ignore-comment]
|
||||
a = 10 / 2 # knot: ignore[unused-ignore-comment, division-by-zero]
|
||||
a = 10 / 2 # knot: ignore[unused-ignore-comment] # type: ignore
|
||||
a = 10 / 2 # type: ignore # knot: ignore[unused-ignore-comment]
|
||||
```
|
||||
|
||||
## Unused ignore comment
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unused-ignore-comment'"
|
||||
a = 10 / 0 # knot: ignore[division-by-zero, unused-ignore-comment]
|
||||
```
|
||||
|
||||
## Multiple unused comments
|
||||
|
||||
Today, Red Knot emits a diagnostic for every unused code. We might want to group the codes by
|
||||
comment at some point in the future.
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'division-by-zero'"
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
||||
a = 10 / 2 # knot: ignore[division-by-zero, unresolved-reference]
|
||||
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'invalid-assignment'"
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
||||
a = 10 / 0 # knot: ignore[invalid-assignment, division-by-zero, unresolved-reference]
|
||||
```
|
||||
|
||||
## Multiple suppressions
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
def test(a: f"f-string type annotation", b: b"byte-string-type-annotation"): ... # knot: ignore[fstring-type-annotation, byte-string-type-annotation]
|
||||
```
|
||||
|
||||
## Can't suppress syntax errors
|
||||
|
||||
<!-- blacken-docs:off -->
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax]
|
||||
# error: [unused-ignore-comment]
|
||||
def test( # knot: ignore
|
||||
```
|
||||
|
||||
<!-- blacken-docs:on -->
|
||||
|
||||
## Can't suppress `revealed-type` diagnostics
|
||||
|
||||
```py
|
||||
a = 10
|
||||
# revealed: Literal[10]
|
||||
# error: [unknown-rule] "Unknown rule `revealed-type`"
|
||||
reveal_type(a) # knot: ignore[revealed-type]
|
||||
```
|
||||
|
||||
## Extra whitespace in type ignore comments is allowed
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot : ignore
|
||||
a = 10 / 0 # knot: ignore [ division-by-zero ]
|
||||
```
|
||||
|
||||
## Whitespace is optional
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
a = 10 / 0 #knot:ignore[division-by-zero]
|
||||
```
|
||||
|
||||
## Trailing codes comma
|
||||
|
||||
Trailing commas in the codes section are allowed:
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot: ignore[division-by-zero,]
|
||||
```
|
||||
|
||||
## Invalid characters in codes
|
||||
|
||||
```py
|
||||
# error: [division-by-zero]
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a alphanumeric character or `-` or `_` as code"
|
||||
a = 10 / 0 # knot: ignore[*-*]
|
||||
```
|
||||
|
||||
## Trailing whitespace
|
||||
|
||||
<!-- blacken-docs:off -->
|
||||
|
||||
```py
|
||||
a = 10 / 0 # knot: ignore[division-by-zero]
|
||||
# ^^^^^^ trailing whitespace
|
||||
```
|
||||
|
||||
<!-- blacken-docs:on -->
|
||||
|
||||
## Missing comma
|
||||
|
||||
A missing comma results in an invalid suppression comment. We may want to recover from this in the
|
||||
future.
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
||||
a = x / 0 # knot: ignore[division-by-zero unresolved-reference]
|
||||
```
|
||||
|
||||
## Missing closing bracket
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference] "Name `x` used when not defined"
|
||||
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
|
||||
a = x / 2 # knot: ignore[unresolved-reference
|
||||
```
|
||||
|
||||
## Empty codes
|
||||
|
||||
An empty codes array suppresses no-diagnostics and is always useless
|
||||
|
||||
```py
|
||||
# error: [division-by-zero]
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` without a code"
|
||||
a = 4 / 0 # knot: ignore[]
|
||||
```
|
||||
|
||||
## File-level suppression comments
|
||||
|
||||
File level suppression comments are currently intentionally unsupported because we've yet to decide
|
||||
if they should use a different syntax that also supports enabling rules or changing the rule's
|
||||
severity: `knot: possibly-undefined-reference=error`
|
||||
|
||||
```py
|
||||
# error: [unused-ignore-comment]
|
||||
# knot: ignore[division-by-zero]
|
||||
|
||||
a = 4 / 0 # error: [division-by-zero]
|
||||
```
|
||||
|
||||
## Unknown rule
|
||||
|
||||
```py
|
||||
# error: [unknown-rule] "Unknown rule `is-equal-14`"
|
||||
a = 10 + 4 # knot: ignore[is-equal-14]
|
||||
```
|
||||
@@ -1,118 +0,0 @@
|
||||
# `@no_type_check`
|
||||
|
||||
> If a type checker supports the `no_type_check` decorator for functions, it should suppress all
|
||||
> type errors for the def statement and its body including any nested functions or classes. It
|
||||
> should also ignore all parameter and return type annotations and treat the function as if it were
|
||||
> unannotated. [source](https://typing.readthedocs.io/en/latest/spec/directives.html#no-type-check)
|
||||
|
||||
## Error in the function body
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test() -> int:
|
||||
return a + 5
|
||||
```
|
||||
|
||||
## Error in nested function
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test() -> int:
|
||||
def nested():
|
||||
return a + 5
|
||||
```
|
||||
|
||||
## Error in nested class
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test() -> int:
|
||||
class Nested:
|
||||
def inner(self):
|
||||
return a + 5
|
||||
```
|
||||
|
||||
## Error in preceding decorator
|
||||
|
||||
Don't suppress diagnostics for decorators appearing before the `no_type_check` decorator.
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@unknown_decorator # error: [unresolved-reference]
|
||||
@no_type_check
|
||||
def test() -> int:
|
||||
return a + 5
|
||||
```
|
||||
|
||||
## Error in following decorator
|
||||
|
||||
Unlike Pyright and mypy, suppress diagnostics appearing after the `no_type_check` decorator. We do
|
||||
this because it more closely matches Python's runtime semantics of decorators. For more details, see
|
||||
the discussion on the
|
||||
[PR adding `@no_type_check` support](https://github.com/astral-sh/ruff/pull/15122#discussion_r1896869411).
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
@unknown_decorator
|
||||
def test() -> int:
|
||||
return a + 5
|
||||
```
|
||||
|
||||
## Error in default value
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test(a: int = "test"):
|
||||
return x + 5
|
||||
```
|
||||
|
||||
## Error in return value position
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test() -> Undefined:
|
||||
return x + 5
|
||||
```
|
||||
|
||||
## `no_type_check` on classes isn't supported
|
||||
|
||||
Red Knot does not support decorating classes with `no_type_check`. The behaviour of `no_type_check`
|
||||
when applied to classes is
|
||||
[not specified currently](https://typing.readthedocs.io/en/latest/spec/directives.html#no-type-check),
|
||||
and is not supported by Pyright or mypy.
|
||||
|
||||
A future improvement might be to emit a diagnostic if a `no_type_check` annotation is applied to a
|
||||
class.
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
class Test:
|
||||
def test(self):
|
||||
return a + 5 # error: [unresolved-reference]
|
||||
```
|
||||
|
||||
## `type: ignore` comments in `@no_type_check` blocks
|
||||
|
||||
```py
|
||||
from typing import no_type_check
|
||||
|
||||
@no_type_check
|
||||
def test():
|
||||
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
|
||||
return x + 5 # knot: ignore[unresolved-reference]
|
||||
```
|
||||
@@ -1,162 +0,0 @@
|
||||
# Suppressing errors with `type: ignore`
|
||||
|
||||
Type check errors can be suppressed by a `type: ignore` comment on the same line as the violation.
|
||||
|
||||
## Simple `type: ignore`
|
||||
|
||||
```py
|
||||
a = 4 + test # type: ignore
|
||||
```
|
||||
|
||||
## Multiline ranges
|
||||
|
||||
A diagnostic with a multiline range can be suppressed by a comment on the same line as the
|
||||
diagnostic's start or end. This is the same behavior as Mypy's.
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
y = (
|
||||
4 / 0 # type: ignore
|
||||
)
|
||||
|
||||
y = (
|
||||
4 / # type: ignore
|
||||
0
|
||||
)
|
||||
|
||||
y = (
|
||||
4 /
|
||||
0 # type: ignore
|
||||
)
|
||||
```
|
||||
|
||||
Pyright diverges from this behavior and instead applies a suppression if its range intersects with
|
||||
the diagnostic range. This can be problematic for nested expressions because a suppression in a
|
||||
child expression now suppresses errors in the outer expression.
|
||||
|
||||
For example, the `type: ignore` comment in this example suppresses the error of adding `2` to
|
||||
`"test"` and adding `"other"` to the result of the cast.
|
||||
|
||||
```py path=nested.py
|
||||
# fmt: off
|
||||
from typing import cast
|
||||
|
||||
y = (
|
||||
cast(int, "test" +
|
||||
# TODO: Remove the expected error after implementing `invalid-operator` for binary expressions
|
||||
# error: [unused-ignore-comment]
|
||||
2 # type: ignore
|
||||
)
|
||||
+ "other" # TODO: expected-error[invalid-operator]
|
||||
)
|
||||
```
|
||||
|
||||
Mypy flags the second usage.
|
||||
|
||||
## Before opening parenthesis
|
||||
|
||||
A suppression that applies to all errors before the opening parenthesis.
|
||||
|
||||
```py
|
||||
a: Test = ( # type: ignore
|
||||
Test() # error: [unresolved-reference]
|
||||
) # fmt: skip
|
||||
```
|
||||
|
||||
## Multiline string
|
||||
|
||||
```py
|
||||
a: int = 4
|
||||
a = """
|
||||
This is a multiline string and the suppression is at its end
|
||||
""" # type: ignore
|
||||
```
|
||||
|
||||
## Line continuations
|
||||
|
||||
Suppressions after a line continuation apply to all previous lines.
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
a = test \
|
||||
+ 2 # type: ignore
|
||||
|
||||
a = test \
|
||||
+ a \
|
||||
+ 2 # type: ignore
|
||||
```
|
||||
|
||||
## Codes
|
||||
|
||||
Mypy supports `type: ignore[code]`. Red Knot doesn't understand mypy's rule names. Therefore, ignore
|
||||
the codes and suppress all errors.
|
||||
|
||||
```py
|
||||
a = test # type: ignore[name-defined]
|
||||
```
|
||||
|
||||
## Nested comments
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
a = test \
|
||||
+ 2 # fmt: skip # type: ignore
|
||||
|
||||
a = test \
|
||||
+ 2 # type: ignore # fmt: skip
|
||||
```
|
||||
|
||||
## Misspelled `type: ignore`
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
# error: [invalid-ignore-comment]
|
||||
a = test + 2 # type: ignoree
|
||||
```
|
||||
|
||||
## Invalid - ignore on opening parentheses
|
||||
|
||||
`type: ignore` comments after an opening parentheses suppress any type errors inside the parentheses
|
||||
in Pyright. Neither Ruff, nor mypy support this and neither does Red Knot.
|
||||
|
||||
```py
|
||||
# fmt: off
|
||||
# error: [unused-ignore-comment]
|
||||
a = ( # type: ignore
|
||||
test + 4 # error: [unresolved-reference]
|
||||
)
|
||||
```
|
||||
|
||||
## File level suppression
|
||||
|
||||
```py
|
||||
# type: ignore
|
||||
|
||||
a = 10 / 0
|
||||
b = a / 0
|
||||
```
|
||||
|
||||
## File level suppression with leading shebang
|
||||
|
||||
```py
|
||||
#!/usr/bin/env/python
|
||||
# type: ignore
|
||||
|
||||
a = 10 / 0
|
||||
b = a / 0
|
||||
```
|
||||
|
||||
## Invalid own-line suppression
|
||||
|
||||
```py
|
||||
"""
|
||||
File level suppressions must come before any non-trivia token,
|
||||
including module docstrings.
|
||||
"""
|
||||
|
||||
# error: [unused-ignore-comment] "Unused blanket `type: ignore` directive"
|
||||
# type: ignore
|
||||
|
||||
a = 10 / 0 # error: [division-by-zero]
|
||||
b = a / 0 # error: [division-by-zero]
|
||||
```
|
||||
@@ -61,7 +61,7 @@ reveal_type(c) # revealed: Literal[4]
|
||||
### Uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# TODO: Add diagnostic (there aren't enough values to unpack)
|
||||
(a, b, c) = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[2]
|
||||
@@ -71,7 +71,7 @@ reveal_type(c) # revealed: Unknown
|
||||
### Uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# TODO: Add diagnostic (too many values to unpack)
|
||||
(a, b) = (1, 2, 3)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[2]
|
||||
@@ -80,7 +80,7 @@ reveal_type(b) # revealed: Literal[2]
|
||||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
[a, *b, c, d] = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
@@ -133,7 +133,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
### Starred expression (6)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 5 or more, got 1)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, b, c, *d, e, f) = (1,)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -199,7 +199,7 @@ reveal_type(b) # revealed: LiteralString
|
||||
### Uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# TODO: Add diagnostic (there aren't enough values to unpack)
|
||||
a, b, c = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
@@ -209,7 +209,7 @@ reveal_type(c) # revealed: Unknown
|
||||
### Uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# TODO: Add diagnostic (too many values to unpack)
|
||||
a, b = "abc"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
@@ -218,7 +218,7 @@ reveal_type(b) # revealed: LiteralString
|
||||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, *b, c, d) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
@@ -271,7 +271,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
### Unicode
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, b) = "é"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
@@ -281,7 +281,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Unicode escape (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, b) = "\u9E6C"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
@@ -291,7 +291,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Unicode escape (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# TODO: Add diagnostic (need more values to unpack)
|
||||
(a, b) = "\U0010FFFF"
|
||||
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
@@ -306,273 +306,3 @@ reveal_type(b) # revealed: Unknown
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
```
|
||||
|
||||
## Union
|
||||
|
||||
### Same types
|
||||
|
||||
Union of two tuples of equal length and each element is of the same type.
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, int] | tuple[int, int]):
|
||||
(a, b) = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### Mixed types (1)
|
||||
|
||||
Union of two tuples of equal length and one element differs in its type.
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, int] | tuple[int, str]):
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int | str
|
||||
```
|
||||
|
||||
### Mixed types (2)
|
||||
|
||||
Union of two tuples of equal length and both the element types are different.
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, str] | tuple[str, int]):
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int | str
|
||||
reveal_type(b) # revealed: str | int
|
||||
```
|
||||
|
||||
### Mixed types (3)
|
||||
|
||||
Union of three tuples of equal length and various combination of element types:
|
||||
|
||||
1. All same types
|
||||
1. One different type
|
||||
1. All different types
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, int, int] | tuple[int, str, bytes] | tuple[int, int, str]):
|
||||
a, b, c = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int | str
|
||||
reveal_type(c) # revealed: int | bytes | str
|
||||
```
|
||||
|
||||
### Nested
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, tuple[str, bytes]] | tuple[tuple[int, bytes], Literal["ab"]]):
|
||||
a, (b, c) = arg
|
||||
reveal_type(a) # revealed: int | tuple[int, bytes]
|
||||
reveal_type(b) # revealed: str
|
||||
reveal_type(c) # revealed: bytes | LiteralString
|
||||
```
|
||||
|
||||
### Starred expression
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
||||
a, *b, c = arg
|
||||
reveal_type(a) # revealed: int
|
||||
# TODO: Should be `list[bytes | int | str]`
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(c) # revealed: int | bytes
|
||||
```
|
||||
|
||||
### Size mismatch (1)
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 5)"
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: bytes | int
|
||||
```
|
||||
|
||||
### Size mismatch (2)
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, bytes] | tuple[int, str]):
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
a, b, c = arg
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: bytes | str
|
||||
reveal_type(c) # revealed: Unknown
|
||||
```
|
||||
|
||||
### Same literal types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
value = (1, 2)
|
||||
else:
|
||||
value = (3, 4)
|
||||
|
||||
a, b = value
|
||||
reveal_type(a) # revealed: Literal[1, 3]
|
||||
reveal_type(b) # revealed: Literal[2, 4]
|
||||
```
|
||||
|
||||
### Mixed literal types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
value = (1, 2)
|
||||
else:
|
||||
value = ("a", "b")
|
||||
|
||||
a, b = value
|
||||
reveal_type(a) # revealed: Literal[1] | Literal["a"]
|
||||
reveal_type(b) # revealed: Literal[2] | Literal["b"]
|
||||
```
|
||||
|
||||
### Typing literal
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(arg: tuple[int, int] | Literal["ab"]):
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int | LiteralString
|
||||
reveal_type(b) # revealed: int | LiteralString
|
||||
```
|
||||
|
||||
### Custom iterator (1)
|
||||
|
||||
```py
|
||||
class Iterator:
|
||||
def __next__(self) -> tuple[int, int] | tuple[int, str]:
|
||||
return (1, 2)
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
((a, b), c) = Iterable()
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int | str
|
||||
reveal_type(c) # revealed: tuple[int, int] | tuple[int, str]
|
||||
```
|
||||
|
||||
### Custom iterator (2)
|
||||
|
||||
```py
|
||||
class Iterator:
|
||||
def __next__(self) -> bytes:
|
||||
return b""
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
def _(arg: tuple[int, str] | Iterable):
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: int | bytes
|
||||
reveal_type(b) # revealed: str | bytes
|
||||
```
|
||||
|
||||
## For statement
|
||||
|
||||
Unpacking in a `for` statement.
|
||||
|
||||
### Same types
|
||||
|
||||
```py
|
||||
def _(arg: tuple[tuple[int, int], tuple[int, int]]):
|
||||
for a, b in arg:
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### Mixed types (1)
|
||||
|
||||
```py
|
||||
def _(arg: tuple[tuple[int, int], tuple[int, str]]):
|
||||
for a, b in arg:
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int | str
|
||||
```
|
||||
|
||||
### Mixed types (2)
|
||||
|
||||
```py
|
||||
def _(arg: tuple[tuple[int, str], tuple[str, int]]):
|
||||
for a, b in arg:
|
||||
reveal_type(a) # revealed: int | str
|
||||
reveal_type(b) # revealed: str | int
|
||||
```
|
||||
|
||||
### Mixed types (3)
|
||||
|
||||
```py
|
||||
def _(arg: tuple[tuple[int, int, int], tuple[int, str, bytes], tuple[int, int, str]]):
|
||||
for a, b, c in arg:
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int | str
|
||||
reveal_type(c) # revealed: int | bytes | str
|
||||
```
|
||||
|
||||
### Same literal values
|
||||
|
||||
```py
|
||||
for a, b in ((1, 2), (3, 4)):
|
||||
reveal_type(a) # revealed: Literal[1, 3]
|
||||
reveal_type(b) # revealed: Literal[2, 4]
|
||||
```
|
||||
|
||||
### Mixed literal values (1)
|
||||
|
||||
```py
|
||||
for a, b in ((1, 2), ("a", "b")):
|
||||
reveal_type(a) # revealed: Literal[1] | Literal["a"]
|
||||
reveal_type(b) # revealed: Literal[2] | Literal["b"]
|
||||
```
|
||||
|
||||
### Mixed literals values (2)
|
||||
|
||||
```py
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
# error: "Object of type `Literal[2]` is not iterable"
|
||||
# error: "Object of type `Literal[4]` is not iterable"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c"):
|
||||
reveal_type(a) # revealed: Unknown | Literal[3, 5] | LiteralString
|
||||
reveal_type(b) # revealed: Unknown | Literal["a", "b"]
|
||||
```
|
||||
|
||||
### Custom iterator (1)
|
||||
|
||||
```py
|
||||
class Iterator:
|
||||
def __next__(self) -> tuple[int, int]:
|
||||
return (1, 2)
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
for a, b in Iterable():
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
```
|
||||
|
||||
### Custom iterator (2)
|
||||
|
||||
```py
|
||||
class Iterator:
|
||||
def __next__(self) -> bytes:
|
||||
return b""
|
||||
|
||||
class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
def _(arg: tuple[tuple[int, str], Iterable]):
|
||||
for a, b in arg:
|
||||
reveal_type(a) # revealed: int | bytes
|
||||
reveal_type(b) # revealed: str | bytes
|
||||
```
|
||||
|
||||
@@ -43,7 +43,7 @@ impl<T> AstNodeRef<T> {
|
||||
}
|
||||
|
||||
/// Returns a reference to the wrapped node.
|
||||
pub const fn node(&self) -> &T {
|
||||
pub fn node(&self) -> &T {
|
||||
// SAFETY: Holding on to `parsed` ensures that the AST to which `node` belongs is still
|
||||
// alive and not moved.
|
||||
unsafe { self.node.as_ref() }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
use crate::lint::RuleSelection;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
|
||||
@@ -8,8 +8,6 @@ pub trait Db: SourceDb + Upcast<dyn SourceDb> {
|
||||
fn is_file_open(&self, file: File) -> bool;
|
||||
|
||||
fn rule_selection(&self) -> &RuleSelection;
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -21,7 +19,7 @@ pub(crate) mod tests {
|
||||
use crate::{default_lint_registry, ProgramSettings, PythonPlatform};
|
||||
|
||||
use super::Db;
|
||||
use crate::lint::{LintRegistry, RuleSelection};
|
||||
use crate::lint::RuleSelection;
|
||||
use anyhow::Context;
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem};
|
||||
@@ -47,7 +45,7 @@ pub(crate) mod tests {
|
||||
vendored: red_knot_vendored::file_system().clone(),
|
||||
events: Arc::default(),
|
||||
files: Files::default(),
|
||||
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
|
||||
rule_selection: Arc::new(RuleSelection::from_registry(&default_lint_registry())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,10 +112,6 @@ pub(crate) mod tests {
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -3,7 +3,6 @@ use std::hash::BuildHasherDefault;
|
||||
use rustc_hash::FxHasher;
|
||||
|
||||
use crate::lint::{LintRegistry, LintRegistryBuilder};
|
||||
use crate::suppression::{INVALID_IGNORE_COMMENT, UNKNOWN_RULE, UNUSED_IGNORE_COMMENT};
|
||||
pub use db::Db;
|
||||
pub use module_name::ModuleName;
|
||||
pub use module_resolver::{resolve_module, system_module_search_paths, KnownModule, Module};
|
||||
@@ -25,7 +24,6 @@ pub mod semantic_index;
|
||||
mod semantic_model;
|
||||
pub(crate) mod site_packages;
|
||||
mod stdlib;
|
||||
mod suppression;
|
||||
pub(crate) mod symbol;
|
||||
pub mod types;
|
||||
mod unpack;
|
||||
@@ -34,21 +32,14 @@ mod visibility_constraints;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Returns the default registry with all known semantic lints.
|
||||
pub fn default_lint_registry() -> &'static LintRegistry {
|
||||
static REGISTRY: std::sync::LazyLock<LintRegistry> = std::sync::LazyLock::new(|| {
|
||||
let mut registry = LintRegistryBuilder::default();
|
||||
register_lints(&mut registry);
|
||||
registry.build()
|
||||
});
|
||||
|
||||
®ISTRY
|
||||
/// Creates a new registry with all known semantic lints.
|
||||
pub fn default_lint_registry() -> LintRegistry {
|
||||
let mut registry = LintRegistryBuilder::default();
|
||||
register_lints(&mut registry);
|
||||
registry.build()
|
||||
}
|
||||
|
||||
/// Register all known semantic lints.
|
||||
pub fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
types::register_lints(registry);
|
||||
registry.register_lint(&UNUSED_IGNORE_COMMENT);
|
||||
registry.register_lint(&UNKNOWN_RULE);
|
||||
registry.register_lint(&INVALID_IGNORE_COMMENT);
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ impl LintRegistryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct LintRegistry {
|
||||
lints: Vec<LintId>,
|
||||
by_name: FxHashMap<&'static str, LintEntry>,
|
||||
@@ -374,7 +374,7 @@ impl LintRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Error, Debug, Clone)]
|
||||
pub enum GetLintError {
|
||||
/// The name maps to this removed lint.
|
||||
#[error("lint {0} has been removed")]
|
||||
@@ -385,7 +385,7 @@ pub enum GetLintError {
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LintEntry {
|
||||
/// An existing lint rule. Can be in preview, stable or deprecated.
|
||||
Lint(LintId),
|
||||
@@ -444,11 +444,6 @@ impl RuleSelection {
|
||||
self.lints.get(&lint).copied()
|
||||
}
|
||||
|
||||
/// Returns `true` if the `lint` is enabled.
|
||||
pub fn is_enabled(&self, lint: LintId) -> bool {
|
||||
self.severity(lint).is_some()
|
||||
}
|
||||
|
||||
/// Enables `lint` and configures with the given `severity`.
|
||||
///
|
||||
/// Overrides any previous configuration for the lint.
|
||||
|
||||
@@ -30,7 +30,7 @@ mod use_def;
|
||||
|
||||
pub(crate) use self::use_def::{
|
||||
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint,
|
||||
DeclarationsIterator, ScopedVisibilityConstraintId,
|
||||
DeclarationsIterator,
|
||||
};
|
||||
|
||||
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>;
|
||||
@@ -156,7 +156,7 @@ impl<'db> SemanticIndex<'db> {
|
||||
/// Use the Salsa cached [`use_def_map()`] query if you only need the
|
||||
/// use-def map for a single scope.
|
||||
#[track_caller]
|
||||
pub(super) fn use_def_map(&self, scope_id: FileScopeId) -> Arc<UseDefMap> {
|
||||
pub(super) fn use_def_map(&'db self, scope_id: FileScopeId) -> Arc<UseDefMap<'db>> {
|
||||
self.use_def_maps[scope_id].clone()
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModule;
|
||||
use ruff_index::IndexVec;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
|
||||
use ruff_python_ast::{self as ast, Pattern};
|
||||
use ruff_python_ast::{BoolOp, Expr};
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::module_name::ModuleName;
|
||||
@@ -24,11 +25,9 @@ use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
|
||||
SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{
|
||||
FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::unpack::{Unpack, UnpackValue};
|
||||
use crate::unpack::Unpack;
|
||||
use crate::visibility_constraints::VisibilityConstraint;
|
||||
use crate::Db;
|
||||
|
||||
@@ -66,9 +65,9 @@ pub(super) struct SemanticIndexBuilder<'db> {
|
||||
current_match_case: Option<CurrentMatchCase<'db>>,
|
||||
|
||||
/// Flow states at each `break` in the current loop.
|
||||
loop_break_states: Vec<FlowSnapshot>,
|
||||
loop_break_states: Vec<FlowSnapshot<'db>>,
|
||||
/// Per-scope contexts regarding nested `try`/`except` statements
|
||||
try_node_context_stack_manager: TryNodeContextStackManager,
|
||||
try_node_context_stack_manager: TryNodeContextStackManager<'db>,
|
||||
|
||||
/// Flags about the file's global scope
|
||||
has_future_annotations: bool,
|
||||
@@ -160,7 +159,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
|
||||
let file_scope_id = self.scopes.push(scope);
|
||||
self.symbol_tables.push(SymbolTableBuilder::default());
|
||||
self.use_def_maps.push(UseDefMapBuilder::default());
|
||||
self.use_def_maps.push(UseDefMapBuilder::new(self.db));
|
||||
let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default());
|
||||
|
||||
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
|
||||
@@ -203,16 +202,17 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
&mut self.ast_ids[scope_id]
|
||||
}
|
||||
|
||||
fn flow_snapshot(&self) -> FlowSnapshot {
|
||||
fn flow_snapshot(&'db self) -> FlowSnapshot<'db> {
|
||||
self.current_use_def_map().snapshot()
|
||||
}
|
||||
|
||||
fn flow_restore(&mut self, state: FlowSnapshot) {
|
||||
fn flow_restore(&'db mut self, state: FlowSnapshot<'db>) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot) {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
fn flow_merge(&mut self, state: FlowSnapshot<'db>) {
|
||||
let db = self.db;
|
||||
self.current_use_def_map_mut().merge(db, state);
|
||||
}
|
||||
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
@@ -236,7 +236,8 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
}
|
||||
|
||||
fn add_definition(
|
||||
&mut self,
|
||||
&'db mut self,
|
||||
db: &'db dyn Db,
|
||||
symbol: ScopedSymbolId,
|
||||
definition_node: impl Into<DefinitionNodeRef<'db>>,
|
||||
) -> Definition<'db> {
|
||||
@@ -269,10 +270,10 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
let use_def = self.current_use_def_map_mut();
|
||||
match category {
|
||||
DefinitionCategory::DeclarationAndBinding => {
|
||||
use_def.record_declaration_and_binding(symbol, definition);
|
||||
use_def.record_declaration_and_binding(db, symbol, definition);
|
||||
}
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(symbol, definition),
|
||||
DefinitionCategory::Binding => use_def.record_binding(symbol, definition),
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(db, symbol, definition),
|
||||
DefinitionCategory::Binding => use_def.record_binding(db, symbol, definition),
|
||||
}
|
||||
|
||||
let mut try_node_stack_manager = std::mem::take(&mut self.try_node_context_stack_manager);
|
||||
@@ -288,7 +289,7 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
constraint
|
||||
}
|
||||
|
||||
fn build_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> {
|
||||
fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> {
|
||||
let expression = self.add_standalone_expression(constraint_node);
|
||||
Constraint {
|
||||
node: ConstraintNode::Expression(expression),
|
||||
@@ -296,91 +297,71 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new constraint to the list of all constraints, but does not record it. Returns the
|
||||
/// constraint ID for later recording using [`SemanticIndexBuilder::record_constraint_id`].
|
||||
fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
self.current_use_def_map_mut().add_constraint(constraint)
|
||||
fn add_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> {
|
||||
self.current_use_def_map_mut().add_constraint(constraint);
|
||||
constraint
|
||||
}
|
||||
|
||||
/// Negates a constraint and adds it to the list of all constraints, does not record it.
|
||||
fn add_negated_constraint(
|
||||
&mut self,
|
||||
constraint: Constraint<'db>,
|
||||
) -> (Constraint<'db>, ScopedConstraintId) {
|
||||
fn add_negated_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> {
|
||||
let negated = Constraint {
|
||||
node: constraint.node,
|
||||
is_positive: false,
|
||||
};
|
||||
let id = self.current_use_def_map_mut().add_constraint(negated);
|
||||
(negated, id)
|
||||
self.current_use_def_map_mut().add_constraint(negated);
|
||||
negated
|
||||
}
|
||||
|
||||
/// Records a previously added constraint by adding it to all live bindings.
|
||||
fn record_constraint_id(&mut self, constraint: ScopedConstraintId) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_constraint_id(constraint);
|
||||
}
|
||||
|
||||
/// Adds and records a constraint, i.e. adds it to all live bindings.
|
||||
fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
self.current_use_def_map_mut().record_constraint(constraint);
|
||||
}
|
||||
|
||||
/// Negates the given constraint and then adds it to all live bindings.
|
||||
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
let (_, id) = self.add_negated_constraint(constraint);
|
||||
self.record_constraint_id(id);
|
||||
id
|
||||
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> Constraint<'db> {
|
||||
let negated = self.add_negated_constraint(constraint);
|
||||
self.record_constraint(negated);
|
||||
negated
|
||||
}
|
||||
|
||||
/// Adds a new visibility constraint, but does not record it. Returns the constraint ID
|
||||
/// for later recording using [`SemanticIndexBuilder::record_visibility_constraint_id`].
|
||||
fn add_visibility_constraint(
|
||||
&mut self,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
self.current_use_def_map_mut()
|
||||
.add_visibility_constraint(constraint)
|
||||
}
|
||||
|
||||
/// Records a previously added visibility constraint by applying it to all live bindings
|
||||
/// and declarations.
|
||||
fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint_id(constraint);
|
||||
}
|
||||
|
||||
/// Negates the given visibility constraint and then adds it to all live bindings and declarations.
|
||||
fn record_negated_visibility_constraint(
|
||||
&mut self,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint))
|
||||
}
|
||||
|
||||
/// Records a visibility constraint by applying it to all live bindings and declarations.
|
||||
fn record_visibility_constraint(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(db, constraint);
|
||||
}
|
||||
|
||||
fn create_and_record_visibility_constraint(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
constraint: Constraint<'db>,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
) -> VisibilityConstraint<'db> {
|
||||
let constraint = VisibilityConstraint::visible_if(db, constraint);
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint))
|
||||
.record_visibility_constraint(db, constraint);
|
||||
constraint
|
||||
}
|
||||
|
||||
/// Records a [`VisibilityConstraint::Ambiguous`] constraint.
|
||||
fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId {
|
||||
fn record_ambiguous_visibility(&mut self, db: &'db dyn Db) {
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(VisibilityConstraint::Ambiguous)
|
||||
.record_visibility_constraint(db, VisibilityConstraint::ambiguous(db));
|
||||
}
|
||||
|
||||
/// Simplifies (resets) visibility constraints on all live bindings and declarations that did
|
||||
/// not see any new definitions since the given snapshot.
|
||||
fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) {
|
||||
fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot<'db>) {
|
||||
self.current_use_def_map_mut()
|
||||
.simplify_visibility_constraints(snapshot);
|
||||
}
|
||||
|
||||
fn record_negated_visibility_constraint(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) -> VisibilityConstraint<'db> {
|
||||
let constraint = VisibilityConstraint::visible_if_not(db, constraint);
|
||||
self.current_use_def_map_mut()
|
||||
.record_visibility_constraint(db, constraint);
|
||||
constraint
|
||||
}
|
||||
|
||||
fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) {
|
||||
self.current_assignments.push(assignment);
|
||||
}
|
||||
@@ -407,11 +388,11 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
let guard = guard.map(|guard| self.add_standalone_expression(guard));
|
||||
|
||||
let kind = match pattern {
|
||||
ast::Pattern::MatchValue(pattern) => {
|
||||
Pattern::MatchValue(pattern) => {
|
||||
let value = self.add_standalone_expression(&pattern.value);
|
||||
PatternConstraintKind::Value(value, guard)
|
||||
}
|
||||
ast::Pattern::MatchSingleton(singleton) => {
|
||||
Pattern::MatchSingleton(singleton) => {
|
||||
PatternConstraintKind::Singleton(singleton.value, guard)
|
||||
}
|
||||
_ => PatternConstraintKind::Unsupported,
|
||||
@@ -490,9 +471,11 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.visit_expr(default);
|
||||
}
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(self.db, symbol, node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(self.db, symbol, node),
|
||||
ast::TypeParam::TypeVarTuple(node) => {
|
||||
self.add_definition(self.db, symbol, node)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -573,20 +556,25 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
if let Some(vararg) = parameters.vararg.as_ref() {
|
||||
let symbol = self.add_symbol(vararg.name.id().clone());
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
DefinitionNodeRef::VariadicPositionalParameter(vararg),
|
||||
);
|
||||
}
|
||||
if let Some(kwarg) = parameters.kwarg.as_ref() {
|
||||
let symbol = self.add_symbol(kwarg.name.id().clone());
|
||||
self.add_definition(symbol, DefinitionNodeRef::VariadicKeywordParameter(kwarg));
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
DefinitionNodeRef::VariadicKeywordParameter(kwarg),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_parameter(&mut self, parameter: &'db ast::ParameterWithDefault) {
|
||||
let symbol = self.add_symbol(parameter.parameter.name.id().clone());
|
||||
|
||||
let definition = self.add_definition(symbol, parameter);
|
||||
let definition = self.add_definition(self.db, symbol, parameter);
|
||||
|
||||
// Insert a mapping from the inner Parameter node to the same definition.
|
||||
// This ensures that calling `HasTy::ty` on the inner parameter returns
|
||||
@@ -701,7 +689,7 @@ where
|
||||
// at the end to match the runtime evaluation of parameter defaults
|
||||
// and return-type annotations.
|
||||
let symbol = self.add_symbol(name.id.clone());
|
||||
self.add_definition(symbol, function_def);
|
||||
self.add_definition(self.db, symbol, function_def);
|
||||
}
|
||||
ast::Stmt::ClassDef(class) => {
|
||||
for decorator in &class.decorator_list {
|
||||
@@ -709,7 +697,7 @@ where
|
||||
}
|
||||
|
||||
let symbol = self.add_symbol(class.name.id.clone());
|
||||
self.add_definition(symbol, class);
|
||||
self.add_definition(self.db, symbol, class);
|
||||
|
||||
self.with_type_params(
|
||||
NodeWithScopeRef::ClassTypeParameters(class),
|
||||
@@ -734,7 +722,7 @@ where
|
||||
.map(|name| name.id.clone())
|
||||
.unwrap_or("<unknown>".into()),
|
||||
);
|
||||
self.add_definition(symbol, type_alias);
|
||||
self.add_definition(self.db, symbol, type_alias);
|
||||
self.visit_expr(&type_alias.name);
|
||||
|
||||
self.with_type_params(
|
||||
@@ -762,7 +750,7 @@ where
|
||||
};
|
||||
|
||||
let symbol = self.add_symbol(symbol_name);
|
||||
self.add_definition(symbol, alias);
|
||||
self.add_definition(self.db, symbol, alias);
|
||||
}
|
||||
}
|
||||
ast::Stmt::ImportFrom(node) => {
|
||||
@@ -783,7 +771,11 @@ where
|
||||
|
||||
let symbol = self.add_symbol(symbol_name.clone());
|
||||
|
||||
self.add_definition(symbol, ImportFromDefinitionNodeRef { node, alias_index });
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
ImportFromDefinitionNodeRef { node, alias_index },
|
||||
);
|
||||
}
|
||||
}
|
||||
ast::Stmt::Assign(node) => {
|
||||
@@ -809,7 +801,7 @@ where
|
||||
unsafe {
|
||||
AstNodeRef::new(self.module.clone(), target)
|
||||
},
|
||||
UnpackValue::Assign(value),
|
||||
value,
|
||||
countme::Count::default(),
|
||||
)),
|
||||
})
|
||||
@@ -883,7 +875,8 @@ where
|
||||
let mut constraints = vec![constraint];
|
||||
self.visit_body(&node.body);
|
||||
|
||||
let visibility_constraint_id = self.record_visibility_constraint(constraint);
|
||||
let visibility_constraint_id =
|
||||
self.create_and_record_visibility_constraint(self.db, constraint);
|
||||
let mut vis_constraints = vec![visibility_constraint_id];
|
||||
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
@@ -924,10 +917,11 @@ where
|
||||
self.visit_body(clause_body);
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
self.record_negated_visibility_constraint(self.db, *id);
|
||||
}
|
||||
if let Some(elif_constraint) = elif_constraint {
|
||||
let id = self.record_visibility_constraint(elif_constraint);
|
||||
let id =
|
||||
self.create_and_record_visibility_constraint(self.db, elif_constraint);
|
||||
vis_constraints.push(id);
|
||||
}
|
||||
}
|
||||
@@ -959,7 +953,8 @@ where
|
||||
self.visit_body(body);
|
||||
self.set_inside_loop(outer_loop_state);
|
||||
|
||||
let vis_constraint_id = self.record_visibility_constraint(constraint);
|
||||
let vis_constraint_id =
|
||||
self.create_and_record_visibility_constraint(self.db, constraint);
|
||||
|
||||
// Get the break states from the body of this loop, and restore the saved outer
|
||||
// ones.
|
||||
@@ -971,14 +966,14 @@ where
|
||||
self.flow_merge(pre_loop.clone());
|
||||
self.record_negated_constraint(constraint);
|
||||
self.visit_body(orelse);
|
||||
self.record_negated_visibility_constraint(vis_constraint_id);
|
||||
self.record_negated_visibility_constraint(self.db, vis_constraint_id);
|
||||
|
||||
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
let snapshot = self.flow_snapshot();
|
||||
self.flow_restore(break_state);
|
||||
self.record_visibility_constraint(constraint);
|
||||
self.create_and_record_visibility_constraint(self.db, constraint);
|
||||
self.flow_merge(snapshot);
|
||||
}
|
||||
|
||||
@@ -1020,47 +1015,18 @@ where
|
||||
orelse,
|
||||
},
|
||||
) => {
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
|
||||
let iter_expr = self.add_standalone_expression(iter);
|
||||
self.add_standalone_expression(iter);
|
||||
self.visit_expr(iter);
|
||||
|
||||
self.record_ambiguous_visibility();
|
||||
self.record_ambiguous_visibility(self.db);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
|
||||
let current_assignment = match &**target {
|
||||
ast::Expr::List(_) | ast::Expr::Tuple(_) => Some(CurrentAssignment::For {
|
||||
node: for_stmt,
|
||||
first: true,
|
||||
unpack: Some(Unpack::new(
|
||||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
AstNodeRef::new(self.module.clone(), target)
|
||||
},
|
||||
UnpackValue::Iterable(iter_expr),
|
||||
countme::Count::default(),
|
||||
)),
|
||||
}),
|
||||
ast::Expr::Name(_) => Some(CurrentAssignment::For {
|
||||
node: for_stmt,
|
||||
unpack: None,
|
||||
first: false,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(current_assignment) = current_assignment {
|
||||
self.push_assignment(current_assignment);
|
||||
}
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
self.push_assignment(for_stmt.into());
|
||||
self.visit_expr(target);
|
||||
if current_assignment.is_some() {
|
||||
self.pop_assignment();
|
||||
}
|
||||
self.pop_assignment();
|
||||
|
||||
// TODO: Definitions created by loop variables
|
||||
// (and definitions created inside the body)
|
||||
@@ -1106,7 +1072,7 @@ where
|
||||
self.visit_match_case(first);
|
||||
|
||||
let first_vis_constraint_id =
|
||||
self.record_visibility_constraint(first_constraint_id);
|
||||
self.create_and_record_visibility_constraint(self.db, first_constraint_id);
|
||||
let mut vis_constraints = vec![first_vis_constraint_id];
|
||||
|
||||
let mut post_case_snapshots = vec![];
|
||||
@@ -1121,9 +1087,10 @@ where
|
||||
self.visit_match_case(case);
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
self.record_negated_visibility_constraint(self.db, *id);
|
||||
}
|
||||
let vis_constraint_id = self.record_visibility_constraint(constraint_id);
|
||||
let vis_constraint_id =
|
||||
self.create_and_record_visibility_constraint(self.db, constraint_id);
|
||||
vis_constraints.push(vis_constraint_id);
|
||||
}
|
||||
|
||||
@@ -1137,7 +1104,7 @@ where
|
||||
self.flow_restore(after_subject.clone());
|
||||
|
||||
for id in &vis_constraints {
|
||||
self.record_negated_visibility_constraint(*id);
|
||||
self.record_negated_visibility_constraint(self.db, *id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,7 +1122,7 @@ where
|
||||
is_star,
|
||||
range: _,
|
||||
}) => {
|
||||
self.record_ambiguous_visibility();
|
||||
self.record_ambiguous_visibility(self.db);
|
||||
|
||||
// Save the state prior to visiting any of the `try` block.
|
||||
//
|
||||
@@ -1214,6 +1181,7 @@ where
|
||||
let symbol = self.add_symbol(symbol_name.id.clone());
|
||||
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef {
|
||||
handler: except_handler,
|
||||
@@ -1296,6 +1264,7 @@ where
|
||||
unpack,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
AssignmentDefinitionNodeRef {
|
||||
unpack,
|
||||
@@ -1306,23 +1275,18 @@ where
|
||||
);
|
||||
}
|
||||
Some(CurrentAssignment::AnnAssign(ann_assign)) => {
|
||||
self.add_definition(symbol, ann_assign);
|
||||
self.add_definition(self.db, symbol, ann_assign);
|
||||
}
|
||||
Some(CurrentAssignment::AugAssign(aug_assign)) => {
|
||||
self.add_definition(symbol, aug_assign);
|
||||
self.add_definition(self.db, symbol, aug_assign);
|
||||
}
|
||||
Some(CurrentAssignment::For {
|
||||
node,
|
||||
first,
|
||||
unpack,
|
||||
}) => {
|
||||
Some(CurrentAssignment::For(node)) => {
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
ForStmtDefinitionNodeRef {
|
||||
unpack,
|
||||
first,
|
||||
iterable: &node.iter,
|
||||
name: name_node,
|
||||
target: name_node,
|
||||
is_async: node.is_async,
|
||||
},
|
||||
);
|
||||
@@ -1331,10 +1295,11 @@ where
|
||||
// TODO(dhruvmanila): If the current scope is a comprehension, then the
|
||||
// named expression is implicitly nonlocal. This is yet to be
|
||||
// implemented.
|
||||
self.add_definition(symbol, named);
|
||||
self.add_definition(self.db, symbol, named);
|
||||
}
|
||||
Some(CurrentAssignment::Comprehension { node, first }) => {
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
ComprehensionDefinitionNodeRef {
|
||||
iterable: &node.iter,
|
||||
@@ -1346,6 +1311,7 @@ where
|
||||
}
|
||||
Some(CurrentAssignment::WithItem { item, is_async }) => {
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
WithItemDefinitionNodeRef {
|
||||
node: item,
|
||||
@@ -1358,9 +1324,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(
|
||||
CurrentAssignment::Assign { first, .. } | CurrentAssignment::For { first, .. },
|
||||
) = self.current_assignment_mut()
|
||||
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
|
||||
{
|
||||
*first = false;
|
||||
}
|
||||
@@ -1409,13 +1373,14 @@ where
|
||||
let pre_if = self.flow_snapshot();
|
||||
let constraint = self.record_expression_constraint(test);
|
||||
self.visit_expr(body);
|
||||
let visibility_constraint = self.record_visibility_constraint(constraint);
|
||||
let visibility_constraint =
|
||||
self.create_and_record_visibility_constraint(self.db, constraint);
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if.clone());
|
||||
|
||||
self.record_negated_constraint(constraint);
|
||||
self.visit_expr(orelse);
|
||||
self.record_negated_visibility_constraint(visibility_constraint);
|
||||
self.record_negated_visibility_constraint(self.db, visibility_constraint);
|
||||
self.flow_merge(post_body);
|
||||
self.simplify_visibility_constraints(pre_if);
|
||||
}
|
||||
@@ -1483,19 +1448,19 @@ where
|
||||
self.visit_expr(value);
|
||||
|
||||
for vid in &visibility_constraints {
|
||||
self.record_visibility_constraint_id(*vid);
|
||||
self.record_visibility_constraint(self.db, *vid);
|
||||
}
|
||||
|
||||
// For the last value, we don't need to model control flow. There is short-circuiting
|
||||
// anymore.
|
||||
if index < values.len() - 1 {
|
||||
let constraint = self.build_constraint(value);
|
||||
let (constraint, constraint_id) = match op {
|
||||
ast::BoolOp::And => (constraint, self.add_constraint(constraint)),
|
||||
ast::BoolOp::Or => self.add_negated_constraint(constraint),
|
||||
let constraint = match op {
|
||||
BoolOp::And => self.add_constraint(constraint),
|
||||
BoolOp::Or => self.add_negated_constraint(constraint),
|
||||
};
|
||||
let visibility_constraint = self
|
||||
.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint));
|
||||
let visibility_constraint =
|
||||
VisibilityConstraint::visible_if(self.db, constraint);
|
||||
|
||||
let after_expr = self.flow_snapshot();
|
||||
|
||||
@@ -1504,16 +1469,16 @@ where
|
||||
// we record all previously existing visibility constraints, and negate the
|
||||
// one for the current expression.
|
||||
for vid in &visibility_constraints {
|
||||
self.record_visibility_constraint_id(*vid);
|
||||
self.record_visibility_constraint(*vid);
|
||||
}
|
||||
self.record_negated_visibility_constraint(visibility_constraint);
|
||||
self.record_negated_visibility_constraint(self.db, visibility_constraint);
|
||||
snapshots.push(self.flow_snapshot());
|
||||
|
||||
// Then we model the non-short-circuiting behavior. Here, we need to delay
|
||||
// the application of the visibility constraint until after the expression
|
||||
// has been evaluated, so we only push it onto the stack here.
|
||||
self.flow_restore(after_expr);
|
||||
self.record_constraint_id(constraint_id);
|
||||
self.record_constraint(constraint);
|
||||
visibility_constraints.push(visibility_constraint);
|
||||
}
|
||||
}
|
||||
@@ -1559,6 +1524,7 @@ where
|
||||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
@@ -1580,6 +1546,7 @@ where
|
||||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
self.db,
|
||||
symbol,
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
@@ -1602,11 +1569,7 @@ enum CurrentAssignment<'a> {
|
||||
},
|
||||
AnnAssign(&'a ast::StmtAnnAssign),
|
||||
AugAssign(&'a ast::StmtAugAssign),
|
||||
For {
|
||||
node: &'a ast::StmtFor,
|
||||
first: bool,
|
||||
unpack: Option<Unpack<'a>>,
|
||||
},
|
||||
For(&'a ast::StmtFor),
|
||||
Named(&'a ast::ExprNamed),
|
||||
Comprehension {
|
||||
node: &'a ast::Comprehension,
|
||||
@@ -1630,6 +1593,12 @@ impl<'a> From<&'a ast::StmtAugAssign> for CurrentAssignment<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtFor> for CurrentAssignment<'a> {
|
||||
fn from(value: &'a ast::StmtFor) -> Self {
|
||||
Self::For(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprNamed> for CurrentAssignment<'a> {
|
||||
fn from(value: &'a ast::ExprNamed) -> Self {
|
||||
Self::Named(value)
|
||||
|
||||
@@ -4,9 +4,9 @@ use super::SemanticIndexBuilder;
|
||||
|
||||
/// An abstraction over the fact that each scope should have its own [`TryNodeContextStack`]
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct TryNodeContextStackManager(Vec<TryNodeContextStack>);
|
||||
pub(super) struct TryNodeContextStackManager<'db>(Vec<TryNodeContextStack<'db>>);
|
||||
|
||||
impl TryNodeContextStackManager {
|
||||
impl<'db> TryNodeContextStackManager<'db> {
|
||||
/// Push a new [`TryNodeContextStack`] onto the stack of stacks.
|
||||
///
|
||||
/// Each [`TryNodeContextStack`] is only valid for a single scope
|
||||
@@ -46,7 +46,7 @@ impl TryNodeContextStackManager {
|
||||
}
|
||||
|
||||
/// Retrieve the [`TryNodeContextStack`] that is relevant for the current scope.
|
||||
fn current_try_context_stack(&mut self) -> &mut TryNodeContextStack {
|
||||
fn current_try_context_stack(&'db mut self) -> &'db mut TryNodeContextStack<'db> {
|
||||
self.0
|
||||
.last_mut()
|
||||
.expect("There should always be at least one `TryBlockContexts` on the stack")
|
||||
@@ -55,9 +55,9 @@ impl TryNodeContextStackManager {
|
||||
|
||||
/// The contexts of nested `try`/`except` blocks for a single scope
|
||||
#[derive(Debug, Default)]
|
||||
struct TryNodeContextStack(Vec<TryNodeContext>);
|
||||
struct TryNodeContextStack<'db>(Vec<TryNodeContext<'db>>);
|
||||
|
||||
impl TryNodeContextStack {
|
||||
impl<'db> TryNodeContextStack<'db> {
|
||||
/// Push a new [`TryNodeContext`] for recording intermediate states
|
||||
/// while visiting a [`ruff_python_ast::StmtTry`] node that has a `finally` branch.
|
||||
fn push_context(&mut self) {
|
||||
@@ -90,11 +90,11 @@ impl TryNodeContextStack {
|
||||
/// It will likely be necessary to add more fields to this struct in the future
|
||||
/// when we add more advanced handling of `finally` branches.
|
||||
#[derive(Debug, Default)]
|
||||
struct TryNodeContext {
|
||||
try_suite_snapshots: Vec<FlowSnapshot>,
|
||||
struct TryNodeContext<'db> {
|
||||
try_suite_snapshots: Vec<FlowSnapshot<'db>>,
|
||||
}
|
||||
|
||||
impl TryNodeContext {
|
||||
impl<'db> TryNodeContext<'db> {
|
||||
/// Take a record of what the internal state looked like after a definition
|
||||
fn record_definition(&mut self, snapshot: FlowSnapshot) {
|
||||
self.try_suite_snapshots.push(snapshot);
|
||||
|
||||
@@ -5,13 +5,13 @@ use crate::db::Db;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct Constraint<'db> {
|
||||
pub(crate) node: ConstraintNode<'db>,
|
||||
pub(crate) is_positive: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum ConstraintNode<'db> {
|
||||
Expression(Expression<'db>),
|
||||
Pattern(PatternConstraint<'db>),
|
||||
|
||||
@@ -225,10 +225,8 @@ pub(crate) struct WithItemDefinitionNodeRef<'a> {
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct ForStmtDefinitionNodeRef<'a> {
|
||||
pub(crate) unpack: Option<Unpack<'a>>,
|
||||
pub(crate) iterable: &'a ast::Expr,
|
||||
pub(crate) name: &'a ast::ExprName,
|
||||
pub(crate) first: bool,
|
||||
pub(crate) target: &'a ast::ExprName,
|
||||
pub(crate) is_async: bool,
|
||||
}
|
||||
|
||||
@@ -300,16 +298,12 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
DefinitionKind::AugmentedAssignment(AstNodeRef::new(parsed, augmented_assignment))
|
||||
}
|
||||
DefinitionNodeRef::For(ForStmtDefinitionNodeRef {
|
||||
unpack,
|
||||
iterable,
|
||||
name,
|
||||
first,
|
||||
target,
|
||||
is_async,
|
||||
}) => DefinitionKind::For(ForStmtDefinitionKind {
|
||||
target: TargetKind::from(unpack),
|
||||
iterable: AstNodeRef::new(parsed.clone(), iterable),
|
||||
name: AstNodeRef::new(parsed, name),
|
||||
first,
|
||||
target: AstNodeRef::new(parsed, target),
|
||||
is_async,
|
||||
}),
|
||||
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef {
|
||||
@@ -388,12 +382,10 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
Self::AnnotatedAssignment(node) => node.into(),
|
||||
Self::AugmentedAssignment(node) => node.into(),
|
||||
Self::For(ForStmtDefinitionNodeRef {
|
||||
unpack: _,
|
||||
iterable: _,
|
||||
name,
|
||||
first: _,
|
||||
target,
|
||||
is_async: _,
|
||||
}) => name.into(),
|
||||
}) => target.into(),
|
||||
Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => target.into(),
|
||||
Self::VariadicPositionalParameter(node) => node.into(),
|
||||
Self::VariadicKeywordParameter(node) => node.into(),
|
||||
@@ -460,7 +452,7 @@ pub enum DefinitionKind<'db> {
|
||||
Assignment(AssignmentDefinitionKind<'db>),
|
||||
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
|
||||
AugmentedAssignment(AstNodeRef<ast::StmtAugAssign>),
|
||||
For(ForStmtDefinitionKind<'db>),
|
||||
For(ForStmtDefinitionKind),
|
||||
Comprehension(ComprehensionDefinitionKind),
|
||||
VariadicPositionalParameter(AstNodeRef<ast::Parameter>),
|
||||
VariadicKeywordParameter(AstNodeRef<ast::Parameter>),
|
||||
@@ -473,14 +465,8 @@ pub enum DefinitionKind<'db> {
|
||||
TypeVarTuple(AstNodeRef<ast::TypeParamTypeVarTuple>),
|
||||
}
|
||||
|
||||
impl DefinitionKind<'_> {
|
||||
/// Returns the [`TextRange`] of the definition target.
|
||||
///
|
||||
/// A definition target would mainly be the node representing the symbol being defined i.e.,
|
||||
/// [`ast::ExprName`] or [`ast::Identifier`] but could also be other nodes.
|
||||
///
|
||||
/// This is mainly used for logging and debugging purposes.
|
||||
pub(crate) fn target_range(&self) -> TextRange {
|
||||
impl Ranged for DefinitionKind<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
DefinitionKind::Import(alias) => alias.range(),
|
||||
DefinitionKind::ImportFrom(import) => import.alias().range(),
|
||||
@@ -491,7 +477,7 @@ impl DefinitionKind<'_> {
|
||||
DefinitionKind::Assignment(assignment) => assignment.name().range(),
|
||||
DefinitionKind::AnnotatedAssignment(assign) => assign.target.range(),
|
||||
DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.target.range(),
|
||||
DefinitionKind::For(for_stmt) => for_stmt.name().range(),
|
||||
DefinitionKind::For(for_stmt) => for_stmt.target().range(),
|
||||
DefinitionKind::Comprehension(comp) => comp.target().range(),
|
||||
DefinitionKind::VariadicPositionalParameter(parameter) => parameter.name.range(),
|
||||
DefinitionKind::VariadicKeywordParameter(parameter) => parameter.name.range(),
|
||||
@@ -504,7 +490,9 @@ impl DefinitionKind<'_> {
|
||||
DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.name.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefinitionKind<'_> {
|
||||
pub(crate) fn category(&self) -> DefinitionCategory {
|
||||
match self {
|
||||
// functions, classes, and imports always bind, and we consider them declarations
|
||||
@@ -677,32 +665,22 @@ impl WithItemDefinitionKind {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ForStmtDefinitionKind<'db> {
|
||||
target: TargetKind<'db>,
|
||||
pub struct ForStmtDefinitionKind {
|
||||
iterable: AstNodeRef<ast::Expr>,
|
||||
name: AstNodeRef<ast::ExprName>,
|
||||
first: bool,
|
||||
target: AstNodeRef<ast::ExprName>,
|
||||
is_async: bool,
|
||||
}
|
||||
|
||||
impl<'db> ForStmtDefinitionKind<'db> {
|
||||
impl ForStmtDefinitionKind {
|
||||
pub(crate) fn iterable(&self) -> &ast::Expr {
|
||||
self.iterable.node()
|
||||
}
|
||||
|
||||
pub(crate) fn target(&self) -> TargetKind<'db> {
|
||||
self.target
|
||||
pub(crate) fn target(&self) -> &ast::ExprName {
|
||||
self.target.node()
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &ast::ExprName {
|
||||
self.name.node()
|
||||
}
|
||||
|
||||
pub(crate) const fn is_first(&self) -> bool {
|
||||
self.first
|
||||
}
|
||||
|
||||
pub(crate) const fn is_async(&self) -> bool {
|
||||
pub(crate) fn is_async(&self) -> bool {
|
||||
self.is_async
|
||||
}
|
||||
}
|
||||
@@ -778,6 +756,12 @@ impl From<&ast::StmtAugAssign> for DefinitionNodeKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::StmtFor> for DefinitionNodeKey {
|
||||
fn from(value: &ast::StmtFor) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::Parameter> for DefinitionNodeKey {
|
||||
fn from(node: &ast::Parameter) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
|
||||
@@ -463,7 +463,10 @@ impl NodeWithScopeKind {
|
||||
}
|
||||
|
||||
pub fn expect_function(&self) -> &ast::StmtFunctionDef {
|
||||
self.as_function().expect("expected function")
|
||||
match self {
|
||||
Self::Function(function) => function.node(),
|
||||
_ => panic!("expected function"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
|
||||
@@ -472,13 +475,6 @@ impl NodeWithScopeKind {
|
||||
_ => panic!("expected type alias"),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> {
|
||||
match self {
|
||||
Self::Function(function) => Some(function.node()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
||||
@@ -169,11 +169,17 @@
|
||||
//! indexvecs in the [`UseDefMap`].
|
||||
//!
|
||||
//! There is another special kind of possible "definition" for a symbol: there might be a path from
|
||||
//! the scope entry to a given use in which the symbol is never bound. We model this with a special
|
||||
//! "unbound" definition (a `None` entry at the start of the `all_definitions` vector). If that
|
||||
//! sentinel definition is present in the live bindings at a given use, it means that there is a
|
||||
//! possible path through control flow in which that symbol is unbound. Similarly, if that sentinel
|
||||
//! is present in the live declarations, it means that the symbol is (possibly) undeclared.
|
||||
//! the scope entry to a given use in which the symbol is never bound.
|
||||
//!
|
||||
//! The simplest way to model "unbound" would be as a "binding" itself: the initial "binding" for
|
||||
//! each symbol in a scope. But actually modeling it this way would unnecessarily increase the
|
||||
//! number of [`Definition`]s that Salsa must track. Since "unbound" is special in that all symbols
|
||||
//! share it, and it doesn't have any additional per-symbol state, and constraints are irrelevant
|
||||
//! to it, we can represent it more efficiently: we use the `may_be_unbound` boolean on the
|
||||
//! [`SymbolBindings`] struct. If this flag is `true` for a use of a symbol, it means the symbol
|
||||
//! has a path to the use in which it is never bound. If this flag is `false`, it means we've
|
||||
//! eliminated the possibility of unbound: every control flow path to the use includes a binding
|
||||
//! for this symbol.
|
||||
//!
|
||||
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
|
||||
//! constraint as they are encountered by the
|
||||
@@ -184,13 +190,11 @@
|
||||
//! end of the scope, it records the state for each symbol as the public definitions of that
|
||||
//! symbol.
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
|
||||
//! the new symbol (before we process the first binding), we create a new undefined `SymbolState`
|
||||
//! which has a single live binding (the "unbound" definition) and a single live declaration (the
|
||||
//! "undeclared" definition). When we see `x = 1`, we record that as the sole live binding of `x`.
|
||||
//! The "unbound" binding is no longer visible. Then we see `x = 2`, and we replace `x = 1` as the
|
||||
//! sole live binding of `x`. When we get to `y = x`, we record that the live bindings for that use
|
||||
//! of `x` are just the `x = 2` definition.
|
||||
//! Let's walk through the above example. Initially we record for `x` that it has no bindings, and
|
||||
//! may be unbound. When we see `x = 1`, we record that as the sole live binding of `x`, and flip
|
||||
//! `may_be_unbound` to `false`. Then we see `x = 2`, and we replace `x = 1` as the sole live
|
||||
//! binding of `x`. When we get to `y = x`, we record that the live bindings for that use of `x`
|
||||
//! are just the `x = 2` definition.
|
||||
//!
|
||||
//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the current state for all symbols,
|
||||
@@ -203,8 +207,8 @@
|
||||
//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
|
||||
//! failed and we didn't execute the `if` body. So we first reset the builder to the pre-if state,
|
||||
//! using the snapshot we took previously (meaning we now have `x = 2` as the sole binding for `x`
|
||||
//! again), and record a *negative* `flag` constraint for all live bindings (`x = 2`). We then
|
||||
//! visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding of `x`.
|
||||
//! again), then visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding
|
||||
//! of `x`.
|
||||
//!
|
||||
//! Now we reach the end of the if/else, and want to visit the following code. The state here needs
|
||||
//! to reflect that we might have gone through the `if` branch, or we might have gone through the
|
||||
@@ -213,58 +217,20 @@
|
||||
//! snapshot (which has `x = 3` as the only live binding). The result of this merge is that we now
|
||||
//! have two live bindings of `x`: `x = 3` and `x = 4`.
|
||||
//!
|
||||
//! Another piece of information that the `UseDefMap` needs to provide are visibility constraints.
|
||||
//! These are similar to the narrowing constraints, but apply to bindings and declarations within a
|
||||
//! control flow path. Consider the following example:
|
||||
//! ```py
|
||||
//! x = 1
|
||||
//! if test:
|
||||
//! x = 2
|
||||
//! y = "y"
|
||||
//! ```
|
||||
//! In principle, there are two possible control flow paths here. However, if we can statically
|
||||
//! infer `test` to be always truthy or always falsy (that is, `__bool__` of `test` is of type
|
||||
//! `Literal[True]` or `Literal[False]`), we can rule out one of the possible paths. To support
|
||||
//! this feature, we record a visibility constraint of `test` to all live bindings and declarations
|
||||
//! *after* visiting the body of the `if` statement. And we record a negative visibility constraint
|
||||
//! `~test` to all live bindings/declarations in the (implicit) `else` branch. For the example
|
||||
//! above, we would record the following visibility constraints (adding the implicit "unbound"
|
||||
//! definitions for clarity):
|
||||
//! ```py
|
||||
//! x = <unbound> # not live, shadowed by `x = 1`
|
||||
//! y = <unbound> # visibility constraint: ~test
|
||||
//!
|
||||
//! x = 1 # visibility constraint: ~test
|
||||
//! if test:
|
||||
//! x = 2 # visibility constraint: test
|
||||
//! y = "y" # visibility constraint: test
|
||||
//! ```
|
||||
//! When we encounter a use of `x` after this `if` statement, we would record two live bindings: `x
|
||||
//! = 1` with a constraint of `~test`, and `x = 2` with a constraint of `test`. In type inference,
|
||||
//! when we iterate over all live bindings, we can evaluate these constraints to determine if a
|
||||
//! particular binding is actually visible. For example, if `test` is always truthy, we only see
|
||||
//! the `x = 2` binding. If `test` is always falsy, we only see the `x = 1` binding. And if the
|
||||
//! `__bool__` method of `test` returns type `bool`, we can see both bindings.
|
||||
//!
|
||||
//! Note that we also record visibility constraints for the start of the scope. This is important
|
||||
//! to determine if a symbol is definitely bound, possibly unbound, or definitely unbound. In the
|
||||
//! example above, The `y = <unbound>` binding is constrained by `~test`, so `y` would only be
|
||||
//! definitely-bound if `test` is always truthy.
|
||||
//!
|
||||
//! The [`UseDefMapBuilder`] itself just exposes methods for taking a snapshot, resetting to a
|
||||
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
|
||||
//! visits a `StmtIf` node.
|
||||
pub(crate) use self::symbol_state::ScopedConstraintId;
|
||||
use self::symbol_state::{
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
};
|
||||
pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstraintId};
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::semantic_index::use_def::symbol_state::DeclarationIdWithConstraint;
|
||||
use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints};
|
||||
use crate::visibility_constraints::VisibilityConstraint;
|
||||
use crate::Db;
|
||||
use ruff_index::IndexVec;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -273,23 +239,19 @@ use super::constraint::Constraint;
|
||||
mod bitset;
|
||||
mod symbol_state;
|
||||
|
||||
type AllConstraints<'db> = IndexVec<ScopedConstraintId, Constraint<'db>>;
|
||||
pub(crate) type AllConstraints<'db> = IndexVec<ScopedConstraintId, Constraint<'db>>;
|
||||
|
||||
/// Applicable definitions and constraints for every use of a name.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct UseDefMap<'db> {
|
||||
/// Array of [`Definition`] in this scope. Only the first entry should be `None`;
|
||||
/// this represents the implicit "unbound"/"undeclared" definition of every symbol.
|
||||
/// Array of [`Definition`] in this scope.
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
|
||||
/// Array of [`Constraint`] in this scope.
|
||||
all_constraints: AllConstraints<'db>,
|
||||
|
||||
/// Array of [`VisibilityConstraint`]s in this scope.
|
||||
visibility_constraints: VisibilityConstraints<'db>,
|
||||
|
||||
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings<'db>>,
|
||||
|
||||
/// [`SymbolBindings`] or [`SymbolDeclarations`] reaching a given [`Definition`].
|
||||
///
|
||||
@@ -303,10 +265,10 @@ pub(crate) struct UseDefMap<'db> {
|
||||
/// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then
|
||||
/// we don't actually need anything here, all we'll need to validate is that our own RHS is a
|
||||
/// valid assignment to our own annotation.
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions>,
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions<'db>>,
|
||||
|
||||
/// [`SymbolState`] visible at end of scope for each symbol.
|
||||
public_symbols: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
public_symbols: IndexVec<ScopedSymbolId, SymbolState<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
@@ -318,9 +280,9 @@ impl<'db> UseDefMap<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn public_bindings(
|
||||
&self,
|
||||
&'db self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
) -> BindingWithConstraintsIterator<'map, 'db> {
|
||||
self.bindings_iterator(self.public_symbols[symbol].bindings())
|
||||
}
|
||||
|
||||
@@ -359,12 +321,11 @@ impl<'db> UseDefMap<'db> {
|
||||
|
||||
fn bindings_iterator<'map>(
|
||||
&'map self,
|
||||
bindings: &'map SymbolBindings,
|
||||
bindings: &'map SymbolBindings<'db>,
|
||||
) -> BindingWithConstraintsIterator<'map, 'db> {
|
||||
BindingWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
visibility_constraints: &self.visibility_constraints,
|
||||
inner: bindings.iter(),
|
||||
}
|
||||
}
|
||||
@@ -375,7 +336,6 @@ impl<'db> UseDefMap<'db> {
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
visibility_constraints: &self.visibility_constraints,
|
||||
inner: declarations.iter(),
|
||||
}
|
||||
}
|
||||
@@ -383,17 +343,16 @@ impl<'db> UseDefMap<'db> {
|
||||
|
||||
/// Either live bindings or live declarations for a symbol.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum SymbolDefinitions {
|
||||
Bindings(SymbolBindings),
|
||||
Declarations(SymbolDeclarations),
|
||||
enum SymbolDefinitions<'db> {
|
||||
Bindings(SymbolBindings<'db>),
|
||||
Declarations(SymbolDeclarations<'db>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
all_constraints: &'map AllConstraints<'db>,
|
||||
pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>,
|
||||
inner: BindingIdWithConstraintsIterator<'map>,
|
||||
inner: BindingIdWithConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
@@ -420,7 +379,7 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Option<Definition<'db>>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
pub(crate) visibility_constraint: VisibilityConstraint<'db>,
|
||||
}
|
||||
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
@@ -442,28 +401,22 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
|
||||
pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
}
|
||||
|
||||
pub(crate) struct DeclarationWithConstraint<'db> {
|
||||
pub(crate) declaration: Option<Definition<'db>>,
|
||||
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
pub(crate) visibility_constraint: VisibilityConstraint<'db>,
|
||||
}
|
||||
|
||||
impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = DeclarationWithConstraint<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(
|
||||
|DeclarationIdWithConstraint {
|
||||
definition,
|
||||
visibility_constraint,
|
||||
}| {
|
||||
DeclarationWithConstraint {
|
||||
declaration: self.all_definitions[definition],
|
||||
visibility_constraint,
|
||||
}
|
||||
|(def_id, visibility_constraint)| DeclarationWithConstraint {
|
||||
declaration: self.all_definitions[def_id],
|
||||
visibility_constraint,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -473,9 +426,9 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
|
||||
|
||||
/// A snapshot of the definitions and constraints state at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
scope_start_visibility: ScopedVisibilityConstraintId,
|
||||
pub(super) struct FlowSnapshot<'db> {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState<'db>>,
|
||||
scope_start_visibility: VisibilityConstraint<'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -486,40 +439,34 @@ pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: AllConstraints<'db>,
|
||||
|
||||
/// Append-only array of [`VisibilityConstraint`].
|
||||
visibility_constraints: VisibilityConstraints<'db>,
|
||||
|
||||
/// A constraint which describes the visibility of the unbound/undeclared state, i.e.
|
||||
/// whether or not the start of the scope is visible. This is important for cases like
|
||||
/// `if True: x = 1; use(x)` where we need to hide the implicit "x = unbound" binding
|
||||
/// in the "else" branch.
|
||||
scope_start_visibility: ScopedVisibilityConstraintId,
|
||||
scope_start_visibility: VisibilityConstraint<'db>,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings<'db>>,
|
||||
|
||||
/// Live bindings or declarations for each so-far-recorded definition.
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions>,
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions<'db>>,
|
||||
|
||||
/// Currently live bindings and declarations for each symbol.
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState<'db>>,
|
||||
}
|
||||
|
||||
impl Default for UseDefMapBuilder<'_> {
|
||||
fn default() -> Self {
|
||||
impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(crate) fn new(db: &'db dyn Db) -> Self {
|
||||
Self {
|
||||
all_definitions: IndexVec::from_iter([None]),
|
||||
all_constraints: IndexVec::new(),
|
||||
visibility_constraints: VisibilityConstraints::default(),
|
||||
scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE,
|
||||
scope_start_visibility: VisibilityConstraint::always_true(db),
|
||||
bindings_by_use: IndexVec::new(),
|
||||
definitions_by_definition: FxHashMap::default(),
|
||||
symbol_states: IndexVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self
|
||||
.symbol_states
|
||||
@@ -527,59 +474,43 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
}
|
||||
|
||||
pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) {
|
||||
pub(super) fn record_binding(
|
||||
&'db mut self,
|
||||
db: &'db dyn Db,
|
||||
symbol: ScopedSymbolId,
|
||||
binding: Definition<'db>,
|
||||
) {
|
||||
let def_id = self.all_definitions.push(Some(binding));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.definitions_by_definition.insert(
|
||||
binding,
|
||||
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
|
||||
);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_binding(db, def_id);
|
||||
}
|
||||
|
||||
pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
self.all_constraints.push(constraint)
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) {
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
let constraint_id = self.add_constraint(constraint);
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint);
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
|
||||
let new_constraint_id = self.add_constraint(constraint);
|
||||
self.record_constraint_id(new_constraint_id);
|
||||
new_constraint_id
|
||||
}
|
||||
|
||||
pub(super) fn add_visibility_constraint(
|
||||
&mut self,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
self.visibility_constraints.add(constraint)
|
||||
}
|
||||
|
||||
pub(super) fn record_visibility_constraint_id(
|
||||
&mut self,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
) {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_visibility_constraint(&mut self.visibility_constraints, constraint);
|
||||
}
|
||||
|
||||
self.scope_start_visibility = self
|
||||
.visibility_constraints
|
||||
.add_and_constraint(self.scope_start_visibility, constraint);
|
||||
}
|
||||
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
let new_constraint_id = self.add_visibility_constraint(constraint);
|
||||
self.record_visibility_constraint_id(new_constraint_id);
|
||||
new_constraint_id
|
||||
) {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_visibility_constraint(db, constraint);
|
||||
}
|
||||
|
||||
self.scope_start_visibility =
|
||||
VisibilityConstraint::kleene_and(db, self.scope_start_visibility, constraint);
|
||||
}
|
||||
|
||||
/// This method resets the visibility constraints for all symbols to a previous state
|
||||
@@ -600,7 +531,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
/// We build a complex visibility constraint for the `y = 0` binding. We build the same
|
||||
/// constraint for the `x = 0` binding as well, but at the `RESET` point, we can get rid
|
||||
/// of it, as the `if`-`elif`-`elif` chain doesn't include any new bindings of `x`.
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) {
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot<'db>) {
|
||||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
|
||||
|
||||
self.scope_start_visibility = snapshot.scope_start_visibility;
|
||||
@@ -617,7 +548,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
&'db mut self,
|
||||
db: &'db dyn Db,
|
||||
symbol: ScopedSymbolId,
|
||||
declaration: Definition<'db>,
|
||||
) {
|
||||
@@ -627,22 +559,23 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
declaration,
|
||||
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
|
||||
);
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_declaration(db, def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
&mut self,
|
||||
&'db mut self,
|
||||
db: &'db dyn Db,
|
||||
symbol: ScopedSymbolId,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(Some(definition));
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
symbol_state.record_declaration(db, def_id);
|
||||
symbol_state.record_binding(db, def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
pub(super) fn record_use(&'db mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
// We have a use of a symbol; clone the current bindings for that symbol, and record them
|
||||
// as the live bindings for this use.
|
||||
let new_use = self
|
||||
@@ -652,7 +585,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
/// Take a snapshot of the current visible-symbols state.
|
||||
pub(super) fn snapshot(&self) -> FlowSnapshot {
|
||||
pub(super) fn snapshot(&'db self) -> FlowSnapshot<'db> {
|
||||
FlowSnapshot {
|
||||
symbol_states: self.symbol_states.clone(),
|
||||
scope_start_visibility: self.scope_start_visibility,
|
||||
@@ -660,7 +593,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot<'db>) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
@@ -683,7 +616,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
pub(super) fn merge(&mut self, snapshot: FlowSnapshot) {
|
||||
pub(super) fn merge(&mut self, db: &'db dyn Db, snapshot: FlowSnapshot<'db>) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
@@ -692,19 +625,18 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
|
||||
for current in &mut self.symbol_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(snapshot, &mut self.visibility_constraints);
|
||||
current.merge(db, snapshot);
|
||||
} else {
|
||||
current.merge(
|
||||
SymbolState::undefined(snapshot.scope_start_visibility),
|
||||
&mut self.visibility_constraints,
|
||||
);
|
||||
current.merge(db, SymbolState::undefined(snapshot.scope_start_visibility));
|
||||
// Symbol not present in snapshot, so it's unbound/undeclared from that path.
|
||||
}
|
||||
}
|
||||
|
||||
self.scope_start_visibility = self
|
||||
.visibility_constraints
|
||||
.add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility);
|
||||
self.scope_start_visibility = VisibilityConstraint::kleene_or(
|
||||
db,
|
||||
self.scope_start_visibility,
|
||||
snapshot.scope_start_visibility,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> UseDefMap<'db> {
|
||||
@@ -717,7 +649,6 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
UseDefMap {
|
||||
all_definitions: self.all_definitions,
|
||||
all_constraints: self.all_constraints,
|
||||
visibility_constraints: self.visibility_constraints,
|
||||
bindings_by_use: self.bindings_by_use,
|
||||
public_symbols: self.symbol_states,
|
||||
definitions_by_definition: self.definitions_by_definition,
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use crate::semantic_index::use_def::VisibilityConstraints;
|
||||
use crate::{visibility_constraints::VisibilityConstraint, Db};
|
||||
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
@@ -54,11 +54,6 @@ use smallvec::SmallVec;
|
||||
pub(super) struct ScopedDefinitionId;
|
||||
|
||||
impl ScopedDefinitionId {
|
||||
/// A special ID that is used to describe an implicit start-of-scope state. When
|
||||
/// we see that this definition is live, we know that the symbol is (possibly)
|
||||
/// unbound or undeclared at a given usage site.
|
||||
/// When creating a use-def-map builder, we always add an empty `None` definition
|
||||
/// at index 0, so this ID is always present.
|
||||
pub(super) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0);
|
||||
}
|
||||
|
||||
@@ -98,46 +93,29 @@ type ConstraintsPerBinding = SmallVec<InlineConstraintArray>;
|
||||
type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>;
|
||||
type ConstraintsIntoIterator = smallvec::IntoIter<InlineConstraintArray>;
|
||||
|
||||
/// A newtype-index for a visibility constraint in a particular scope.
|
||||
#[newtype_index]
|
||||
pub(crate) struct ScopedVisibilityConstraintId;
|
||||
|
||||
impl ScopedVisibilityConstraintId {
|
||||
/// A special ID that is used for an "always true" / "always visible" constraint.
|
||||
/// When we create a new [`VisibilityConstraints`] object, this constraint is always
|
||||
/// present at index 0.
|
||||
pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId =
|
||||
ScopedVisibilityConstraintId::from_u32(0);
|
||||
}
|
||||
/// Similar to what we have above, but for visibility constraints.
|
||||
|
||||
const INLINE_VISIBILITY_CONSTRAINTS: usize = 4;
|
||||
type InlineVisibilityConstraintsArray =
|
||||
[ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS];
|
||||
type InlineVisibilityConstraintsArray<'db> =
|
||||
[VisibilityConstraint<'db>; INLINE_VISIBILITY_CONSTRAINTS];
|
||||
type VisibilityConstraintPerDeclaration<'db> = SmallVec<InlineVisibilityConstraintsArray<'db>>;
|
||||
type VisibilityConstraintPerBinding<'db> = SmallVec<InlineVisibilityConstraintsArray<'db>>;
|
||||
type VisibilityConstraintsIterator<'db> = std::slice::Iter<'db, VisibilityConstraint<'db>>;
|
||||
type VisibilityConstraintsIntoIterator<'db> =
|
||||
smallvec::IntoIter<InlineVisibilityConstraintsArray<'db>>;
|
||||
|
||||
/// One [`ScopedVisibilityConstraintId`] per live declaration.
|
||||
type VisibilityConstraintPerDeclaration = SmallVec<InlineVisibilityConstraintsArray>;
|
||||
|
||||
/// One [`ScopedVisibilityConstraintId`] per live binding.
|
||||
type VisibilityConstraintPerBinding = SmallVec<InlineVisibilityConstraintsArray>;
|
||||
|
||||
/// Iterator over the visibility constraints for all live bindings/declarations.
|
||||
type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>;
|
||||
|
||||
type VisibilityConstraintsIntoIterator = smallvec::IntoIter<InlineVisibilityConstraintsArray>;
|
||||
|
||||
/// Live declarations for a single symbol at some point in control flow, with their
|
||||
/// corresponding visibility constraints.
|
||||
/// Live declarations for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolDeclarations {
|
||||
pub(super) struct SymbolDeclarations<'db> {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
pub(crate) live_declarations: Declarations,
|
||||
|
||||
/// For each live declaration, which visibility constraint applies to it?
|
||||
pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration,
|
||||
/// For each live declaration, which visibility constraints apply to it?
|
||||
pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration<'db>,
|
||||
}
|
||||
|
||||
impl SymbolDeclarations {
|
||||
fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
impl<'db> SymbolDeclarations<'db> {
|
||||
fn undeclared(scope_start_visibility: VisibilityConstraint<'db>) -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::with(0),
|
||||
visibility_constraints: VisibilityConstraintPerDeclaration::from_iter([
|
||||
@@ -147,22 +125,22 @@ impl SymbolDeclarations {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
fn record_declaration(&mut self, db: &'db dyn Db, declaration_id: ScopedDefinitionId) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
|
||||
self.visibility_constraints = VisibilityConstraintPerDeclaration::with_capacity(1);
|
||||
self.visibility_constraints
|
||||
.push(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
.push(VisibilityConstraint::always_true(db));
|
||||
}
|
||||
|
||||
/// Add given visibility constraint to all live declarations.
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
visibility_constraints: &mut VisibilityConstraints,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) {
|
||||
for existing in &mut self.visibility_constraints {
|
||||
*existing = visibility_constraints.add_and_constraint(*existing, constraint);
|
||||
*existing = VisibilityConstraint::kleene_and(db, *existing, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,10 +153,9 @@ impl SymbolDeclarations {
|
||||
}
|
||||
}
|
||||
|
||||
/// Live bindings for a single symbol at some point in control flow. Each live binding comes
|
||||
/// with a set of narrowing constraints and a visibility constraint.
|
||||
/// Live bindings and narrowing constraints for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolBindings {
|
||||
pub(super) struct SymbolBindings<'db> {
|
||||
/// [`BitSet`]: which bindings (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_bindings: Bindings,
|
||||
|
||||
@@ -188,14 +165,14 @@ pub(super) struct SymbolBindings {
|
||||
/// binding in `live_bindings`.
|
||||
constraints: ConstraintsPerBinding,
|
||||
|
||||
/// For each live binding, which visibility constraint applies to it?
|
||||
visibility_constraints: VisibilityConstraintPerBinding,
|
||||
/// For each live binding, which visibility constraints apply to it?
|
||||
visibility_constraints: VisibilityConstraintPerBinding<'db>,
|
||||
}
|
||||
|
||||
impl SymbolBindings {
|
||||
fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
impl<'db> SymbolBindings<'db> {
|
||||
fn unbound(scope_start_visibility: VisibilityConstraint<'db>) -> Self {
|
||||
Self {
|
||||
live_bindings: Bindings::with(ScopedDefinitionId::UNBOUND.as_u32()),
|
||||
live_bindings: Bindings::with(0),
|
||||
constraints: ConstraintsPerBinding::from_iter([Constraints::default()]),
|
||||
visibility_constraints: VisibilityConstraintPerBinding::from_iter([
|
||||
scope_start_visibility,
|
||||
@@ -204,7 +181,7 @@ impl SymbolBindings {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
pub(super) fn record_binding(&mut self, db: &'db dyn Db, binding_id: ScopedDefinitionId) {
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
// constraints.
|
||||
self.live_bindings = Bindings::with(binding_id.into());
|
||||
@@ -213,7 +190,7 @@ impl SymbolBindings {
|
||||
|
||||
self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1);
|
||||
self.visibility_constraints
|
||||
.push(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
.push(VisibilityConstraint::always_true(db));
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -226,11 +203,11 @@ impl SymbolBindings {
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
visibility_constraints: &mut VisibilityConstraints,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) {
|
||||
for existing in &mut self.visibility_constraints {
|
||||
*existing = visibility_constraints.add_and_constraint(*existing, constraint);
|
||||
*existing = VisibilityConstraint::kleene_and(db, *existing, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,14 +222,14 @@ impl SymbolBindings {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolState {
|
||||
declarations: SymbolDeclarations,
|
||||
bindings: SymbolBindings,
|
||||
pub(super) struct SymbolState<'db> {
|
||||
declarations: SymbolDeclarations<'db>,
|
||||
bindings: SymbolBindings<'db>,
|
||||
}
|
||||
|
||||
impl SymbolState {
|
||||
impl<'db> SymbolState<'db> {
|
||||
/// Return a new [`SymbolState`] representing an unbound, undeclared symbol.
|
||||
pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
|
||||
pub(super) fn undefined(scope_start_visibility: VisibilityConstraint<'db>) -> Self {
|
||||
Self {
|
||||
declarations: SymbolDeclarations::undeclared(scope_start_visibility),
|
||||
bindings: SymbolBindings::unbound(scope_start_visibility),
|
||||
@@ -260,9 +237,9 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
pub(super) fn record_binding(&mut self, db: &'db dyn Db, binding_id: ScopedDefinitionId) {
|
||||
debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND);
|
||||
self.bindings.record_binding(binding_id);
|
||||
self.bindings.record_binding(db, binding_id);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -273,16 +250,15 @@ impl SymbolState {
|
||||
/// Add given visibility constraint to all live bindings.
|
||||
pub(super) fn record_visibility_constraint(
|
||||
&mut self,
|
||||
visibility_constraints: &mut VisibilityConstraints,
|
||||
constraint: ScopedVisibilityConstraintId,
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) {
|
||||
self.bindings
|
||||
.record_visibility_constraint(visibility_constraints, constraint);
|
||||
self.bindings.record_visibility_constraint(db, constraint);
|
||||
self.declarations
|
||||
.record_visibility_constraint(visibility_constraints, constraint);
|
||||
.record_visibility_constraint(db, constraint);
|
||||
}
|
||||
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) {
|
||||
pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState<'db>) {
|
||||
if self.bindings.live_bindings == snapshot_state.bindings.live_bindings {
|
||||
self.bindings.visibility_constraints = snapshot_state.bindings.visibility_constraints;
|
||||
}
|
||||
@@ -293,16 +269,16 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
) {
|
||||
self.declarations.record_declaration(db, declaration_id);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
pub(super) fn merge(
|
||||
&mut self,
|
||||
b: SymbolState,
|
||||
visibility_constraints: &mut VisibilityConstraints,
|
||||
) {
|
||||
pub(super) fn merge(&mut self, db: &'db dyn Db, b: SymbolState<'db>) {
|
||||
let mut a = Self {
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
@@ -339,13 +315,10 @@ impl SymbolState {
|
||||
visibility_constraints_iter: &mut VisibilityConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.bindings.live_bindings.insert(def);
|
||||
// SAFETY: we only ever create SymbolState using [`SymbolState::undefined`], which adds
|
||||
// one "unbound" definition with corresponding narrowing and visibility constraints, or
|
||||
// using [`SymbolState::record_binding`] or [`SymbolState::record_declaration`], which
|
||||
// similarly add one definition with corresponding constraints. [`SymbolState::merge`]
|
||||
// always pushes one definition and one constraint bitset and one visibility constraint
|
||||
// together (just below), so the number of definitions and the number of constraints can
|
||||
// never get out of sync.
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
// `::merge` always pushes one definition and one constraint bitset together (just
|
||||
// below), so the number of definitions and the number of constraint bitsets can never
|
||||
// get out of sync.
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
@@ -391,26 +364,31 @@ impl SymbolState {
|
||||
&mut b_vis_constraints_iter,
|
||||
self,
|
||||
);
|
||||
|
||||
// SAFETY: see comment in `push` above.
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and
|
||||
// no constraint bitsets (`::unbound`) or one definition and one constraint
|
||||
// bitset (`::with`), and `::merge` always pushes one definition and one
|
||||
// constraint bitset together (just below), so the number of definitions
|
||||
// and the number of constraint bitsets can never get out of sync.
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
let current_constraints = self.bindings.constraints.last_mut().unwrap();
|
||||
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
current_constraints.intersect(&a_constraints);
|
||||
self.bindings
|
||||
.constraints
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
|
||||
// For visibility constraints, we merge them using a ternary OR operation:
|
||||
// TODO: documentation
|
||||
// SAFETY: See above
|
||||
let a_vis_constraint = a_vis_constraints_iter
|
||||
.next()
|
||||
.expect("visibility_constraints length mismatch");
|
||||
let current_vis_constraint =
|
||||
self.bindings.visibility_constraints.last_mut().unwrap();
|
||||
*current_vis_constraint = visibility_constraints
|
||||
.add_or_constraint(*current_vis_constraint, a_vis_constraint);
|
||||
let current = self.bindings.visibility_constraints.last_mut().unwrap();
|
||||
*current = VisibilityConstraint::kleene_or(db, *current, a_vis_constraint);
|
||||
|
||||
opt_a_def = a_defs_iter.next();
|
||||
opt_b_def = b_defs_iter.next();
|
||||
@@ -480,8 +458,7 @@ impl SymbolState {
|
||||
.next()
|
||||
.expect("declarations and visibility_constraints length mismatch");
|
||||
let current = self.declarations.visibility_constraints.last_mut().unwrap();
|
||||
*current =
|
||||
visibility_constraints.add_or_constraint(*current, a_vis_constraint);
|
||||
*current = VisibilityConstraint::kleene_or(db, *current, a_vis_constraint);
|
||||
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
@@ -500,34 +477,33 @@ impl SymbolState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn bindings(&self) -> &SymbolBindings {
|
||||
pub(super) fn bindings(&'db self) -> &'db SymbolBindings<'db> {
|
||||
&self.bindings
|
||||
}
|
||||
|
||||
pub(super) fn declarations(&self) -> &SymbolDeclarations {
|
||||
pub(super) fn declarations(&'db self) -> &'db SymbolDeclarations<'db> {
|
||||
&self.declarations
|
||||
}
|
||||
}
|
||||
|
||||
/// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable
|
||||
/// narrowing constraints ([`ScopedConstraintId`]) and a corresponding visibility
|
||||
/// visibility constraint ([`ScopedVisibilityConstraintId`]).
|
||||
/// [`ScopedConstraintId`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraints<'map> {
|
||||
pub(super) struct BindingIdWithConstraints<'map, 'db> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'map>,
|
||||
pub(super) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
pub(super) visibility_constraint: VisibilityConstraint<'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'map> {
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'map, 'db> {
|
||||
definitions: BindingsIterator<'map>,
|
||||
constraints: ConstraintsIterator<'map>,
|
||||
visibility_constraints: VisibilityConstraintsIterator<'map>,
|
||||
visibility_constraints: VisibilityConstraintsIterator<'db>,
|
||||
}
|
||||
|
||||
impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> {
|
||||
type Item = BindingIdWithConstraints<'map>;
|
||||
impl<'map, 'db> Iterator for BindingIdWithConstraintsIterator<'map, 'db> {
|
||||
type Item = BindingIdWithConstraints<'map, 'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (
|
||||
@@ -551,7 +527,7 @@ impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
@@ -568,31 +544,21 @@ impl Iterator for ConstraintIdIterator<'_> {
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
|
||||
/// A single declaration (as [`ScopedDefinitionId`]) with a corresponding visibility
|
||||
/// visibility constraint ([`ScopedVisibilityConstraintId`]).
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdWithConstraint {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) visibility_constraint: ScopedVisibilityConstraintId,
|
||||
}
|
||||
|
||||
pub(super) struct DeclarationIdIterator<'map> {
|
||||
pub(crate) declarations: DeclarationsIterator<'map>,
|
||||
pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>,
|
||||
}
|
||||
|
||||
impl Iterator for DeclarationIdIterator<'_> {
|
||||
type Item = DeclarationIdWithConstraint;
|
||||
impl<'db> Iterator for DeclarationIdIterator<'db> {
|
||||
type Item = (ScopedDefinitionId, VisibilityConstraint<'db>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.declarations.next(), self.visibility_constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(declaration), Some(&visibility_constraint)) => {
|
||||
Some(DeclarationIdWithConstraint {
|
||||
definition: ScopedDefinitionId::from_u32(declaration),
|
||||
visibility_constraint,
|
||||
})
|
||||
}
|
||||
(Some(declaration), Some(visibility_constraints_id)) => Some((
|
||||
ScopedDefinitionId::from_u32(declaration),
|
||||
*visibility_constraints_id,
|
||||
)),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("declarations and visibility_constraints length mismatch"),
|
||||
}
|
||||
@@ -601,174 +567,169 @@ impl Iterator for DeclarationIdIterator<'_> {
|
||||
|
||||
impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
#[track_caller]
|
||||
fn assert_bindings(symbol: &SymbolState, expected: &[&str]) {
|
||||
let actual = symbol
|
||||
.bindings()
|
||||
.iter()
|
||||
.map(|def_id_with_constraints| {
|
||||
let def_id = def_id_with_constraints.definition;
|
||||
let def = if def_id == ScopedDefinitionId::UNBOUND {
|
||||
"unbound".into()
|
||||
} else {
|
||||
def_id.as_u32().to_string()
|
||||
};
|
||||
let constraints = def_id_with_constraints
|
||||
.constraint_ids
|
||||
.map(ScopedConstraintId::as_u32)
|
||||
.map(|idx| idx.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{def}<{constraints}>")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
// #[track_caller]
|
||||
// fn assert_bindings(symbol: &SymbolState, expected: &[&str]) {
|
||||
// let actual = symbol
|
||||
// .bindings()
|
||||
// .iter()
|
||||
// .map(|def_id_with_constraints| {
|
||||
// let def_id = def_id_with_constraints.definition;
|
||||
// let def = if def_id == ScopedDefinitionId::UNBOUND {
|
||||
// "unbound".into()
|
||||
// } else {
|
||||
// def_id.as_u32().to_string()
|
||||
// };
|
||||
// let constraints = def_id_with_constraints
|
||||
// .constraint_ids
|
||||
// .map(ScopedConstraintId::as_u32)
|
||||
// .map(|idx| idx.to_string())
|
||||
// .collect::<Vec<_>>()
|
||||
// .join(", ");
|
||||
// format!("{def}<{constraints}>")
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// assert_eq!(actual, expected);
|
||||
// }
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) {
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(
|
||||
|DeclarationIdWithConstraint {
|
||||
definition,
|
||||
visibility_constraint: _,
|
||||
}| {
|
||||
if definition == ScopedDefinitionId::UNBOUND {
|
||||
"undeclared".into()
|
||||
} else {
|
||||
definition.as_u32().to_string()
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
// #[track_caller]
|
||||
// pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) {
|
||||
// let actual = symbol
|
||||
// .declarations()
|
||||
// .iter()
|
||||
// .map(|(def_id, _)| {
|
||||
// if def_id == ScopedDefinitionId::UNBOUND {
|
||||
// "undeclared".into()
|
||||
// } else {
|
||||
// def_id.as_u32().to_string()
|
||||
// }
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
// assert_eq!(actual, expected);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn unbound() {
|
||||
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// #[test]
|
||||
// fn unbound() {
|
||||
// let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
assert_bindings(&sym, &["unbound<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, &["unbound<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn with() {
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_bindings(&sym, &["1<>"]);
|
||||
}
|
||||
// assert_bindings(&sym, &["1<>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_constraint() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// #[test]
|
||||
// fn record_constraint() {
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
assert_bindings(&sym, &["1<0>"]);
|
||||
}
|
||||
// assert_bindings(&sym, &["1<0>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
let mut visibility_constraints = VisibilityConstraints::default();
|
||||
// #[test]
|
||||
// fn merge() {
|
||||
// let mut visibility_constraints = VisibilityConstraints::default();
|
||||
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// // merging the same definition with the same constraint keeps the constraint
|
||||
// let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
// let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
sym1a.merge(sym1b, &mut visibility_constraints);
|
||||
let mut sym1 = sym1a;
|
||||
assert_bindings(&sym1, &["1<0>"]);
|
||||
// sym1a.merge(sym1b, &mut visibility_constraints);
|
||||
// let mut sym1 = sym1a;
|
||||
// assert_bindings(&sym1, &["1<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym2a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
// // merging the same definition with differing constraints drops all constraints
|
||||
// let mut sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
// sym2a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
// let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym1b.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
// sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
sym2a.merge(sym1b, &mut visibility_constraints);
|
||||
let sym2 = sym2a;
|
||||
assert_bindings(&sym2, &["2<>"]);
|
||||
// sym2a.merge(sym1b, &mut visibility_constraints);
|
||||
// let sym2 = sym2a;
|
||||
// assert_bindings(&sym2, &["2<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym3a.record_binding(ScopedDefinitionId::from_u32(3));
|
||||
sym3a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
// // merging a constrained definition with unbound keeps both
|
||||
// let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym3a.record_binding(ScopedDefinitionId::from_u32(3));
|
||||
// sym3a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
sym3a.merge(sym2b, &mut visibility_constraints);
|
||||
let sym3 = sym3a;
|
||||
assert_bindings(&sym3, &["unbound<>", "3<3>"]);
|
||||
// sym3a.merge(sym2b, &mut visibility_constraints);
|
||||
// let sym3 = sym3a;
|
||||
// assert_bindings(&sym3, &["unbound<>", "3<3>"]);
|
||||
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
sym1.merge(sym3, &mut visibility_constraints);
|
||||
let sym = sym1;
|
||||
assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]);
|
||||
}
|
||||
// // merging different definitions keeps them each with their existing constraints
|
||||
// sym1.merge(sym3, &mut visibility_constraints);
|
||||
// let sym = sym1;
|
||||
// assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// #[test]
|
||||
// fn no_declaration() {
|
||||
// let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
assert_declarations(&sym, &["undeclared"]);
|
||||
}
|
||||
// assert_declarations(&sym, &["undeclared"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration() {
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_declarations(&sym, &["1"]);
|
||||
}
|
||||
// assert_declarations(&sym, &["1"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// #[test]
|
||||
// fn record_declaration_override() {
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
assert_declarations(&sym, &["2"]);
|
||||
}
|
||||
// assert_declarations(&sym, &["2"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge() {
|
||||
let mut visibility_constraints = VisibilityConstraints::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge() {
|
||||
// let mut visibility_constraints = VisibilityConstraints::default();
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
// let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
sym.merge(sym2, &mut visibility_constraints);
|
||||
// sym.merge(sym2, &mut visibility_constraints);
|
||||
|
||||
assert_declarations(&sym, &["1", "2"]);
|
||||
}
|
||||
// assert_declarations(&sym, &["1", "2"]);
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut visibility_constraints = VisibilityConstraints::default();
|
||||
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// #[test]
|
||||
// fn record_declaration_merge_partial_undeclared() {
|
||||
// let mut visibility_constraints = VisibilityConstraints::default();
|
||||
// let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
// let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
|
||||
|
||||
sym.merge(sym2, &mut visibility_constraints);
|
||||
// sym.merge(sym2, &mut visibility_constraints);
|
||||
|
||||
assert_declarations(&sym, &["undeclared", "1"]);
|
||||
}
|
||||
}
|
||||
// assert_declarations(&sym, &["undeclared", "1"]);
|
||||
// }
|
||||
// }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ impl Boundness {
|
||||
/// possibly_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound),
|
||||
/// non_existent: Symbol::Unbound,
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Symbol<'db> {
|
||||
Type(Type<'db>, Boundness),
|
||||
Unbound,
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::semantic_index::{
|
||||
DeclarationsIterator,
|
||||
};
|
||||
use crate::stdlib::{builtins_symbol, known_module_symbol, typing_extensions_symbol};
|
||||
use crate::suppression::check_suppressions;
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::types::call::{CallDunderResult, CallOutcome};
|
||||
use crate::types::class_base::ClassBase;
|
||||
@@ -46,7 +45,6 @@ mod infer;
|
||||
mod mro;
|
||||
mod narrow;
|
||||
mod signatures;
|
||||
mod slots;
|
||||
mod string_annotation;
|
||||
mod unpacker;
|
||||
|
||||
@@ -67,13 +65,10 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
diagnostics.extend(result.diagnostics());
|
||||
}
|
||||
|
||||
check_suppressions(db, file, &mut diagnostics);
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
#[salsa::tracked]
|
||||
fn symbol_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolId) -> Symbol<'db> {
|
||||
let _span = tracing::trace_span!("symbol_by_id", ?symbol).entered();
|
||||
|
||||
@@ -279,7 +274,6 @@ fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Symbol<'db> {
|
||||
let visibility_constraints = bindings_with_constraints.visibility_constraints;
|
||||
let mut bindings_with_constraints = bindings_with_constraints.peekable();
|
||||
|
||||
let unbound_visibility = if let Some(BindingWithConstraints {
|
||||
@@ -288,7 +282,7 @@ fn bindings_ty<'db>(
|
||||
visibility_constraint,
|
||||
}) = bindings_with_constraints.peek()
|
||||
{
|
||||
visibility_constraints.evaluate(db, *visibility_constraint)
|
||||
crate::visibility_constraints::evaluate(db, *visibility_constraint)
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
};
|
||||
@@ -300,7 +294,8 @@ fn bindings_ty<'db>(
|
||||
visibility_constraint,
|
||||
}| {
|
||||
let binding = binding?;
|
||||
let static_visibility = visibility_constraints.evaluate(db, visibility_constraint);
|
||||
let static_visibility =
|
||||
crate::visibility_constraints::evaluate(db, visibility_constraint);
|
||||
|
||||
if static_visibility.is_always_false() {
|
||||
return None;
|
||||
@@ -367,7 +362,6 @@ fn declarations_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let visibility_constraints = declarations.visibility_constraints;
|
||||
let mut declarations = declarations.peekable();
|
||||
|
||||
let undeclared_visibility = if let Some(DeclarationWithConstraint {
|
||||
@@ -375,7 +369,7 @@ fn declarations_ty<'db>(
|
||||
visibility_constraint,
|
||||
}) = declarations.peek()
|
||||
{
|
||||
visibility_constraints.evaluate(db, *visibility_constraint)
|
||||
crate::visibility_constraints::evaluate(db, *visibility_constraint)
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
};
|
||||
@@ -386,7 +380,8 @@ fn declarations_ty<'db>(
|
||||
visibility_constraint,
|
||||
}| {
|
||||
let declaration = declaration?;
|
||||
let static_visibility = visibility_constraints.evaluate(db, visibility_constraint);
|
||||
let static_visibility =
|
||||
crate::visibility_constraints::evaluate(db, visibility_constraint);
|
||||
|
||||
if static_visibility.is_always_false() {
|
||||
None
|
||||
@@ -670,13 +665,6 @@ impl<'db> Type<'db> {
|
||||
.expect("Expected a Type::KnownInstance variant")
|
||||
}
|
||||
|
||||
pub const fn into_tuple(self) -> Option<TupleType<'db>> {
|
||||
match self {
|
||||
Type::Tuple(tuple_type) => Some(tuple_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_boolean_literal(&self) -> bool {
|
||||
matches!(self, Type::BooleanLiteral(..))
|
||||
}
|
||||
@@ -1210,6 +1198,14 @@ impl<'db> Type<'db> {
|
||||
Type::SubclassOf(_),
|
||||
) => true,
|
||||
|
||||
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
||||
// TODO: Once we have support for final classes, we can determine disjointness in some cases
|
||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
||||
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
||||
// final classes inside `Type::SubclassOf` everywhere.
|
||||
false
|
||||
}
|
||||
|
||||
(Type::AlwaysTruthy, ty) | (ty, Type::AlwaysTruthy) => {
|
||||
// `Truthiness::Ambiguous` may include `AlwaysTrue` as a subset, so it's not guaranteed to be disjoint.
|
||||
// Thus, they are only disjoint if `ty.bool() == AlwaysFalse`.
|
||||
@@ -1220,14 +1216,6 @@ impl<'db> Type<'db> {
|
||||
matches!(ty.bool(db), Truthiness::AlwaysTrue)
|
||||
}
|
||||
|
||||
(Type::SubclassOf(_), _) | (_, Type::SubclassOf(_)) => {
|
||||
// TODO: Once we have support for final classes, we can determine disjointness in some cases
|
||||
// here. However, note that it might be better to turn `Type::SubclassOf('FinalClass')` into
|
||||
// `Type::ClassLiteral('FinalClass')` during construction, instead of adding special cases for
|
||||
// final classes inside `Type::SubclassOf` everywhere.
|
||||
false
|
||||
}
|
||||
|
||||
(Type::KnownInstance(left), right) => {
|
||||
left.instance_fallback(db).is_disjoint_from(db, right)
|
||||
}
|
||||
@@ -1682,13 +1670,15 @@ impl<'db> Type<'db> {
|
||||
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
||||
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
||||
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
class.metaclass(db).to_instance(db).bool(db)
|
||||
Type::ClassLiteral(_) => {
|
||||
// TODO: lookup `__bool__` and `__len__` methods on the class's metaclass
|
||||
// More info in https://docs.python.org/3/library/stdtypes.html#truth-value-testing
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
Type::SubclassOf(_) => {
|
||||
// TODO: see above
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
Type::SubclassOf(SubclassOfType { base }) => base
|
||||
.into_class()
|
||||
.map(|class| Type::class_literal(class).bool(db))
|
||||
.unwrap_or(Truthiness::Ambiguous),
|
||||
Type::AlwaysTruthy => Truthiness::AlwaysTrue,
|
||||
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
|
||||
instance_ty @ Type::Instance(InstanceType { class }) => {
|
||||
@@ -3086,16 +3076,13 @@ pub enum KnownFunction {
|
||||
Len,
|
||||
/// `typing(_extensions).final`
|
||||
Final,
|
||||
|
||||
/// [`typing(_extensions).no_type_check`](https://typing.readthedocs.io/en/latest/spec/directives.html#no-type-check)
|
||||
NoTypeCheck,
|
||||
}
|
||||
|
||||
impl KnownFunction {
|
||||
pub fn constraint_function(self) -> Option<KnownConstraintFunction> {
|
||||
match self {
|
||||
Self::ConstraintFunction(f) => Some(f),
|
||||
Self::RevealType | Self::Len | Self::Final | Self::NoTypeCheck => None,
|
||||
Self::RevealType | Self::Len | Self::Final => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3114,9 +3101,6 @@ impl KnownFunction {
|
||||
),
|
||||
"len" if definition.is_builtin_definition(db) => Some(KnownFunction::Len),
|
||||
"final" if definition.is_typing_definition(db) => Some(KnownFunction::Final),
|
||||
"no_type_check" if definition.is_typing_definition(db) => {
|
||||
Some(KnownFunction::NoTypeCheck)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,13 @@ use ruff_db::{
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use super::{binding_ty, KnownFunction, TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::{
|
||||
lint::{LintId, LintMetadata},
|
||||
suppression::suppressions,
|
||||
Db,
|
||||
};
|
||||
|
||||
use super::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
|
||||
/// Context for inferring the types of a single file.
|
||||
///
|
||||
/// One context exists for at least for every inferred region but it's
|
||||
@@ -32,21 +29,17 @@ use crate::{
|
||||
/// on the current [`TypeInference`](super::infer::TypeInference) result.
|
||||
pub(crate) struct InferContext<'db> {
|
||||
db: &'db dyn Db,
|
||||
scope: ScopeId<'db>,
|
||||
file: File,
|
||||
diagnostics: std::cell::RefCell<TypeCheckDiagnostics>,
|
||||
no_type_check: InNoTypeCheck,
|
||||
bomb: DebugDropBomb,
|
||||
}
|
||||
|
||||
impl<'db> InferContext<'db> {
|
||||
pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self {
|
||||
pub(crate) fn new(db: &'db dyn Db, file: File) -> Self {
|
||||
Self {
|
||||
db,
|
||||
scope,
|
||||
file: scope.file(db),
|
||||
file,
|
||||
diagnostics: std::cell::RefCell::new(TypeCheckDiagnostics::default()),
|
||||
no_type_check: InNoTypeCheck::default(),
|
||||
bomb: DebugDropBomb::new("`InferContext` needs to be explicitly consumed by calling `::finish` to prevent accidental loss of diagnostics."),
|
||||
}
|
||||
}
|
||||
@@ -64,7 +57,9 @@ impl<'db> InferContext<'db> {
|
||||
where
|
||||
T: WithDiagnostics,
|
||||
{
|
||||
self.diagnostics.get_mut().extend(other.diagnostics());
|
||||
self.diagnostics
|
||||
.get_mut()
|
||||
.extend(other.diagnostics().iter().cloned());
|
||||
}
|
||||
|
||||
/// Reports a lint located at `node`.
|
||||
@@ -72,28 +67,13 @@ impl<'db> InferContext<'db> {
|
||||
&self,
|
||||
lint: &'static LintMetadata,
|
||||
node: AnyNodeRef,
|
||||
message: fmt::Arguments,
|
||||
message: std::fmt::Arguments,
|
||||
) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip over diagnostics if the rule is disabled.
|
||||
let Some(severity) = self.db.rule_selection().severity(LintId::of(lint)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if self.is_in_no_type_check() {
|
||||
return;
|
||||
}
|
||||
|
||||
let suppressions = suppressions(self.db, self.file);
|
||||
|
||||
if let Some(suppression) = suppressions.find_suppression(node.range(), LintId::of(lint)) {
|
||||
self.diagnostics.borrow_mut().mark_used(suppression.id());
|
||||
return;
|
||||
}
|
||||
|
||||
self.report_diagnostic(node, DiagnosticId::Lint(lint.name()), severity, message);
|
||||
}
|
||||
|
||||
@@ -105,7 +85,7 @@ impl<'db> InferContext<'db> {
|
||||
node: AnyNodeRef,
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
message: fmt::Arguments,
|
||||
message: std::fmt::Arguments,
|
||||
) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
@@ -116,6 +96,7 @@ impl<'db> InferContext<'db> {
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
// * Check for suppression comments, bump a counter if the diagnostic is suppressed.
|
||||
|
||||
self.diagnostics.borrow_mut().push(TypeCheckDiagnostic {
|
||||
file: self.file,
|
||||
@@ -126,42 +107,6 @@ impl<'db> InferContext<'db> {
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn set_in_no_type_check(&mut self, no_type_check: InNoTypeCheck) {
|
||||
self.no_type_check = no_type_check;
|
||||
}
|
||||
|
||||
fn is_in_no_type_check(&self) -> bool {
|
||||
match self.no_type_check {
|
||||
InNoTypeCheck::Possibly => {
|
||||
// Accessing the semantic index here is fine because
|
||||
// the index belongs to the same file as for which we emit the diagnostic.
|
||||
let index = semantic_index(self.db, self.file);
|
||||
|
||||
let scope_id = self.scope.file_scope_id(self.db);
|
||||
|
||||
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
|
||||
let mut function_scope_tys = index
|
||||
.ancestor_scopes(scope_id)
|
||||
.filter_map(|(_, scope)| scope.node().as_function())
|
||||
.filter_map(|function| {
|
||||
binding_ty(self.db, index.definition(function)).into_function_literal()
|
||||
});
|
||||
|
||||
// Iterate over all functions and test if any is decorated with `@no_type_check`.
|
||||
function_scope_tys.any(|function_ty| {
|
||||
function_ty
|
||||
.decorators(self.db)
|
||||
.iter()
|
||||
.filter_map(|decorator| decorator.into_function_literal())
|
||||
.any(|decorator_ty| {
|
||||
decorator_ty.is_known(self.db, KnownFunction::NoTypeCheck)
|
||||
})
|
||||
})
|
||||
}
|
||||
InNoTypeCheck::Yes => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn finish(mut self) -> TypeCheckDiagnostics {
|
||||
self.bomb.defuse();
|
||||
@@ -181,17 +126,6 @@ impl fmt::Debug for InferContext<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
pub(crate) enum InNoTypeCheck {
|
||||
/// The inference might be in a `no_type_check` block but only if any
|
||||
/// ancestor function is decorated with `@no_type_check`.
|
||||
#[default]
|
||||
Possibly,
|
||||
|
||||
/// The inference is known to be in an `@no_type_check` decorated function.
|
||||
Yes,
|
||||
}
|
||||
|
||||
pub(crate) trait WithDiagnostics {
|
||||
fn diagnostics(&self) -> &TypeCheckDiagnostics;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use super::context::InferContext;
|
||||
use crate::declare_lint;
|
||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||
use crate::suppression::FileSuppressionId;
|
||||
use crate::types::string_annotation::{
|
||||
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||
@@ -12,12 +10,13 @@ use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::context::InferContext;
|
||||
|
||||
/// Registers all known type check lints.
|
||||
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&CALL_NON_CALLABLE);
|
||||
@@ -27,7 +26,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
|
||||
registry.register_lint(&DIVISION_BY_ZERO);
|
||||
registry.register_lint(&DUPLICATE_BASE);
|
||||
registry.register_lint(&INCOMPATIBLE_SLOTS);
|
||||
registry.register_lint(&INCONSISTENT_MRO);
|
||||
registry.register_lint(&INDEX_OUT_OF_BOUNDS);
|
||||
registry.register_lint(&INVALID_ASSIGNMENT);
|
||||
@@ -149,64 +147,6 @@ declare_lint! {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for classes whose bases define incompatible `__slots__`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Inheriting from bases with incompatible `__slots__`s
|
||||
/// will lead to a `TypeError` at runtime.
|
||||
///
|
||||
/// Classes with no or empty `__slots__` are always compatible:
|
||||
///
|
||||
/// ```python
|
||||
/// class A: ...
|
||||
/// class B:
|
||||
/// __slots__ = ()
|
||||
/// class C:
|
||||
/// __slots__ = ("a", "b")
|
||||
///
|
||||
/// # fine
|
||||
/// class D(A, B, C): ...
|
||||
/// ```
|
||||
///
|
||||
/// Multiple inheritance from more than one different class
|
||||
/// defining non-empty `__slots__` is not allowed:
|
||||
///
|
||||
/// ```python
|
||||
/// class A:
|
||||
/// __slots__ = ("a", "b")
|
||||
///
|
||||
/// class B:
|
||||
/// __slots__ = ("a", "b") # Even if the values are the same
|
||||
///
|
||||
/// # TypeError: multiple bases have instance lay-out conflict
|
||||
/// class C(A, B): ...
|
||||
/// ```
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Dynamic (not tuple or string literal) `__slots__` are not checked.
|
||||
/// Additionally, classes inheriting from built-in classes with implicit layouts
|
||||
/// like `str` or `int` are also not checked.
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> hasattr(int, "__slots__")
|
||||
/// False
|
||||
/// >>> hasattr(str, "__slots__")
|
||||
/// False
|
||||
/// >>> class A(int, str): ...
|
||||
/// Traceback (most recent call last):
|
||||
/// File "<python-input-0>", line 1, in <module>
|
||||
/// class A(int, str): ...
|
||||
/// TypeError: multiple bases have instance lay-out conflict
|
||||
/// ```
|
||||
pub(crate) static INCOMPATIBLE_SLOTS = {
|
||||
summary: "detects class definitions whose MRO has conflicting `__slots__`",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
pub(crate) static INCONSISTENT_MRO = {
|
||||
@@ -572,11 +512,11 @@ declare_lint! {
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
pub(crate) id: DiagnosticId,
|
||||
pub(crate) message: String,
|
||||
pub(crate) range: TextRange,
|
||||
pub(crate) severity: Severity,
|
||||
pub(crate) file: File,
|
||||
pub(super) id: DiagnosticId,
|
||||
pub(super) message: String,
|
||||
pub(super) range: TextRange,
|
||||
pub(super) severity: Severity,
|
||||
pub(super) file: File,
|
||||
}
|
||||
|
||||
impl TypeCheckDiagnostic {
|
||||
@@ -630,41 +570,41 @@ impl Ranged for TypeCheckDiagnostic {
|
||||
/// each Salsa-struct comes with an overhead.
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostics {
|
||||
diagnostics: Vec<Arc<TypeCheckDiagnostic>>,
|
||||
used_suppressions: FxHashSet<FileSuppressionId>,
|
||||
inner: Vec<std::sync::Arc<TypeCheckDiagnostic>>,
|
||||
}
|
||||
|
||||
impl TypeCheckDiagnostics {
|
||||
pub(crate) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
|
||||
self.diagnostics.push(Arc::new(diagnostic));
|
||||
}
|
||||
|
||||
pub(super) fn extend(&mut self, other: &TypeCheckDiagnostics) {
|
||||
self.diagnostics.extend_from_slice(&other.diagnostics);
|
||||
self.used_suppressions.extend(&other.used_suppressions);
|
||||
}
|
||||
|
||||
pub(crate) fn mark_used(&mut self, suppression_id: FileSuppressionId) {
|
||||
self.used_suppressions.insert(suppression_id);
|
||||
}
|
||||
|
||||
pub(crate) fn is_used(&self, suppression_id: FileSuppressionId) -> bool {
|
||||
self.used_suppressions.contains(&suppression_id)
|
||||
}
|
||||
|
||||
pub(crate) fn used_len(&self) -> usize {
|
||||
self.used_suppressions.len()
|
||||
pub(super) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
|
||||
self.inner.push(Arc::new(diagnostic));
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
self.used_suppressions.shrink_to_fit();
|
||||
self.diagnostics.shrink_to_fit();
|
||||
self.inner.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<TypeCheckDiagnostic> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = TypeCheckDiagnostic>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter.into_iter().map(std::sync::Arc::new));
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Extend<&'a std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = &'a Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner
|
||||
.extend(iter.into_iter().map(std::sync::Arc::clone));
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TypeCheckDiagnostics {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.diagnostics.fmt(f)
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +612,7 @@ impl Deref for TypeCheckDiagnostics {
|
||||
type Target = [std::sync::Arc<TypeCheckDiagnostic>];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.diagnostics
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +621,7 @@ impl IntoIterator for TypeCheckDiagnostics {
|
||||
type IntoIter = std::vec::IntoIter<std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.diagnostics.into_iter()
|
||||
self.inner.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,7 +630,7 @@ impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
|
||||
type IntoIter = std::slice::Iter<'a, std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.diagnostics.iter()
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,11 +812,3 @@ pub(crate) fn report_invalid_exception_cause(context: &InferContext, node: &ast:
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node: &ast::Expr) {
|
||||
context.report_lint(
|
||||
&INCOMPATIBLE_SLOTS,
|
||||
node.into(),
|
||||
format_args!("Class base has incompatible `__slots__`"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -324,12 +324,6 @@ impl<'db> TypeArrayDisplay<'db> for Vec<Type<'db>> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeArrayDisplay<'db> for [Type<'db>] {
|
||||
fn display(&self, db: &'db dyn Db) -> DisplayTypeArray {
|
||||
DisplayTypeArray { types: self, db }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayTypeArray<'b, 'db> {
|
||||
types: &'b [Type<'db>],
|
||||
db: &'db dyn Db,
|
||||
|
||||
@@ -31,7 +31,7 @@ use std::num::NonZeroU32;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, UnaryOp};
|
||||
use ruff_text_size::Ranged;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use salsa;
|
||||
@@ -42,7 +42,7 @@ use crate::module_resolver::{file_to_module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, HasScopedUseId, ScopedExpressionId};
|
||||
use crate::semantic_index::definition::{
|
||||
AssignmentDefinitionKind, Definition, DefinitionKind, DefinitionNodeKey,
|
||||
ExceptHandlerDefinitionKind, ForStmtDefinitionKind, TargetKind,
|
||||
ExceptHandlerDefinitionKind, TargetKind,
|
||||
};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::semantic_index;
|
||||
@@ -72,14 +72,13 @@ use crate::unpack::Unpack;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
use crate::Db;
|
||||
|
||||
use super::context::{InNoTypeCheck, InferContext, WithDiagnostics};
|
||||
use super::context::{InferContext, WithDiagnostics};
|
||||
use super::diagnostic::{
|
||||
report_index_out_of_bounds, report_invalid_exception_caught, report_invalid_exception_cause,
|
||||
report_invalid_exception_raised, report_non_subscriptable,
|
||||
report_possibly_unresolved_reference, report_slice_step_size_zero, report_unresolved_reference,
|
||||
SUBCLASS_OF_FINAL_CLASS,
|
||||
};
|
||||
use super::slots::check_class_slots;
|
||||
use super::string_annotation::{
|
||||
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||
};
|
||||
@@ -132,7 +131,7 @@ pub(crate) fn infer_definition_types<'db>(
|
||||
let file = definition.file(db);
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_definition_types",
|
||||
range = ?definition.kind(db).target_range(),
|
||||
range = ?definition.kind(db).range(),
|
||||
file = %file.path(db)
|
||||
)
|
||||
.entered();
|
||||
@@ -155,7 +154,7 @@ pub(crate) fn infer_deferred_types<'db>(
|
||||
let _span = tracing::trace_span!(
|
||||
"infer_deferred_types",
|
||||
definition = ?definition.as_id(),
|
||||
range = ?definition.kind(db).target_range(),
|
||||
range = ?definition.kind(db).range(),
|
||||
file = %file.path(db)
|
||||
)
|
||||
.entered();
|
||||
@@ -169,6 +168,7 @@ pub(crate) fn infer_deferred_types<'db>(
|
||||
/// Use rarely; only for cases where we'd otherwise risk double-inferring an expression: RHS of an
|
||||
/// assignment, which might be unpacking/multi-target and thus part of multiple definitions, or a
|
||||
/// type narrowing guard expression (e.g. if statement test node).
|
||||
#[allow(unused)]
|
||||
#[salsa::tracked(return_ref)]
|
||||
pub(crate) fn infer_expression_types<'db>(
|
||||
db: &'db dyn Db,
|
||||
@@ -198,16 +198,21 @@ pub(crate) fn infer_expression_types<'db>(
|
||||
fn infer_unpack_types<'db>(db: &'db dyn Db, unpack: Unpack<'db>) -> UnpackResult<'db> {
|
||||
let file = unpack.file(db);
|
||||
let _span =
|
||||
tracing::trace_span!("infer_unpack_types", range=?unpack.range(db), file=%file.path(db))
|
||||
tracing::trace_span!("infer_unpack_types", unpack=?unpack.as_id(), file=%file.path(db))
|
||||
.entered();
|
||||
|
||||
let mut unpacker = Unpacker::new(db, unpack.scope(db));
|
||||
unpacker.unpack(unpack.target(db), unpack.value(db));
|
||||
let value = unpack.value(db);
|
||||
let scope = unpack.scope(db);
|
||||
|
||||
let result = infer_expression_types(db, value);
|
||||
let value_ty = result.expression_ty(value.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
let mut unpacker = Unpacker::new(db, file);
|
||||
unpacker.unpack(unpack.target(db), value_ty, scope);
|
||||
unpacker.finish()
|
||||
}
|
||||
|
||||
/// A region within which we can infer types.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum InferenceRegion<'db> {
|
||||
/// infer types for a standalone [`Expression`]
|
||||
Expression(Expression<'db>),
|
||||
@@ -219,18 +224,6 @@ pub(crate) enum InferenceRegion<'db> {
|
||||
Scope(ScopeId<'db>),
|
||||
}
|
||||
|
||||
impl<'db> InferenceRegion<'db> {
|
||||
fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
match self {
|
||||
InferenceRegion::Expression(expression) => expression.scope(db),
|
||||
InferenceRegion::Definition(definition) | InferenceRegion::Deferred(definition) => {
|
||||
definition.scope(db)
|
||||
}
|
||||
InferenceRegion::Scope(scope) => scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The inferred types for a single region.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) struct TypeInference<'db> {
|
||||
@@ -389,10 +382,16 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
region: InferenceRegion<'db>,
|
||||
index: &'db SemanticIndex<'db>,
|
||||
) -> Self {
|
||||
let scope = region.scope(db);
|
||||
let (file, scope) = match region {
|
||||
InferenceRegion::Expression(expression) => (expression.file(db), expression.scope(db)),
|
||||
InferenceRegion::Definition(definition) | InferenceRegion::Deferred(definition) => {
|
||||
(definition.file(db), definition.scope(db))
|
||||
}
|
||||
InferenceRegion::Scope(scope) => (scope.file(db), scope),
|
||||
};
|
||||
|
||||
Self {
|
||||
context: InferContext::new(db, scope),
|
||||
context: InferContext::new(db, file),
|
||||
index,
|
||||
region,
|
||||
deferred_state: DeferredExpressionState::None,
|
||||
@@ -592,44 +591,41 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// (3) Check that the class's MRO is resolvable
|
||||
match class.try_mro(self.db()).as_ref() {
|
||||
Err(mro_error) => {
|
||||
match mro_error.reason() {
|
||||
MroErrorKind::DuplicateBases(duplicates) => {
|
||||
let base_nodes = class_node.bases();
|
||||
for (index, duplicate) in duplicates {
|
||||
self.context.report_lint(
|
||||
&DUPLICATE_BASE,
|
||||
(&base_nodes[*index]).into(),
|
||||
format_args!("Duplicate base class `{}`", duplicate.name(self.db())),
|
||||
);
|
||||
}
|
||||
if let Err(mro_error) = class.try_mro(self.db()).as_ref() {
|
||||
match mro_error.reason() {
|
||||
MroErrorKind::DuplicateBases(duplicates) => {
|
||||
let base_nodes = class_node.bases();
|
||||
for (index, duplicate) in duplicates {
|
||||
self.context.report_lint(
|
||||
&DUPLICATE_BASE,
|
||||
(&base_nodes[*index]).into(),
|
||||
format_args!("Duplicate base class `{}`", duplicate.name(self.db())),
|
||||
);
|
||||
}
|
||||
MroErrorKind::InvalidBases(bases) => {
|
||||
let base_nodes = class_node.bases();
|
||||
for (index, base_ty) in bases {
|
||||
self.context.report_lint(
|
||||
&INVALID_BASE,
|
||||
(&base_nodes[*index]).into(),
|
||||
format_args!(
|
||||
"Invalid class base with type `{}` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
|
||||
base_ty.display(self.db())
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
MroErrorKind::UnresolvableMro { bases_list } => self.context.report_lint(
|
||||
&INCONSISTENT_MRO,
|
||||
class_node.into(),
|
||||
format_args!(
|
||||
"Cannot create a consistent method resolution order (MRO) for class `{}` with bases list `[{}]`",
|
||||
class.name(self.db()),
|
||||
bases_list.iter().map(|base| base.display(self.db())).join(", ")
|
||||
),
|
||||
)
|
||||
}
|
||||
MroErrorKind::InvalidBases(bases) => {
|
||||
let base_nodes = class_node.bases();
|
||||
for (index, base_ty) in bases {
|
||||
self.context.report_lint(
|
||||
&INVALID_BASE,
|
||||
(&base_nodes[*index]).into(),
|
||||
format_args!(
|
||||
"Invalid class base with type `{}` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
|
||||
base_ty.display(self.db())
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
MroErrorKind::UnresolvableMro { bases_list } => self.context.report_lint(
|
||||
&INCONSISTENT_MRO,
|
||||
class_node.into(),
|
||||
format_args!(
|
||||
"Cannot create a consistent method resolution order (MRO) for class `{}` with bases list `[{}]`",
|
||||
class.name(self.db()),
|
||||
bases_list.iter().map(|base| base.display(self.db())).join(", ")
|
||||
),
|
||||
)
|
||||
}
|
||||
Ok(_) => check_class_slots(&self.context, class, class_node)
|
||||
}
|
||||
|
||||
// (4) Check that the class's metaclass can be determined without error.
|
||||
@@ -714,7 +710,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_augment_assignment_definition(augmented_assignment.node(), definition);
|
||||
}
|
||||
DefinitionKind::For(for_statement_definition) => {
|
||||
self.infer_for_statement_definition(for_statement_definition, definition);
|
||||
self.infer_for_statement_definition(
|
||||
for_statement_definition.target(),
|
||||
for_statement_definition.iterable(),
|
||||
for_statement_definition.is_async(),
|
||||
definition,
|
||||
);
|
||||
}
|
||||
DefinitionKind::NamedExpression(named_expression) => {
|
||||
self.infer_named_expression_definition(named_expression.node(), definition);
|
||||
@@ -1028,20 +1029,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
decorator_list,
|
||||
} = function;
|
||||
|
||||
// Check if the function is decorated with the `no_type_check` decorator
|
||||
// and, if so, suppress any errors that come after the decorators.
|
||||
let mut decorator_tys = Vec::with_capacity(decorator_list.len());
|
||||
|
||||
for decorator in decorator_list {
|
||||
let ty = self.infer_decorator(decorator);
|
||||
decorator_tys.push(ty);
|
||||
|
||||
if let Type::FunctionLiteral(function) = ty {
|
||||
if function.is_known(self.db(), KnownFunction::NoTypeCheck) {
|
||||
self.context.set_in_no_type_check(InNoTypeCheck::Yes);
|
||||
}
|
||||
}
|
||||
}
|
||||
let decorator_tys: Box<[Type]> = decorator_list
|
||||
.iter()
|
||||
.map(|decorator| self.infer_decorator(decorator))
|
||||
.collect();
|
||||
|
||||
for default in parameters
|
||||
.iter_non_variadic_params()
|
||||
@@ -1077,7 +1068,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
&name.id,
|
||||
function_kind,
|
||||
body_scope,
|
||||
decorator_tys.into_boxed_slice(),
|
||||
decorator_tys,
|
||||
));
|
||||
|
||||
self.add_declaration_with_binding(function.into(), definition, function_ty, function_ty);
|
||||
@@ -1842,28 +1833,18 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
} = assignment;
|
||||
|
||||
for target in targets {
|
||||
self.infer_target(target, value);
|
||||
self.infer_assignment_target(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer the definition types involved in a `target` expression.
|
||||
///
|
||||
/// This is used for assignment statements, for statements, etc. with a single or multiple
|
||||
/// targets (unpacking).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the `value` is not a standalone expression.
|
||||
fn infer_target(&mut self, target: &ast::Expr, value: &ast::Expr) {
|
||||
// TODO: Remove the `value` argument once we handle all possible assignment targets.
|
||||
fn infer_assignment_target(&mut self, target: &ast::Expr, value: &ast::Expr) {
|
||||
match target {
|
||||
ast::Expr::Name(name) => self.infer_definition(name),
|
||||
ast::Expr::List(ast::ExprList { elts, .. })
|
||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
for element in elts {
|
||||
self.infer_target(element, value);
|
||||
}
|
||||
if elts.is_empty() {
|
||||
self.infer_standalone_expression(value);
|
||||
self.infer_assignment_target(element, value);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -1882,7 +1863,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let value = assignment.value();
|
||||
let name = assignment.name();
|
||||
|
||||
let value_ty = self.infer_standalone_expression(value);
|
||||
self.infer_standalone_expression(value);
|
||||
|
||||
let value_ty = self.expression_ty(value);
|
||||
let name_ast_id = name.scoped_expression_id(self.db(), self.scope());
|
||||
|
||||
let mut target_ty = match assignment.target() {
|
||||
TargetKind::Sequence(unpack) => {
|
||||
@@ -1893,7 +1877,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.context.extend(unpacked);
|
||||
}
|
||||
|
||||
let name_ast_id = name.scoped_expression_id(self.db(), self.scope());
|
||||
unpacked.get(name_ast_id).unwrap_or(Type::Unknown)
|
||||
}
|
||||
TargetKind::Name => value_ty,
|
||||
@@ -2121,41 +2104,36 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
is_async: _,
|
||||
} = for_statement;
|
||||
|
||||
self.infer_target(target, iter);
|
||||
// TODO more complex assignment targets
|
||||
if let ast::Expr::Name(name) = &**target {
|
||||
self.infer_definition(name);
|
||||
} else {
|
||||
self.infer_standalone_expression(iter);
|
||||
self.infer_expression(target);
|
||||
}
|
||||
self.infer_body(body);
|
||||
self.infer_body(orelse);
|
||||
}
|
||||
|
||||
fn infer_for_statement_definition(
|
||||
&mut self,
|
||||
for_stmt: &ForStmtDefinitionKind<'db>,
|
||||
target: &ast::ExprName,
|
||||
iterable: &ast::Expr,
|
||||
is_async: bool,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let iterable = for_stmt.iterable();
|
||||
let name = for_stmt.name();
|
||||
|
||||
let iterable_ty = self.infer_standalone_expression(iterable);
|
||||
|
||||
let loop_var_value_ty = if for_stmt.is_async() {
|
||||
let loop_var_value_ty = if is_async {
|
||||
todo_type!("async iterables/iterators")
|
||||
} else {
|
||||
match for_stmt.target() {
|
||||
TargetKind::Sequence(unpack) => {
|
||||
let unpacked = infer_unpack_types(self.db(), unpack);
|
||||
if for_stmt.is_first() {
|
||||
self.context.extend(unpacked);
|
||||
}
|
||||
let name_ast_id = name.scoped_expression_id(self.db(), self.scope());
|
||||
unpacked.get(name_ast_id).unwrap_or(Type::Unknown)
|
||||
}
|
||||
TargetKind::Name => iterable_ty
|
||||
.iterate(self.db())
|
||||
.unwrap_with_diagnostic(&self.context, iterable.into()),
|
||||
}
|
||||
iterable_ty
|
||||
.iterate(self.db())
|
||||
.unwrap_with_diagnostic(&self.context, iterable.into())
|
||||
};
|
||||
|
||||
self.store_expression_type(name, loop_var_value_ty);
|
||||
self.add_binding(name.into(), definition, loop_var_value_ty);
|
||||
self.store_expression_type(target, loop_var_value_ty);
|
||||
self.add_binding(target.into(), definition, loop_var_value_ty);
|
||||
}
|
||||
|
||||
fn infer_while_statement(&mut self, while_statement: &ast::StmtWhile) {
|
||||
@@ -3255,19 +3233,17 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
(_, Type::Never) => Type::Never,
|
||||
(_, Type::Unknown) => Type::Unknown,
|
||||
|
||||
(ast::UnaryOp::UAdd, Type::IntLiteral(value)) => Type::IntLiteral(value),
|
||||
(ast::UnaryOp::USub, Type::IntLiteral(value)) => Type::IntLiteral(-value),
|
||||
(ast::UnaryOp::Invert, Type::IntLiteral(value)) => Type::IntLiteral(!value),
|
||||
(UnaryOp::UAdd, Type::IntLiteral(value)) => Type::IntLiteral(value),
|
||||
(UnaryOp::USub, Type::IntLiteral(value)) => Type::IntLiteral(-value),
|
||||
(UnaryOp::Invert, Type::IntLiteral(value)) => Type::IntLiteral(!value),
|
||||
|
||||
(ast::UnaryOp::UAdd, Type::BooleanLiteral(bool)) => Type::IntLiteral(i64::from(bool)),
|
||||
(ast::UnaryOp::USub, Type::BooleanLiteral(bool)) => Type::IntLiteral(-i64::from(bool)),
|
||||
(ast::UnaryOp::Invert, Type::BooleanLiteral(bool)) => {
|
||||
Type::IntLiteral(!i64::from(bool))
|
||||
}
|
||||
(UnaryOp::UAdd, Type::BooleanLiteral(bool)) => Type::IntLiteral(i64::from(bool)),
|
||||
(UnaryOp::USub, Type::BooleanLiteral(bool)) => Type::IntLiteral(-i64::from(bool)),
|
||||
(UnaryOp::Invert, Type::BooleanLiteral(bool)) => Type::IntLiteral(!i64::from(bool)),
|
||||
|
||||
(ast::UnaryOp::Not, ty) => ty.bool(self.db()).negate().into_type(self.db()),
|
||||
(UnaryOp::Not, ty) => ty.bool(self.db()).negate().into_type(self.db()),
|
||||
(
|
||||
op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub | ast::UnaryOp::Invert),
|
||||
op @ (UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert),
|
||||
Type::FunctionLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
@@ -3285,10 +3261,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
| Type::Tuple(_),
|
||||
) => {
|
||||
let unary_dunder_method = match op {
|
||||
ast::UnaryOp::Invert => "__invert__",
|
||||
ast::UnaryOp::UAdd => "__pos__",
|
||||
ast::UnaryOp::USub => "__neg__",
|
||||
ast::UnaryOp::Not => {
|
||||
UnaryOp::Invert => "__invert__",
|
||||
UnaryOp::UAdd => "__pos__",
|
||||
UnaryOp::USub => "__neg__",
|
||||
UnaryOp::Not => {
|
||||
unreachable!("Not operator is handled in its own case");
|
||||
}
|
||||
};
|
||||
@@ -3606,37 +3582,27 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
n_values: usize,
|
||||
) -> Type<'db> {
|
||||
let mut done = false;
|
||||
|
||||
let elements = values.into_iter().enumerate().map(|(i, ty)| {
|
||||
if done {
|
||||
return Type::Never;
|
||||
}
|
||||
|
||||
let is_last = i == n_values - 1;
|
||||
|
||||
match (ty.bool(db), is_last, op) {
|
||||
(Truthiness::AlwaysTrue, false, ast::BoolOp::And) => Type::Never,
|
||||
(Truthiness::AlwaysFalse, false, ast::BoolOp::Or) => Type::Never,
|
||||
|
||||
(Truthiness::AlwaysFalse, _, ast::BoolOp::And)
|
||||
| (Truthiness::AlwaysTrue, _, ast::BoolOp::Or) => {
|
||||
done = true;
|
||||
ty
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
values.into_iter().enumerate().map(|(i, ty)| {
|
||||
if done {
|
||||
Type::Never
|
||||
} else {
|
||||
let is_last = i == n_values - 1;
|
||||
match (ty.bool(db), is_last, op) {
|
||||
(Truthiness::Ambiguous, _, _) => ty,
|
||||
(Truthiness::AlwaysTrue, false, ast::BoolOp::And) => Type::Never,
|
||||
(Truthiness::AlwaysFalse, false, ast::BoolOp::Or) => Type::Never,
|
||||
(Truthiness::AlwaysFalse, _, ast::BoolOp::And)
|
||||
| (Truthiness::AlwaysTrue, _, ast::BoolOp::Or) => {
|
||||
done = true;
|
||||
ty
|
||||
}
|
||||
(_, true, _) => ty,
|
||||
}
|
||||
}
|
||||
|
||||
(Truthiness::Ambiguous, false, _) => IntersectionBuilder::new(db)
|
||||
.add_positive(ty)
|
||||
.add_negative(match op {
|
||||
ast::BoolOp::And => Type::AlwaysTruthy,
|
||||
ast::BoolOp::Or => Type::AlwaysFalsy,
|
||||
})
|
||||
.build(),
|
||||
|
||||
(_, true, _) => ty,
|
||||
}
|
||||
});
|
||||
|
||||
UnionType::from_elements(db, elements)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn infer_compare_expression(&mut self, compare: &ast::ExprCompare) -> Type<'db> {
|
||||
@@ -5233,7 +5199,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
// for negative and positive numbers
|
||||
ast::Expr::UnaryOp(ref u)
|
||||
if matches!(u.op, ast::UnaryOp::USub | ast::UnaryOp::UAdd)
|
||||
if matches!(u.op, UnaryOp::USub | UnaryOp::UAdd)
|
||||
&& u.operand.is_number_literal_expr() =>
|
||||
{
|
||||
self.infer_unary_expression(u)
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::diagnostic::report_base_with_incompatible_slots;
|
||||
use crate::types::{Class, ClassLiteralType, Type};
|
||||
|
||||
use super::InferContext;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
enum SlotsKind {
|
||||
/// `__slots__` is not found in the class.
|
||||
NotSpecified,
|
||||
/// `__slots__` is defined but empty: `__slots__ = ()`.
|
||||
Empty,
|
||||
/// `__slots__` is defined and is not empty: `__slots__ = ("a", "b")`.
|
||||
NotEmpty,
|
||||
/// `__slots__` is defined but its value is dynamic:
|
||||
/// * `__slots__ = tuple(a for a in b)`
|
||||
/// * `__slots__ = ["a", "b"]`
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
impl SlotsKind {
|
||||
fn from(db: &dyn Db, base: Class) -> Self {
|
||||
let Symbol::Type(slots_ty, bound) = base.own_class_member(db, "__slots__") else {
|
||||
return Self::NotSpecified;
|
||||
};
|
||||
|
||||
if matches!(bound, Boundness::PossiblyUnbound) {
|
||||
return Self::Dynamic;
|
||||
};
|
||||
|
||||
match slots_ty {
|
||||
// __slots__ = ("a", "b")
|
||||
Type::Tuple(tuple) => {
|
||||
if tuple.elements(db).is_empty() {
|
||||
Self::Empty
|
||||
} else {
|
||||
Self::NotEmpty
|
||||
}
|
||||
}
|
||||
|
||||
// __slots__ = "abc" # Same as `("abc",)`
|
||||
Type::StringLiteral(_) => Self::NotEmpty,
|
||||
|
||||
_ => Self::Dynamic,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_class_slots(context: &InferContext, class: Class, node: &ast::StmtClassDef) {
|
||||
let db = context.db();
|
||||
|
||||
let mut first_with_solid_base = None;
|
||||
let mut common_solid_base = None;
|
||||
let mut found_second = false;
|
||||
|
||||
for (index, base) in class.explicit_bases(db).iter().enumerate() {
|
||||
let Type::ClassLiteral(ClassLiteralType { class: base }) = base else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let solid_base = base.iter_mro(db).find_map(|current| {
|
||||
let ClassBase::Class(current) = current else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match SlotsKind::from(db, current) {
|
||||
SlotsKind::NotEmpty => Some(current),
|
||||
SlotsKind::NotSpecified | SlotsKind::Empty => None,
|
||||
SlotsKind::Dynamic => None,
|
||||
}
|
||||
});
|
||||
|
||||
if solid_base.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let base_node = &node.bases()[index];
|
||||
|
||||
if first_with_solid_base.is_none() {
|
||||
first_with_solid_base = Some(index);
|
||||
common_solid_base = solid_base;
|
||||
continue;
|
||||
}
|
||||
|
||||
if solid_base == common_solid_base {
|
||||
continue;
|
||||
}
|
||||
|
||||
found_second = true;
|
||||
report_base_with_incompatible_slots(context, base_node);
|
||||
}
|
||||
|
||||
if found_second {
|
||||
if let Some(index) = first_with_solid_base {
|
||||
let base_node = &node.bases()[index];
|
||||
report_base_with_incompatible_slots(context, base_node);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,27 @@
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{infer_expression_types, todo_type, Type, TypeCheckDiagnostics};
|
||||
use crate::unpack::UnpackValue;
|
||||
use crate::types::{todo_type, Type, TypeCheckDiagnostics};
|
||||
use crate::Db;
|
||||
|
||||
use super::context::{InferContext, WithDiagnostics};
|
||||
use super::diagnostic::INVALID_ASSIGNMENT;
|
||||
use super::{TupleType, UnionType};
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
pub(crate) struct Unpacker<'db> {
|
||||
context: InferContext<'db>,
|
||||
scope: ScopeId<'db>,
|
||||
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> Unpacker<'db> {
|
||||
pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self {
|
||||
pub(crate) fn new(db: &'db dyn Db, file: File) -> Self {
|
||||
Self {
|
||||
context: InferContext::new(db, scope),
|
||||
context: InferContext::new(db, file),
|
||||
targets: FxHashMap::default(),
|
||||
scope,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,211 +29,98 @@ impl<'db> Unpacker<'db> {
|
||||
self.context.db()
|
||||
}
|
||||
|
||||
/// Unpack the value to the target expression.
|
||||
pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) {
|
||||
debug_assert!(
|
||||
matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)),
|
||||
"Unpacking target must be a list or tuple expression"
|
||||
);
|
||||
|
||||
let mut value_ty = infer_expression_types(self.db(), value.expression())
|
||||
.expression_ty(value.scoped_expression_id(self.db(), self.scope));
|
||||
|
||||
if value.is_iterable() {
|
||||
// If the value is an iterable, then the type that needs to be unpacked is the iterator
|
||||
// type.
|
||||
value_ty = value_ty
|
||||
.iterate(self.db())
|
||||
.unwrap_with_diagnostic(&self.context, value.as_any_node_ref(self.db()));
|
||||
}
|
||||
|
||||
self.unpack_inner(target, value_ty);
|
||||
}
|
||||
|
||||
fn unpack_inner(&mut self, target: &ast::Expr, value_ty: Type<'db>) {
|
||||
pub(crate) fn unpack(&mut self, target: &ast::Expr, value_ty: Type<'db>, scope: ScopeId<'db>) {
|
||||
match target {
|
||||
ast::Expr::Name(target_name) => {
|
||||
self.targets.insert(
|
||||
target_name.scoped_expression_id(self.db(), self.scope),
|
||||
value_ty,
|
||||
);
|
||||
self.targets
|
||||
.insert(target_name.scoped_expression_id(self.db(), scope), value_ty);
|
||||
}
|
||||
ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
|
||||
self.unpack_inner(value, value_ty);
|
||||
self.unpack(value, value_ty, scope);
|
||||
}
|
||||
ast::Expr::List(ast::ExprList { elts, .. })
|
||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
// Initialize the vector of target types, one for each target.
|
||||
//
|
||||
// This is mainly useful for the union type where the target type at index `n` is
|
||||
// going to be a union of types from every union type element at index `n`.
|
||||
//
|
||||
// For example, if the type is `tuple[int, int] | tuple[int, str]` and the target
|
||||
// has two elements `(a, b)`, then
|
||||
// * The type of `a` will be a union of `int` and `int` which are at index 0 in the
|
||||
// first and second tuple respectively which resolves to an `int`.
|
||||
// * Similarly, the type of `b` will be a union of `int` and `str` which are at
|
||||
// index 1 in the first and second tuple respectively which will be `int | str`.
|
||||
let mut target_types = vec![vec![]; elts.len()];
|
||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => match value_ty {
|
||||
Type::Tuple(tuple_ty) => {
|
||||
let starred_index = elts.iter().position(ast::Expr::is_starred_expr);
|
||||
|
||||
let unpack_types = match value_ty {
|
||||
Type::Union(union_ty) => union_ty.elements(self.db()),
|
||||
_ => std::slice::from_ref(&value_ty),
|
||||
};
|
||||
let element_types = if let Some(starred_index) = starred_index {
|
||||
if tuple_ty.len(self.db()) >= elts.len() - 1 {
|
||||
let mut element_types = Vec::with_capacity(elts.len());
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[..starred_index],
|
||||
);
|
||||
|
||||
for ty in unpack_types.iter().copied() {
|
||||
// Deconstruct certain types to delegate the inference back to the tuple type
|
||||
// for correct handling of starred expressions.
|
||||
let ty = match ty {
|
||||
Type::StringLiteral(string_literal_ty) => {
|
||||
// We could go further and deconstruct to an array of `StringLiteral`
|
||||
// with each individual character, instead of just an array of
|
||||
// `LiteralString`, but there would be a cost and it's not clear that
|
||||
// it's worth it.
|
||||
Type::tuple(
|
||||
self.db(),
|
||||
std::iter::repeat(Type::LiteralString)
|
||||
.take(string_literal_ty.python_len(self.db())),
|
||||
)
|
||||
}
|
||||
_ => ty,
|
||||
};
|
||||
// E.g., in `(a, *b, c, d) = ...`, the index of starred element `b`
|
||||
// is 1 and the remaining elements after that are 2.
|
||||
let remaining = elts.len() - (starred_index + 1);
|
||||
// This index represents the type of the last element that belongs
|
||||
// to the starred expression, in an exclusive manner.
|
||||
let starred_end_index = tuple_ty.len(self.db()) - remaining;
|
||||
// SAFETY: Safe because of the length check above.
|
||||
let _starred_element_types =
|
||||
&tuple_ty.elements(self.db())[starred_index..starred_end_index];
|
||||
// TODO: Combine the types into a list type. If the
|
||||
// starred_element_types is empty, then it should be `List[Any]`.
|
||||
// combine_types(starred_element_types);
|
||||
element_types.push(todo_type!("starred unpacking"));
|
||||
|
||||
if let Some(tuple_ty) = ty.into_tuple() {
|
||||
let tuple_ty_elements = self.tuple_ty_elements(target, elts, tuple_ty);
|
||||
|
||||
match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
Ordering::Less => {
|
||||
self.context.report_lint(
|
||||
&INVALID_ASSIGNMENT,
|
||||
target.into(),
|
||||
format_args!(
|
||||
"Too many values to unpack (expected {}, got {})",
|
||||
elts.len(),
|
||||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
self.context.report_lint(
|
||||
&INVALID_ASSIGNMENT,
|
||||
target.into(),
|
||||
format_args!(
|
||||
"Not enough values to unpack (expected {}, got {})",
|
||||
elts.len(),
|
||||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
|
||||
for (index, ty) in tuple_ty_elements.iter().enumerate() {
|
||||
if let Some(element_types) = target_types.get_mut(index) {
|
||||
element_types.push(*ty);
|
||||
}
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[starred_end_index..],
|
||||
);
|
||||
Cow::Owned(element_types)
|
||||
} else {
|
||||
let mut element_types = tuple_ty.elements(self.db()).to_vec();
|
||||
// Subtract 1 to insert the starred expression type at the correct
|
||||
// index.
|
||||
element_types.resize(elts.len() - 1, Type::Unknown);
|
||||
// TODO: This should be `list[Unknown]`
|
||||
element_types.insert(starred_index, todo_type!("starred unpacking"));
|
||||
Cow::Owned(element_types)
|
||||
}
|
||||
} else {
|
||||
let ty = if ty.is_literal_string() {
|
||||
Type::LiteralString
|
||||
} else {
|
||||
ty.iterate(self.db())
|
||||
.unwrap_with_diagnostic(&self.context, AnyNodeRef::from(target))
|
||||
};
|
||||
for target_type in &mut target_types {
|
||||
target_type.push(ty);
|
||||
}
|
||||
Cow::Borrowed(tuple_ty.elements(self.db()).as_ref())
|
||||
};
|
||||
|
||||
for (index, element) in elts.iter().enumerate() {
|
||||
self.unpack(
|
||||
element,
|
||||
element_types.get(index).copied().unwrap_or(Type::Unknown),
|
||||
scope,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (index, element) in elts.iter().enumerate() {
|
||||
// SAFETY: `target_types` is initialized with the same length as `elts`.
|
||||
let element_ty = match target_types[index].as_slice() {
|
||||
[] => Type::Unknown,
|
||||
types => UnionType::from_elements(self.db(), types),
|
||||
};
|
||||
self.unpack_inner(element, element_ty);
|
||||
Type::StringLiteral(string_literal_ty) => {
|
||||
// Deconstruct the string literal to delegate the inference back to the
|
||||
// tuple type for correct handling of starred expressions. We could go
|
||||
// further and deconstruct to an array of `StringLiteral` with each
|
||||
// individual character, instead of just an array of `LiteralString`, but
|
||||
// there would be a cost and it's not clear that it's worth it.
|
||||
let value_ty = Type::tuple(
|
||||
self.db(),
|
||||
std::iter::repeat(Type::LiteralString)
|
||||
.take(string_literal_ty.python_len(self.db())),
|
||||
);
|
||||
self.unpack(target, value_ty, scope);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let value_ty = if value_ty.is_literal_string() {
|
||||
Type::LiteralString
|
||||
} else {
|
||||
value_ty
|
||||
.iterate(self.db())
|
||||
.unwrap_with_diagnostic(&self.context, AnyNodeRef::from(target))
|
||||
};
|
||||
for element in elts {
|
||||
self.unpack(element, value_ty, scope);
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Type`] elements inside the given [`TupleType`] taking into account that there
|
||||
/// can be a starred expression in the `elements`.
|
||||
fn tuple_ty_elements(
|
||||
&self,
|
||||
expr: &ast::Expr,
|
||||
targets: &[ast::Expr],
|
||||
tuple_ty: TupleType<'db>,
|
||||
) -> Cow<'_, [Type<'db>]> {
|
||||
// If there is a starred expression, it will consume all of the types at that location.
|
||||
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
|
||||
// Otherwise, the types will be unpacked 1-1 to the targets.
|
||||
return Cow::Borrowed(tuple_ty.elements(self.db()).as_ref());
|
||||
};
|
||||
|
||||
if tuple_ty.len(self.db()) >= targets.len() - 1 {
|
||||
// This branch is only taken when there are enough elements in the tuple type to
|
||||
// combine for the starred expression. So, the arithmetic and indexing operations are
|
||||
// safe to perform.
|
||||
let mut element_types = Vec::with_capacity(targets.len());
|
||||
|
||||
// Insert all the elements before the starred expression.
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[..starred_index],
|
||||
);
|
||||
|
||||
// The number of target expressions that are remaining after the starred expression.
|
||||
// For example, in `(a, *b, c, d) = ...`, the index of starred element `b` is 1 and the
|
||||
// remaining elements after that are 2.
|
||||
let remaining = targets.len() - (starred_index + 1);
|
||||
|
||||
// This index represents the position of the last element that belongs to the starred
|
||||
// expression, in an exclusive manner. For example, in `(a, *b, c) = (1, 2, 3, 4)`, the
|
||||
// starred expression `b` will consume the elements `Literal[2]` and `Literal[3]` and
|
||||
// the index value would be 3.
|
||||
let starred_end_index = tuple_ty.len(self.db()) - remaining;
|
||||
|
||||
// SAFETY: Safe because of the length check above.
|
||||
let _starred_element_types =
|
||||
&tuple_ty.elements(self.db())[starred_index..starred_end_index];
|
||||
// TODO: Combine the types into a list type. If the
|
||||
// starred_element_types is empty, then it should be `List[Any]`.
|
||||
// combine_types(starred_element_types);
|
||||
element_types.push(todo_type!("starred unpacking"));
|
||||
|
||||
// Insert the types remaining that aren't consumed by the starred expression.
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
&tuple_ty.elements(self.db())[starred_end_index..],
|
||||
);
|
||||
|
||||
Cow::Owned(element_types)
|
||||
} else {
|
||||
self.context.report_lint(
|
||||
&INVALID_ASSIGNMENT,
|
||||
expr.into(),
|
||||
format_args!(
|
||||
"Not enough values to unpack (expected {} or more, got {})",
|
||||
targets.len() - 1,
|
||||
tuple_ty.len(self.db())
|
||||
),
|
||||
);
|
||||
|
||||
let mut element_types = tuple_ty.elements(self.db()).to_vec();
|
||||
|
||||
// Subtract 1 to insert the starred expression type at the correct
|
||||
// index.
|
||||
element_types.resize(targets.len() - 1, Type::Unknown);
|
||||
// TODO: This should be `list[Unknown]`
|
||||
element_types.insert(starred_index, todo_type!("starred unpacking"));
|
||||
|
||||
Cow::Owned(element_types)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
|
||||
self.targets.shrink_to_fit();
|
||||
UnpackResult {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::{self as ast};
|
||||
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
|
||||
use crate::Db;
|
||||
@@ -43,7 +41,7 @@ pub(crate) struct Unpack<'db> {
|
||||
/// The ingredient representing the value expression of the unpacking. For example, in
|
||||
/// `(a, b) = (1, 2)`, the value expression is `(1, 2)`.
|
||||
#[no_eq]
|
||||
pub(crate) value: UnpackValue<'db>,
|
||||
pub(crate) value: Expression<'db>,
|
||||
|
||||
#[no_eq]
|
||||
count: countme::Count<Unpack<'static>>,
|
||||
@@ -54,48 +52,4 @@ impl<'db> Unpack<'db> {
|
||||
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.file_scope(db).to_scope_id(db, self.file(db))
|
||||
}
|
||||
|
||||
/// Returns the range of the unpack target expression.
|
||||
pub(crate) fn range(self, db: &'db dyn Db) -> TextRange {
|
||||
self.target(db).range()
|
||||
}
|
||||
}
|
||||
|
||||
/// The expression that is being unpacked.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum UnpackValue<'db> {
|
||||
/// An iterable expression like the one in a `for` loop or a comprehension.
|
||||
Iterable(Expression<'db>),
|
||||
/// An expression that is being assigned to a target.
|
||||
Assign(Expression<'db>),
|
||||
}
|
||||
|
||||
impl<'db> UnpackValue<'db> {
|
||||
/// Returns `true` if the value is an iterable expression.
|
||||
pub(crate) const fn is_iterable(self) -> bool {
|
||||
matches!(self, UnpackValue::Iterable(_))
|
||||
}
|
||||
|
||||
/// Returns the underlying [`Expression`] that is being unpacked.
|
||||
pub(crate) const fn expression(self) -> Expression<'db> {
|
||||
match self {
|
||||
UnpackValue::Assign(expr) | UnpackValue::Iterable(expr) => expr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`ScopedExpressionId`] of the underlying expression.
|
||||
pub(crate) fn scoped_expression_id(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
scope: ScopeId<'db>,
|
||||
) -> ScopedExpressionId {
|
||||
self.expression()
|
||||
.node_ref(db)
|
||||
.scoped_expression_id(db, scope)
|
||||
}
|
||||
|
||||
/// Returns the expression as an [`AnyNodeRef`].
|
||||
pub(crate) fn as_any_node_ref(self, db: &'db dyn Db) -> AnyNodeRef<'db> {
|
||||
self.expression().node_ref(db).node().into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,158 +1,3 @@
|
||||
//! # Visibility constraints
|
||||
//!
|
||||
//! During semantic index building, we collect visibility constraints for each binding and
|
||||
//! declaration. These constraints are then used during type-checking to determine the static
|
||||
//! visibility of a certain definition. This allows us to re-analyze control flow during type
|
||||
//! checking, potentially "hiding" some branches that we can statically determine to never be
|
||||
//! taken. Consider the following example first. We added implicit "unbound" definitions at the
|
||||
//! start of the scope. Note how visibility constraints can apply to bindings outside of the
|
||||
//! if-statement:
|
||||
//! ```py
|
||||
//! x = <unbound> # not a live binding for the use of x below, shadowed by `x = 1`
|
||||
//! y = <unbound> # visibility constraint: ~test
|
||||
//!
|
||||
//! x = 1 # visibility constraint: ~test
|
||||
//! if test:
|
||||
//! x = 2 # visibility constraint: test
|
||||
//!
|
||||
//! y = 2 # visibility constraint: test
|
||||
//!
|
||||
//! use(x)
|
||||
//! use(y)
|
||||
//! ```
|
||||
//! The static truthiness of the `test` condition can either be always-false, ambiguous, or
|
||||
//! always-true. Similarly, we have the same three options when evaluating a visibility constraint.
|
||||
//! This outcome determines the visibility of a definition: always-true means that the definition
|
||||
//! is definitely visible for a given use, always-false means that the definition is definitely
|
||||
//! not visible, and ambiguous means that we might see this definition or not. In the latter case,
|
||||
//! we need to consider both options during type inference and boundness analysis. For the example
|
||||
//! above, these are the possible type inference / boundness results for the uses of `x` and `y`:
|
||||
//!
|
||||
//! ```text
|
||||
//! | `test` truthiness | `~test` truthiness | type of `x` | boundness of `y` |
|
||||
//! |-------------------|--------------------|-----------------|------------------|
|
||||
//! | always false | always true | `Literal[1]` | unbound |
|
||||
//! | ambiguous | ambiguous | `Literal[1, 2]` | possibly unbound |
|
||||
//! | always true | always false | `Literal[2]` | bound |
|
||||
//! ```
|
||||
//!
|
||||
//! ### Sequential constraints (ternary AND)
|
||||
//!
|
||||
//! As we have seen above, visibility constraints can apply outside of a control flow element.
|
||||
//! So we need to consider the possibility that multiple constraints apply to the same binding.
|
||||
//! Here, we consider what happens if multiple `if`-statements lead to a sequence of constraints.
|
||||
//! Consider the following example:
|
||||
//! ```py
|
||||
//! x = 0
|
||||
//!
|
||||
//! if test1:
|
||||
//! x = 1
|
||||
//!
|
||||
//! if test2:
|
||||
//! x = 2
|
||||
//! ```
|
||||
//! The binding `x = 2` is easy to analyze. Its visibility corresponds to the truthiness of `test2`.
|
||||
//! For the `x = 1` binding, things are a bit more interesting. It is always visible if `test1` is
|
||||
//! always-true *and* `test2` is always-false. It is never visible if `test1` is always-false *or*
|
||||
//! `test2` is always-true. And it is ambiguous otherwise. This corresponds to a ternary *test1 AND
|
||||
//! ~test2* operation in three-valued Kleene logic [Kleene]:
|
||||
//!
|
||||
//! ```text
|
||||
//! | AND | always-false | ambiguous | always-true |
|
||||
//! |--------------|--------------|--------------|--------------|
|
||||
//! | always false | always-false | always-false | always-false |
|
||||
//! | ambiguous | always-false | ambiguous | ambiguous |
|
||||
//! | always true | always-false | ambiguous | always-true |
|
||||
//! ```
|
||||
//!
|
||||
//! The `x = 0` binding can be handled similarly, with the difference that both `test1` and `test2`
|
||||
//! are negated:
|
||||
//! ```py
|
||||
//! x = 0 # ~test1 AND ~test2
|
||||
//!
|
||||
//! if test1:
|
||||
//! x = 1 # test1 AND ~test2
|
||||
//!
|
||||
//! if test2:
|
||||
//! x = 2 # test2
|
||||
//! ```
|
||||
//!
|
||||
//! ### Merged constraints (ternary OR)
|
||||
//!
|
||||
//! Finally, we consider what happens in "parallel" control flow. Consider the following example
|
||||
//! where we have omitted the test condition for the outer `if` for clarity:
|
||||
//! ```py
|
||||
//! x = 0
|
||||
//!
|
||||
//! if <…>:
|
||||
//! if test1:
|
||||
//! x = 1
|
||||
//! else:
|
||||
//! if test2:
|
||||
//! x = 2
|
||||
//!
|
||||
//! use(x)
|
||||
//! ```
|
||||
//! At the usage of `x`, i.e. after control flow has been merged again, the visibility of the `x =
|
||||
//! 0` binding behaves as follows: the binding is always visible if `test1` is always-false *or*
|
||||
//! `test2` is always-false; and it is never visible if `test1` is always-true *and* `test2` is
|
||||
//! always-true. This corresponds to a ternary *OR* operation in Kleene logic:
|
||||
//!
|
||||
//! ```text
|
||||
//! | OR | always-false | ambiguous | always-true |
|
||||
//! |--------------|--------------|--------------|--------------|
|
||||
//! | always false | always-false | ambiguous | always-true |
|
||||
//! | ambiguous | ambiguous | ambiguous | always-true |
|
||||
//! | always true | always-true | always-true | always-true |
|
||||
//! ```
|
||||
//!
|
||||
//! Using this, we can annotate the visibility constraints for the example above:
|
||||
//! ```py
|
||||
//! x = 0 # ~test1 OR ~test2
|
||||
//!
|
||||
//! if <…>:
|
||||
//! if test1:
|
||||
//! x = 1 # test1
|
||||
//! else:
|
||||
//! if test2:
|
||||
//! x = 2 # test2
|
||||
//!
|
||||
//! use(x)
|
||||
//! ```
|
||||
//!
|
||||
//! ### Explicit ambiguity
|
||||
//!
|
||||
//! In some cases, we explicitly add a `VisibilityConstraint::Ambiguous` constraint to all bindings
|
||||
//! in a certain control flow path. We do this when branching on something that we can not (or
|
||||
//! intentionally do not want to) analyze statically. `for` loops are one example:
|
||||
//! ```py
|
||||
//! x = <unbound>
|
||||
//!
|
||||
//! for _ in range(2):
|
||||
//! x = 1
|
||||
//! ```
|
||||
//! Here, we report an ambiguous visibility constraint before branching off. If we don't do this,
|
||||
//! the `x = <unbound>` binding would be considered unconditionally visible in the no-loop case.
|
||||
//! And since the other branch does not have the live `x = <unbound>` binding, we would incorrectly
|
||||
//! create a state where the `x = <unbound>` binding is always visible.
|
||||
//!
|
||||
//!
|
||||
//! ### Properties
|
||||
//!
|
||||
//! The ternary `AND` and `OR` operations have the property that `~a OR ~b = ~(a AND b)`. This
|
||||
//! means we could, in principle, get rid of either of these two to simplify the representation.
|
||||
//!
|
||||
//! However, we already apply negative constraints `~test1` and `~test2` to the "branches not
|
||||
//! taken" in the example above. This means that the tree-representation `~test1 OR ~test2` is much
|
||||
//! cheaper/shallower than basically creating `~(~(~test1) AND ~(~test2))`. Similarly, if we wanted
|
||||
//! to get rid of `AND`, we would also have to create additional nodes. So for performance reasons,
|
||||
//! there is a small "duplication" in the code between those two constraint types.
|
||||
//!
|
||||
//! [Kleene]: <https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics>
|
||||
|
||||
use ruff_index::IndexVec;
|
||||
|
||||
use crate::semantic_index::ScopedVisibilityConstraintId;
|
||||
use crate::semantic_index::{
|
||||
ast_ids::HasScopedExpressionId,
|
||||
constraint::{Constraint, ConstraintNode, PatternConstraintKind},
|
||||
@@ -160,179 +5,157 @@ use crate::semantic_index::{
|
||||
use crate::types::{infer_expression_types, Truthiness};
|
||||
use crate::Db;
|
||||
|
||||
/// The maximum depth of recursion when evaluating visibility constraints.
|
||||
///
|
||||
/// This is a performance optimization that prevents us from descending deeply in case of
|
||||
/// pathological cases. The actual limit here has been derived from performance testing on
|
||||
/// the `black` codebase. When increasing the limit beyond 32, we see a 5x runtime increase
|
||||
/// resulting from a few files with a lot of boolean expressions and `if`-statements.
|
||||
const MAX_RECURSION_DEPTH: usize = 24;
|
||||
const MAX_RECURSION_DEPTH: usize = 10;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum VisibilityConstraint<'db> {
|
||||
#[salsa::interned]
|
||||
pub(crate) struct VisibilityConstraint<'db> {
|
||||
kind: VisibilityConstraintKind<'db>,
|
||||
}
|
||||
|
||||
impl<'db> VisibilityConstraint<'db> {
|
||||
pub(crate) fn always_true(db: &'db dyn Db) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::AlwaysTrue)
|
||||
}
|
||||
|
||||
pub(crate) fn ambiguous(db: &'db dyn Db) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::Ambiguous)
|
||||
}
|
||||
|
||||
pub(crate) fn visible_if(db: &'db dyn Db, constraint: Constraint<'db>) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::VisibleIf(constraint))
|
||||
}
|
||||
|
||||
pub(crate) fn visible_if_not(db: &'db dyn Db, constraint: VisibilityConstraint<'db>) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::VisibleIfNot(constraint))
|
||||
}
|
||||
|
||||
pub(crate) fn kleene_and(
|
||||
db: &'db dyn Db,
|
||||
lhs: VisibilityConstraint<'db>,
|
||||
rhs: VisibilityConstraint<'db>,
|
||||
) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::KleeneAnd(lhs, rhs))
|
||||
}
|
||||
|
||||
pub(crate) fn kleene_or(
|
||||
db: &'db dyn Db,
|
||||
lhs: VisibilityConstraint<'db>,
|
||||
rhs: VisibilityConstraint<'db>,
|
||||
) -> Self {
|
||||
Self::new(db, VisibilityConstraintKind::KleeneOr(lhs, rhs))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum VisibilityConstraintKind<'db> {
|
||||
AlwaysTrue,
|
||||
Ambiguous,
|
||||
VisibleIf(Constraint<'db>),
|
||||
VisibleIfNot(ScopedVisibilityConstraintId),
|
||||
KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
|
||||
KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
|
||||
VisibleIfNot(VisibilityConstraint<'db>),
|
||||
KleeneAnd(VisibilityConstraint<'db>, VisibilityConstraint<'db>),
|
||||
KleeneOr(VisibilityConstraint<'db>, VisibilityConstraint<'db>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct VisibilityConstraints<'db> {
|
||||
constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraint<'db>>,
|
||||
/// Analyze the statically known visibility for a given visibility constraint.
|
||||
pub(crate) fn evaluate<'db>(db: &'db dyn Db, constraint: VisibilityConstraint<'db>) -> Truthiness {
|
||||
evaluate_impl(db, constraint, MAX_RECURSION_DEPTH)
|
||||
}
|
||||
|
||||
impl Default for VisibilityConstraints<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]),
|
||||
fn evaluate_impl<'db>(
|
||||
db: &'db dyn Db,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
max_depth: usize,
|
||||
) -> Truthiness {
|
||||
if max_depth == 0 {
|
||||
return Truthiness::Ambiguous;
|
||||
}
|
||||
|
||||
match constraint.kind(db) {
|
||||
VisibilityConstraintKind::AlwaysTrue => Truthiness::AlwaysTrue,
|
||||
VisibilityConstraintKind::Ambiguous => Truthiness::Ambiguous,
|
||||
VisibilityConstraintKind::VisibleIf(constraint) => analyze_single(db, &constraint),
|
||||
VisibilityConstraintKind::VisibleIfNot(negated) => {
|
||||
evaluate_impl(db, negated, max_depth - 1).negate()
|
||||
}
|
||||
VisibilityConstraintKind::KleeneAnd(lhs, rhs) => {
|
||||
let lhs = evaluate_impl(db, lhs, max_depth - 1);
|
||||
|
||||
if lhs == Truthiness::AlwaysFalse {
|
||||
return Truthiness::AlwaysFalse;
|
||||
}
|
||||
|
||||
let rhs = evaluate_impl(db, rhs, max_depth - 1);
|
||||
|
||||
if rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
VisibilityConstraintKind::KleeneOr(lhs, rhs) => {
|
||||
let lhs = evaluate_impl(db, lhs, max_depth - 1);
|
||||
|
||||
if lhs == Truthiness::AlwaysTrue {
|
||||
return Truthiness::AlwaysTrue;
|
||||
}
|
||||
|
||||
let rhs = evaluate_impl(db, rhs, max_depth - 1);
|
||||
|
||||
if rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> VisibilityConstraints<'db> {
|
||||
pub(crate) fn add(
|
||||
&mut self,
|
||||
constraint: VisibilityConstraint<'db>,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
self.constraints.push(constraint)
|
||||
}
|
||||
fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness {
|
||||
match constraint.node {
|
||||
ConstraintNode::Expression(test_expr) => {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
let ty =
|
||||
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
pub(crate) fn add_or_constraint(
|
||||
&mut self,
|
||||
a: ScopedVisibilityConstraintId,
|
||||
b: ScopedVisibilityConstraintId,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
match (&self.constraints[a], &self.constraints[b]) {
|
||||
(_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => {
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE
|
||||
}
|
||||
(VisibilityConstraint::VisibleIfNot(id), _) if *id == b => {
|
||||
ScopedVisibilityConstraintId::ALWAYS_TRUE
|
||||
}
|
||||
_ => self.add(VisibilityConstraint::KleeneOr(a, b)),
|
||||
ty.bool(db).negate_if(!constraint.is_positive)
|
||||
}
|
||||
}
|
||||
ConstraintNode::Pattern(inner) => match inner.kind(db) {
|
||||
PatternConstraintKind::Value(value, guard) => {
|
||||
let subject_expression = inner.subject(db);
|
||||
let inference = infer_expression_types(db, *subject_expression);
|
||||
let scope = subject_expression.scope(db);
|
||||
let subject_ty = inference.expression_ty(
|
||||
subject_expression
|
||||
.node_ref(db)
|
||||
.scoped_expression_id(db, scope),
|
||||
);
|
||||
|
||||
pub(crate) fn add_and_constraint(
|
||||
&mut self,
|
||||
a: ScopedVisibilityConstraintId,
|
||||
b: ScopedVisibilityConstraintId,
|
||||
) -> ScopedVisibilityConstraintId {
|
||||
if a == ScopedVisibilityConstraintId::ALWAYS_TRUE {
|
||||
b
|
||||
} else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE {
|
||||
a
|
||||
} else {
|
||||
self.add(VisibilityConstraint::KleeneAnd(a, b))
|
||||
}
|
||||
}
|
||||
let inference = infer_expression_types(db, *value);
|
||||
let scope = value.scope(db);
|
||||
let value_ty =
|
||||
inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
/// Analyze the statically known visibility for a given visibility constraint.
|
||||
pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness {
|
||||
self.evaluate_impl(db, id, MAX_RECURSION_DEPTH)
|
||||
}
|
||||
if subject_ty.is_single_valued(db) {
|
||||
let truthiness = Truthiness::from(subject_ty.is_equivalent_to(db, value_ty));
|
||||
|
||||
fn evaluate_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
id: ScopedVisibilityConstraintId,
|
||||
max_depth: usize,
|
||||
) -> Truthiness {
|
||||
if max_depth == 0 {
|
||||
return Truthiness::Ambiguous;
|
||||
}
|
||||
|
||||
let visibility_constraint = &self.constraints[id];
|
||||
match visibility_constraint {
|
||||
VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue,
|
||||
VisibilityConstraint::Ambiguous => Truthiness::Ambiguous,
|
||||
VisibilityConstraint::VisibleIf(constraint) => Self::analyze_single(db, constraint),
|
||||
VisibilityConstraint::VisibleIfNot(negated) => {
|
||||
self.evaluate_impl(db, *negated, max_depth - 1).negate()
|
||||
}
|
||||
VisibilityConstraint::KleeneAnd(lhs, rhs) => {
|
||||
let lhs = self.evaluate_impl(db, *lhs, max_depth - 1);
|
||||
|
||||
if lhs == Truthiness::AlwaysFalse {
|
||||
return Truthiness::AlwaysFalse;
|
||||
}
|
||||
|
||||
let rhs = self.evaluate_impl(db, *rhs, max_depth - 1);
|
||||
|
||||
if rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => {
|
||||
let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1);
|
||||
|
||||
if lhs == Truthiness::AlwaysTrue {
|
||||
return Truthiness::AlwaysTrue;
|
||||
}
|
||||
|
||||
let rhs = self.evaluate_impl(db, *rhs_id, max_depth - 1);
|
||||
|
||||
if rhs == Truthiness::AlwaysTrue {
|
||||
Truthiness::AlwaysTrue
|
||||
} else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse {
|
||||
Truthiness::AlwaysFalse
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness {
|
||||
match constraint.node {
|
||||
ConstraintNode::Expression(test_expr) => {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
let ty =
|
||||
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
ty.bool(db).negate_if(!constraint.is_positive)
|
||||
}
|
||||
ConstraintNode::Pattern(inner) => match inner.kind(db) {
|
||||
PatternConstraintKind::Value(value, guard) => {
|
||||
let subject_expression = inner.subject(db);
|
||||
let inference = infer_expression_types(db, *subject_expression);
|
||||
let scope = subject_expression.scope(db);
|
||||
let subject_ty = inference.expression_ty(
|
||||
subject_expression
|
||||
.node_ref(db)
|
||||
.scoped_expression_id(db, scope),
|
||||
);
|
||||
|
||||
let inference = infer_expression_types(db, *value);
|
||||
let scope = value.scope(db);
|
||||
let value_ty =
|
||||
inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope));
|
||||
|
||||
if subject_ty.is_single_valued(db) {
|
||||
let truthiness =
|
||||
Truthiness::from(subject_ty.is_equivalent_to(db, value_ty));
|
||||
|
||||
if truthiness.is_always_true() && guard.is_some() {
|
||||
// Fall back to ambiguous, the guard might change the result.
|
||||
Truthiness::Ambiguous
|
||||
} else {
|
||||
truthiness
|
||||
}
|
||||
} else {
|
||||
if truthiness.is_always_true() && guard.is_some() {
|
||||
// Fall back to ambiguous, the guard might change the result.
|
||||
Truthiness::Ambiguous
|
||||
} else {
|
||||
truthiness
|
||||
}
|
||||
}
|
||||
PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => {
|
||||
} else {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => {
|
||||
Truthiness::Ambiguous
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::lint::RuleSelection;
|
||||
use red_knot_python_semantic::{
|
||||
default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonPlatform,
|
||||
PythonVersion, SearchPathSettings,
|
||||
@@ -21,7 +21,7 @@ pub(crate) struct Db {
|
||||
|
||||
impl Db {
|
||||
pub(crate) fn setup(workspace_root: SystemPathBuf) -> Self {
|
||||
let rule_selection = RuleSelection::from_registry(default_lint_registry());
|
||||
let rule_selection = RuleSelection::from_registry(&default_lint_registry());
|
||||
|
||||
let db = Self {
|
||||
workspace_root,
|
||||
@@ -97,10 +97,6 @@ impl SemanticDb for Db {
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -100,11 +100,7 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
|
||||
|
||||
let test_files: Vec<_> = test
|
||||
.files()
|
||||
.filter_map(|embedded| {
|
||||
if embedded.lang == "ignore" {
|
||||
return None;
|
||||
}
|
||||
|
||||
.map(|embedded| {
|
||||
assert!(
|
||||
matches!(embedded.lang, "py" | "pyi"),
|
||||
"Non-Python files not supported yet."
|
||||
@@ -113,10 +109,10 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
|
||||
db.write_file(&full_path, embedded.code).unwrap();
|
||||
let file = system_path_to_file(db, full_path).unwrap();
|
||||
|
||||
Some(TestFile {
|
||||
TestFile {
|
||||
file,
|
||||
backtick_offset: embedded.md_offset,
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::workspace::{check_file, Workspace, WorkspaceMetadata};
|
||||
use crate::DEFAULT_LINT_REGISTRY;
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::lint::RuleSelection;
|
||||
use red_knot_python_semantic::{Db as SemanticDb, Program};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, Files};
|
||||
@@ -116,10 +116,6 @@ impl SemanticDb for RootDatabase {
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
&DEFAULT_LINT_REGISTRY
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
@@ -166,7 +162,7 @@ pub(crate) mod tests {
|
||||
|
||||
use salsa::Event;
|
||||
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::lint::RuleSelection;
|
||||
use red_knot_python_semantic::Db as SemanticDb;
|
||||
use ruff_db::files::Files;
|
||||
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
|
||||
@@ -272,10 +268,6 @@ pub(crate) mod tests {
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
&DEFAULT_LINT_REGISTRY
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -182,11 +182,11 @@ impl<'db> PullTypesVisitor<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_target(&mut self, target: &Expr) {
|
||||
fn visit_assign_target(&mut self, target: &Expr) {
|
||||
match target {
|
||||
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
for element in elts {
|
||||
self.visit_target(element);
|
||||
self.visit_assign_target(element);
|
||||
}
|
||||
}
|
||||
_ => self.visit_expr(target),
|
||||
@@ -205,16 +205,8 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
}
|
||||
Stmt::Assign(assign) => {
|
||||
for target in &assign.targets {
|
||||
self.visit_target(target);
|
||||
self.visit_assign_target(target);
|
||||
}
|
||||
self.visit_expr(&assign.value);
|
||||
return;
|
||||
}
|
||||
Stmt::For(for_stmt) => {
|
||||
self.visit_target(&for_stmt.target);
|
||||
self.visit_expr(&for_stmt.iter);
|
||||
self.visit_body(&for_stmt.body);
|
||||
self.visit_body(&for_stmt.orelse);
|
||||
return;
|
||||
}
|
||||
Stmt::AnnAssign(_)
|
||||
@@ -222,6 +214,7 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
| Stmt::Delete(_)
|
||||
| Stmt::AugAssign(_)
|
||||
| Stmt::TypeAlias(_)
|
||||
| Stmt::For(_)
|
||||
| Stmt::While(_)
|
||||
| Stmt::If(_)
|
||||
| Stmt::With(_)
|
||||
|
||||
@@ -799,7 +799,7 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
// ```
|
||||
Rule::MissingTrailingComma,
|
||||
// The formatter always removes blank lines before the docstring.
|
||||
Rule::IncorrectBlankLineBeforeClass,
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
] {
|
||||
if setting.linter.rules.enabled(rule) {
|
||||
incompatible_rules.insert(rule);
|
||||
@@ -830,7 +830,7 @@ pub(super) fn warn_incompatible_formatter_settings(resolver: &Resolver) {
|
||||
}
|
||||
|
||||
// Validate all rules that rely on tab styles.
|
||||
if setting.linter.rules.enabled(Rule::DocstringTabIndentation)
|
||||
if setting.linter.rules.enabled(Rule::IndentWithSpaces)
|
||||
&& setting.formatter.indent_style.is_tab()
|
||||
{
|
||||
warn_user_once!("The `format.indent-style=\"tab\"` option is incompatible with `D206`, with requires space-based indentation. We recommend disabling these rules when using the formatter, which enforces a consistent indentation style. Alternatively, set the `format.indent-style` option to `\"space\"`.");
|
||||
|
||||
@@ -1112,7 +1112,7 @@ def say_hy(name: str):
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
|
||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding them to the `ignore` configuration.
|
||||
");
|
||||
|
||||
@@ -1037,7 +1037,7 @@ fn preview_enabled_all() {
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
|
||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
");
|
||||
}
|
||||
|
||||
@@ -375,7 +375,6 @@ linter.pylint.max_locals = 15
|
||||
linter.pyupgrade.keep_runtime_typing = false
|
||||
linter.ruff.parenthesize_tuple_in_subscript = false
|
||||
linter.ruff.extend_markup_names = []
|
||||
linter.ruff.allowed_markup_calls = []
|
||||
|
||||
# Formatter Settings
|
||||
formatter.exclude = []
|
||||
|
||||
@@ -43,7 +43,6 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
||||
"warning[lint:unused-ignore-comment] /src/tomllib/_parser.py:682:31 Unused blanket `type: ignore` directive"
|
||||
];
|
||||
|
||||
fn get_test_file(name: &str) -> TestFile {
|
||||
|
||||
@@ -2,10 +2,9 @@ use anyhow::Result;
|
||||
use std::sync::Arc;
|
||||
use zip::CompressionMethod;
|
||||
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::lint::RuleSelection;
|
||||
use red_knot_python_semantic::{
|
||||
default_lint_registry, Db, Program, ProgramSettings, PythonPlatform, PythonVersion,
|
||||
SearchPathSettings,
|
||||
Db, Program, ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings,
|
||||
};
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{OsSystem, System, SystemPathBuf};
|
||||
@@ -94,10 +93,6 @@ impl Db for ModuleDb {
|
||||
fn rule_selection(&self) -> &RuleSelection {
|
||||
&self.rule_selection
|
||||
}
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
from airflow.plugins_manager import AirflowPlugin
|
||||
|
||||
|
||||
class AirflowTestPlugin(AirflowPlugin):
|
||||
name = "test_plugin"
|
||||
# --- Invalid extensions start
|
||||
operators = [PluginOperator]
|
||||
sensors = [PluginSensorOperator]
|
||||
hooks = [PluginHook]
|
||||
executors = [PluginExecutor]
|
||||
# --- Invalid extensions end
|
||||
macros = [plugin_macro]
|
||||
flask_blueprints = [bp]
|
||||
appbuilder_views = [v_appbuilder_package]
|
||||
appbuilder_menu_items = [appbuilder_mitem, appbuilder_mitem_toplevel]
|
||||
global_operator_extra_links = [
|
||||
AirflowLink(),
|
||||
GithubLink(),
|
||||
]
|
||||
operator_extra_links = [
|
||||
GoogleLink(),
|
||||
AirflowLink2(),
|
||||
CustomOpLink(),
|
||||
CustomBaseIndexOpLink(1),
|
||||
]
|
||||
timetables = [CustomCronDataIntervalTimetable]
|
||||
listeners = [empty_listener, ClassBasedListener()]
|
||||
ti_deps = [CustomTestTriggerRule()]
|
||||
priority_weight_strategies = [CustomPriorityWeightStrategy]
|
||||
@@ -1,18 +1,15 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from airflow import DAG, dag
|
||||
from airflow.operators.datetime import BranchDateTimeOperator
|
||||
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
|
||||
from airflow.providers.amazon.aws.log.s3_task_handler import S3TaskHandler
|
||||
from airflow.providers.apache.hdfs.log.hdfs_task_handler import HdfsTaskHandler
|
||||
from airflow.providers.elasticsearch.log.es_task_handler import ElasticsearchTaskHandler
|
||||
from airflow.providers.fab.auth_manager.fab_auth_manager import FabAuthManager
|
||||
from airflow.providers.google.cloud.log.gcs_task_handler import GCSTaskHandler
|
||||
from airflow.providers.standard.operators import datetime, trigger_dagrun
|
||||
from airflow.providers.standard.sensors import weekday
|
||||
from airflow.sensors.weekday import BranchDayOfWeekOperator, DayOfWeekSensor
|
||||
from airflow.timetables.simple import NullTimetable
|
||||
|
||||
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
|
||||
from airflow.providers.standard.operators import trigger_dagrun
|
||||
|
||||
from airflow.operators.datetime import BranchDateTimeOperator
|
||||
from airflow.providers.standard.operators import datetime
|
||||
|
||||
from airflow.sensors.weekday import DayOfWeekSensor, BranchDayOfWeekOperator
|
||||
from airflow.providers.standard.sensors import weekday
|
||||
|
||||
DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
|
||||
DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
@@ -57,12 +54,10 @@ def decorator_deprecated_operator_args():
|
||||
)
|
||||
|
||||
branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||
task_id="branch_dt_op", use_task_execution_day=True
|
||||
)
|
||||
branch_dt_op2 = BranchDateTimeOperator(
|
||||
task_id="branch_dt_op2",
|
||||
use_task_execution_day=True,
|
||||
sla=timedelta(seconds=10),
|
||||
task_id="branch_dt_op2", use_task_execution_day=True
|
||||
)
|
||||
|
||||
dof_task_sensor = weekday.DayOfWeekSensor(
|
||||
@@ -81,12 +76,3 @@ def decorator_deprecated_operator_args():
|
||||
branch_dt_op >> branch_dt_op2
|
||||
dof_task_sensor >> dof_task_sensor2
|
||||
bdow_op >> bdow_op2
|
||||
|
||||
|
||||
# deprecated filename_template arugment in FileTaskHandler
|
||||
S3TaskHandler(filename_template="/tmp/test")
|
||||
HdfsTaskHandler(filename_template="/tmp/test")
|
||||
ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||
GCSTaskHandler(filename_template="/tmp/test")
|
||||
|
||||
FabAuthManager(None)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
from airflow.datasets.manager import DatasetManager
|
||||
from airflow.lineage.hook import DatasetLineageInfo, HookLineageCollector
|
||||
from airflow.providers.amazon.auth_manager.aws_auth_manager import AwsAuthManager
|
||||
from airflow.providers.apache.beam.hooks import BeamHook, NotAir302HookError
|
||||
from airflow.providers.google.cloud.secrets.secret_manager import (
|
||||
CloudSecretManagerBackend,
|
||||
)
|
||||
from airflow.providers.hashicorp.secrets.vault import NotAir302SecretError, VaultBackend
|
||||
from airflow.providers_manager import ProvidersManager
|
||||
from airflow.secrets.base_secrets import BaseSecretsBackend
|
||||
|
||||
dm = DatasetManager()
|
||||
dm.register_dataset_change()
|
||||
dm.create_datasets()
|
||||
dm.notify_dataset_created()
|
||||
dm.notify_dataset_changed()
|
||||
dm.notify_dataset_alias_created()
|
||||
|
||||
hlc = HookLineageCollector()
|
||||
hlc.create_dataset()
|
||||
hlc.add_input_dataset()
|
||||
hlc.add_output_dataset()
|
||||
hlc.collected_datasets()
|
||||
|
||||
aam = AwsAuthManager()
|
||||
aam.is_authorized_dataset()
|
||||
|
||||
pm = ProvidersManager()
|
||||
pm.initialize_providers_asset_uri_resources()
|
||||
pm.dataset_factories
|
||||
|
||||
base_secret_backend = BaseSecretsBackend()
|
||||
base_secret_backend.get_conn_uri()
|
||||
base_secret_backend.get_connections()
|
||||
|
||||
csm_backend = CloudSecretManagerBackend()
|
||||
csm_backend.get_conn_uri()
|
||||
csm_backend.get_connections()
|
||||
|
||||
vault_backend = VaultBackend()
|
||||
vault_backend.get_conn_uri()
|
||||
vault_backend.get_connections()
|
||||
|
||||
not_an_error = NotAir302SecretError()
|
||||
not_an_error.get_conn_uri()
|
||||
|
||||
beam_hook = BeamHook()
|
||||
beam_hook.get_conn_uri()
|
||||
|
||||
not_an_error = NotAir302HookError()
|
||||
not_an_error.get_conn_uri()
|
||||
|
||||
provider_manager = ProvidersManager()
|
||||
provider_manager.dataset_factories
|
||||
provider_manager.dataset_uri_handlers
|
||||
provider_manager.dataset_to_openlineage_converters
|
||||
|
||||
dl_info = DatasetLineageInfo()
|
||||
dl_info.dataset
|
||||
@@ -36,7 +36,6 @@ from airflow.datasets.manager import (
|
||||
dataset_manager,
|
||||
resolve_dataset_manager,
|
||||
)
|
||||
from airflow.hooks.base_hook import BaseHook
|
||||
from airflow.lineage.hook import DatasetLineageInfo
|
||||
from airflow.listeners.spec.dataset import on_dataset_changed, on_dataset_created
|
||||
from airflow.metrics.validators import AllowListValidator, BlockListValidator
|
||||
@@ -97,10 +96,6 @@ from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_
|
||||
PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
DatasetFromRoot
|
||||
|
||||
dataset_from_root = DatasetFromRoot()
|
||||
dataset_from_root.iter_datasets()
|
||||
dataset_from_root.iter_dataset_aliases()
|
||||
|
||||
# airflow.api_connexion.security
|
||||
requires_access, requires_access_dataset
|
||||
|
||||
@@ -124,24 +119,9 @@ DatasetAny
|
||||
expand_alias_to_datasets
|
||||
Metadata
|
||||
|
||||
dataset_to_test_method_call = Dataset()
|
||||
dataset_to_test_method_call.iter_datasets()
|
||||
dataset_to_test_method_call.iter_dataset_aliases()
|
||||
|
||||
alias_to_test_method_call = DatasetAlias()
|
||||
alias_to_test_method_call.iter_datasets()
|
||||
alias_to_test_method_call.iter_dataset_aliases()
|
||||
|
||||
any_to_test_method_call = DatasetAny()
|
||||
any_to_test_method_call.iter_datasets()
|
||||
any_to_test_method_call.iter_dataset_aliases()
|
||||
|
||||
# airflow.datasets.manager
|
||||
DatasetManager, dataset_manager, resolve_dataset_manager
|
||||
|
||||
# airflow.hooks
|
||||
BaseHook()
|
||||
|
||||
# airflow.lineage.hook
|
||||
DatasetLineageInfo
|
||||
|
||||
@@ -273,3 +253,34 @@ has_access_dataset
|
||||
|
||||
# airflow.www.utils
|
||||
get_sensitive_variables_fields, should_hide_value_for_key
|
||||
|
||||
from airflow.datasets.manager import DatasetManager
|
||||
|
||||
dm = DatasetManager()
|
||||
dm.register_dataset_change()
|
||||
dm.create_datasets()
|
||||
dm.notify_dataset_created()
|
||||
dm.notify_dataset_changed()
|
||||
dm.notify_dataset_alias_created()
|
||||
|
||||
|
||||
from airflow.lineage.hook import HookLineageCollector
|
||||
|
||||
hlc = HookLineageCollector()
|
||||
hlc.create_dataset()
|
||||
hlc.add_input_dataset()
|
||||
hlc.add_output_dataset()
|
||||
hlc.collected_datasets()
|
||||
|
||||
|
||||
from airflow.providers.amazon.auth_manager.aws_auth_manager import AwsAuthManager
|
||||
|
||||
aam = AwsAuthManager()
|
||||
aam.is_authorized_dataset()
|
||||
|
||||
|
||||
from airflow.providers_manager import ProvidersManager
|
||||
|
||||
pm = ProvidersManager()
|
||||
pm.initialize_providers_asset_uri_resources()
|
||||
pm.dataset_factories
|
||||
|
||||
@@ -5,39 +5,12 @@ from airflow.auth.managers.fab.api.auth.backend import (
|
||||
)
|
||||
from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager
|
||||
from airflow.auth.managers.fab.security_manager import override as fab_override
|
||||
from airflow.config_templates.default_celery import DEFAULT_CELERY_CONFIG
|
||||
from airflow.executors.celery_executor import app
|
||||
from airflow.executors.kubernetes_executor_types import (
|
||||
ALL_NAMESPACES,
|
||||
POD_EXECUTOR_DONE_KEY,
|
||||
)
|
||||
from airflow.hooks.dbapi import ConnectorProtocol, DbApiHook
|
||||
from airflow.hooks.hive_hooks import HIVE_QUEUE_PRIORITIES
|
||||
from airflow.macros.hive import closest_ds_partition, max_partition
|
||||
from airflow.www.security import FabAirflowSecurityManagerOverride
|
||||
|
||||
# apache-airflow-providers-fab
|
||||
basic_auth, kerberos_auth
|
||||
auth_current_user
|
||||
backend_kerberos_auth
|
||||
fab_override
|
||||
|
||||
FabAuthManager()
|
||||
FabAirflowSecurityManagerOverride()
|
||||
|
||||
# apache-airflow-providers-celery
|
||||
DEFAULT_CELERY_CONFIG
|
||||
app
|
||||
|
||||
# apache-airflow-providers-common-sql
|
||||
ConnectorProtocol()
|
||||
DbApiHook()
|
||||
|
||||
# apache-airflow-providers-cncf-kubernetes
|
||||
ALL_NAMESPACES
|
||||
POD_EXECUTOR_DONE_KEY
|
||||
|
||||
# apache-airflow-providers-apache-hive
|
||||
HIVE_QUEUE_PRIORITIES
|
||||
closest_ds_partition()
|
||||
max_partition()
|
||||
FabAuthManager
|
||||
FabAirflowSecurityManagerOverride
|
||||
|
||||
@@ -108,18 +108,3 @@ app = None
|
||||
@app.post("/items/", response_model=Item)
|
||||
async def create_item(item: Item) -> Item:
|
||||
return item
|
||||
|
||||
|
||||
# Routes might be defined inside functions
|
||||
|
||||
|
||||
def setup_app(app_arg: FastAPI, non_app: str) -> None:
|
||||
# Error
|
||||
@app_arg.get("/", response_model=str)
|
||||
async def get_root() -> str:
|
||||
return "Hello World!"
|
||||
|
||||
# Ok
|
||||
@non_app.get("/", response_model=str)
|
||||
async def get_root() -> str:
|
||||
return "Hello World!"
|
||||
|
||||
@@ -57,9 +57,6 @@ def get_users(
|
||||
pass
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
|
||||
pass
|
||||
|
||||
# Non fixable errors
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{k: v for k, v in y}
|
||||
{k: v for k, v in d.items()}
|
||||
[(k, v) for k, v in d.items()]
|
||||
[(k, v) for [k, v] in d.items()]
|
||||
{k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
[i for i, in z]
|
||||
@@ -23,7 +22,3 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
|
||||
zz = [[1], [2], [3]]
|
||||
[(i,) for (i,) in zz] # != list(zz)
|
||||
{(i,) for (i,) in zz} # != set(zz)
|
||||
|
||||
@@ -14,7 +14,6 @@ JediOrder.YODA == age # SIM300
|
||||
0 < (number - 100) # SIM300
|
||||
B<A[0][0]or B
|
||||
B or(B)<A[0][0]
|
||||
{"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
|
||||
# Errors in preview
|
||||
['upper'] == UPPER_LIST
|
||||
@@ -40,7 +39,4 @@ age == JediOrder.YODA
|
||||
(number - 100) > 0
|
||||
SECONDS_IN_DAY == 60 * 60 * 24 # Error in 0.1.8
|
||||
SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60) # Error in 0.1.8
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/14761
|
||||
{"": print(1)} == print(2)
|
||||
{0: 1, **print(2)} == print(4)
|
||||
{"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
|
||||
@@ -1,70 +1,15 @@
|
||||
from pathlib import Path, PurePath
|
||||
from pathlib import Path as pth
|
||||
|
||||
|
||||
# match
|
||||
_ = Path(".")
|
||||
_ = pth(".")
|
||||
_ = PurePath(".")
|
||||
_ = Path("")
|
||||
|
||||
Path('', )
|
||||
|
||||
Path(
|
||||
'',
|
||||
)
|
||||
|
||||
Path( # Comment before argument
|
||||
'',
|
||||
)
|
||||
|
||||
Path(
|
||||
'', # EOL comment
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment in the middle of implicitly concatenated string
|
||||
".",
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment before comma
|
||||
,
|
||||
)
|
||||
|
||||
Path(
|
||||
'',
|
||||
) / "bare"
|
||||
|
||||
Path( # Comment before argument
|
||||
'',
|
||||
) / ("parenthesized")
|
||||
|
||||
Path(
|
||||
'', # EOL comment
|
||||
) / ( ("double parenthesized" ) )
|
||||
|
||||
( Path(
|
||||
'' # Comment in the middle of implicitly concatenated string
|
||||
".",
|
||||
) )/ (("parenthesized path call")
|
||||
# Comment between closing parentheses
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment before comma
|
||||
,
|
||||
) / "multiple" / (
|
||||
"frag" # Comment
|
||||
'ment'
|
||||
)
|
||||
|
||||
|
||||
# no match
|
||||
_ = Path()
|
||||
print(".")
|
||||
Path("file.txt")
|
||||
Path(".", "folder")
|
||||
PurePath(".", "folder")
|
||||
|
||||
Path()
|
||||
|
||||
@@ -156,24 +156,3 @@ from typing import Callable
|
||||
class FilterDataclass:
|
||||
# OK
|
||||
filter: Callable[[str], bool] = lambda _: True
|
||||
|
||||
|
||||
# Regression tests for:
|
||||
# * https://github.com/astral-sh/ruff/issues/7720
|
||||
x = lambda: """
|
||||
a
|
||||
b
|
||||
"""
|
||||
|
||||
# * https://github.com/astral-sh/ruff/issues/10277
|
||||
at_least_one_million = lambda _: _ >= 1_000_000
|
||||
|
||||
x = lambda: (
|
||||
# comment
|
||||
5 + 10
|
||||
)
|
||||
|
||||
x = lambda: (
|
||||
# comment
|
||||
y := 10
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Test: blank-line-after-function special-cases around comment handling."""
|
||||
"""Test: no-blank-line-after-function special-cases around comment handling."""
|
||||
|
||||
# OK
|
||||
def outer():
|
||||
|
||||
@@ -41,49 +41,3 @@ def first_word_lots_of_whitespace():
|
||||
|
||||
What do you think?
|
||||
"""
|
||||
|
||||
def single_word_newline():
|
||||
"""singleword
|
||||
|
||||
"""
|
||||
|
||||
def single_word_dot_newline():
|
||||
"""singleword.
|
||||
|
||||
"""
|
||||
|
||||
def single_word_second_line():
|
||||
"""
|
||||
singleword
|
||||
"""
|
||||
|
||||
def single_word_dot_second_line():
|
||||
"""
|
||||
singleword.
|
||||
"""
|
||||
|
||||
def single_word_then_more_text():
|
||||
"""singleword
|
||||
|
||||
This is more text.
|
||||
"""
|
||||
|
||||
def single_word_dot_then_more_text():
|
||||
"""singleword.
|
||||
|
||||
This is more text.
|
||||
"""
|
||||
|
||||
def single_word_second_line_then_more_text():
|
||||
"""
|
||||
singleword
|
||||
|
||||
This is more text.
|
||||
"""
|
||||
|
||||
def single_word_dot_second_line_then_more_text():
|
||||
"""
|
||||
singleword.
|
||||
|
||||
This is more text.
|
||||
"""
|
||||
@@ -1,28 +0,0 @@
|
||||
# Regression tests for:
|
||||
# https://github.com/astral-sh/ruff/issues/10812
|
||||
|
||||
from typing import Annotated, Literal, TypedDict
|
||||
|
||||
|
||||
# No errors
|
||||
single: TypedDict[{"foo": int}]
|
||||
|
||||
# Error at `qux`
|
||||
multiple: TypedDict[{
|
||||
"bar": str,
|
||||
"baz": list["qux"],
|
||||
}]
|
||||
|
||||
# Error at `dolor`
|
||||
nested: TypedDict[
|
||||
"lorem": TypedDict[{
|
||||
"ipsum": "dolor"
|
||||
}],
|
||||
"sit": Literal["amet"]
|
||||
]
|
||||
|
||||
# Error at `adipiscing`, `eiusmod`, `tempor`
|
||||
unpack: TypedDict[{
|
||||
"consectetur": Annotated["adipiscing", "elit"]
|
||||
**{"sed do": str, int: "eiusmod", **tempor}
|
||||
}]
|
||||
@@ -64,22 +64,3 @@ def f(x: typing.Deque[str]) -> None:
|
||||
|
||||
def f(x: typing.DefaultDict[str, str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.AbstractSet[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.Pattern[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: typing.Sequence[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import Collection
|
||||
|
||||
|
||||
def f(x: typing.Collection[str]) -> None:
|
||||
...
|
||||
|
||||
@@ -8,19 +8,3 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
def f(x: typing.DefaultDict[str, str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from collections.abc import Set
|
||||
from typing_extensions import Awaitable
|
||||
|
||||
|
||||
def f(x: typing.AbstractSet[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: Set) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f(x: Awaitable) -> None:
|
||||
...
|
||||
|
||||
@@ -80,20 +80,11 @@ async def test() -> str:
|
||||
return ",".join(vals)
|
||||
|
||||
|
||||
# FastApi routes can be async without actually using await
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/count")
|
||||
async def fastapi_route():
|
||||
async def fastapi_route(): # Ok: FastApi routes can be async without actually using await
|
||||
return 1
|
||||
|
||||
|
||||
def setup_app(app_arg: FastAPI, non_app: str) -> None:
|
||||
@app_arg.get("/")
|
||||
async def get_root() -> str:
|
||||
return "Hello World!"
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from bleach import clean
|
||||
from markupsafe import Markup
|
||||
|
||||
content = "<script>alert('Hello, world!')</script>"
|
||||
Markup(clean(content))
|
||||
|
||||
# indirect assignments are currently not supported
|
||||
cleaned = clean(content)
|
||||
Markup(cleaned) # RUF035
|
||||
@@ -29,33 +29,6 @@ int(round(1, None))
|
||||
int(round(1.))
|
||||
int(round(1., None))
|
||||
|
||||
int(1)
|
||||
int(v := 1)
|
||||
int(~1)
|
||||
int(-1)
|
||||
int(+1)
|
||||
|
||||
int(1 + 1)
|
||||
int(1 - 1)
|
||||
int(1 * 1)
|
||||
int(1 % 1)
|
||||
int(1 ** 1)
|
||||
int(1 << 1)
|
||||
int(1 >> 1)
|
||||
int(1 | 1)
|
||||
int(1 ^ 1)
|
||||
int(1 & 1)
|
||||
int(1 // 1)
|
||||
|
||||
int(1 if ... else 2)
|
||||
|
||||
int(1 and 0)
|
||||
int(0 or -1)
|
||||
|
||||
|
||||
if int(1 + 2) * 3:
|
||||
...
|
||||
|
||||
|
||||
### Unsafe
|
||||
|
||||
@@ -95,62 +68,3 @@ int(round(inferred_int, 3.14))
|
||||
|
||||
int(round(0, 0), base)
|
||||
int(round(0, 0, extra=keyword))
|
||||
|
||||
int(foo if ... else 4)
|
||||
|
||||
int(3.14)
|
||||
int(2.8j)
|
||||
|
||||
async def f():
|
||||
int(await f())
|
||||
|
||||
int(foo.bar)
|
||||
int(bar([1][False]))
|
||||
|
||||
int(1 == 1)
|
||||
int(1 != 1)
|
||||
int(1 < 1)
|
||||
int(1 <= 1)
|
||||
int(1 > 1)
|
||||
int(1 >= 1)
|
||||
int(1 in 1)
|
||||
int(1 not in 1)
|
||||
int(1 is 1)
|
||||
int(2 is not 3)
|
||||
int(foo in 1)
|
||||
int(foo not in 1)
|
||||
int(foo is 1)
|
||||
int(foo is not 1)
|
||||
|
||||
int(1 == 2 == 3)
|
||||
int(foo == 1)
|
||||
int(foo != 1)
|
||||
int(foo < 1)
|
||||
int(foo <= 1)
|
||||
int(foo > 1)
|
||||
int(foo >= 1)
|
||||
|
||||
int(v := {}[{}['']])
|
||||
|
||||
int(foo + 1)
|
||||
int(foo - 1)
|
||||
int(foo * 1)
|
||||
int(foo @ 1)
|
||||
int(foo / 1)
|
||||
int(foo % 1)
|
||||
int(foo ** 1)
|
||||
int(foo << 1)
|
||||
int(foo >> 1)
|
||||
int(foo | 1)
|
||||
int(foo ^ 1)
|
||||
int(foo & 1)
|
||||
int(foo // 1)
|
||||
|
||||
int(v := 3.7)
|
||||
|
||||
int(not 109)
|
||||
|
||||
int(1 / 1)
|
||||
int(1 @ 1)
|
||||
|
||||
int(1. if ... else .2)
|
||||
|
||||
@@ -77,7 +77,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if !checker.source_type.is_stub() && checker.enabled(Rule::UnquotedTypeAlias) {
|
||||
if checker.enabled(Rule::UnquotedTypeAlias) {
|
||||
if let Some(diagnostics) =
|
||||
flake8_type_checking::rules::unquoted_type_alias(checker, binding)
|
||||
{
|
||||
|
||||
@@ -34,40 +34,40 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
let enforce_stubs_and_runtime = checker.enabled(Rule::IterMethodReturnIterable);
|
||||
let enforce_dunder_method = checker.enabled(Rule::BadDunderMethodName);
|
||||
let enforce_docstrings = checker.any_enabled(&[
|
||||
Rule::MissingBlankLineAfterLastSection,
|
||||
Rule::MissingBlankLineAfterSummary,
|
||||
Rule::BlankLineAfterLastSection,
|
||||
Rule::BlankLineAfterSummary,
|
||||
Rule::BlankLineBeforeClass,
|
||||
Rule::BlankLinesBetweenHeaderAndContent,
|
||||
Rule::NonCapitalizedSectionName,
|
||||
Rule::MissingDashedUnderlineAfterSection,
|
||||
Rule::CapitalizeSectionName,
|
||||
Rule::DashedUnderlineAfterSection,
|
||||
Rule::DocstringStartsWithThis,
|
||||
Rule::EmptyDocstring,
|
||||
Rule::EmptyDocstringSection,
|
||||
Rule::MissingTrailingPeriod,
|
||||
Rule::MissingTerminalPunctuation,
|
||||
Rule::EndsInPeriod,
|
||||
Rule::EndsInPunctuation,
|
||||
Rule::EscapeSequenceInDocstring,
|
||||
Rule::FirstWordUncapitalized,
|
||||
Rule::UnnecessaryMultilineDocstring,
|
||||
Rule::DocstringTabIndentation,
|
||||
Rule::FitsOnOneLine,
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
Rule::MultiLineSummarySecondLine,
|
||||
Rule::NewLineAfterLastParagraph,
|
||||
Rule::MissingNewLineAfterSectionName,
|
||||
Rule::BlankLineAfterFunction,
|
||||
Rule::NewLineAfterSectionName,
|
||||
Rule::NoBlankLineAfterFunction,
|
||||
Rule::NoBlankLineAfterSection,
|
||||
Rule::BlankLineBeforeFunction,
|
||||
Rule::NoBlankLineBeforeFunction,
|
||||
Rule::NoBlankLineBeforeSection,
|
||||
Rule::SignatureInDocstring,
|
||||
Rule::NoSignature,
|
||||
Rule::NonImperativeMood,
|
||||
Rule::IncorrectBlankLineAfterClass,
|
||||
Rule::IncorrectBlankLineBeforeClass,
|
||||
Rule::OneBlankLineAfterClass,
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
Rule::OverIndentation,
|
||||
Rule::OverloadWithDocstring,
|
||||
Rule::MissingSectionNameColon,
|
||||
Rule::OverindentedSection,
|
||||
Rule::MissingSectionUnderlineAfterName,
|
||||
Rule::MismatchedSectionUnderlineLength,
|
||||
Rule::OverindentedSectionUnderline,
|
||||
Rule::SectionNameEndsInColon,
|
||||
Rule::SectionNotOverIndented,
|
||||
Rule::SectionUnderlineAfterName,
|
||||
Rule::SectionUnderlineMatchesSectionLength,
|
||||
Rule::SectionUnderlineNotOverIndented,
|
||||
Rule::SurroundingWhitespace,
|
||||
Rule::TripleSingleQuotes,
|
||||
Rule::UnderIndentation,
|
||||
@@ -220,24 +220,27 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if !pydocstyle::rules::not_empty(checker, &docstring) {
|
||||
continue;
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryMultilineDocstring) {
|
||||
if checker.enabled(Rule::FitsOnOneLine) {
|
||||
pydocstyle::rules::one_liner(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[Rule::BlankLineAfterFunction, Rule::BlankLineBeforeFunction]) {
|
||||
if checker.any_enabled(&[
|
||||
Rule::NoBlankLineAfterFunction,
|
||||
Rule::NoBlankLineBeforeFunction,
|
||||
]) {
|
||||
pydocstyle::rules::blank_before_after_function(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::BlankLineBeforeClass,
|
||||
Rule::IncorrectBlankLineAfterClass,
|
||||
Rule::IncorrectBlankLineBeforeClass,
|
||||
Rule::OneBlankLineAfterClass,
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
]) {
|
||||
pydocstyle::rules::blank_before_after_class(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::MissingBlankLineAfterSummary) {
|
||||
if checker.enabled(Rule::BlankLineAfterSummary) {
|
||||
pydocstyle::rules::blank_after_summary(checker, &docstring);
|
||||
}
|
||||
if checker.any_enabled(&[
|
||||
Rule::DocstringTabIndentation,
|
||||
Rule::IndentWithSpaces,
|
||||
Rule::OverIndentation,
|
||||
Rule::UnderIndentation,
|
||||
]) {
|
||||
@@ -261,7 +264,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EscapeSequenceInDocstring) {
|
||||
pydocstyle::rules::backslashes(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::MissingTrailingPeriod) {
|
||||
if checker.enabled(Rule::EndsInPeriod) {
|
||||
pydocstyle::rules::ends_with_period(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::NonImperativeMood) {
|
||||
@@ -271,7 +274,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
&checker.settings.pydocstyle,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::SignatureInDocstring) {
|
||||
if checker.enabled(Rule::NoSignature) {
|
||||
pydocstyle::rules::no_signature(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::FirstWordUncapitalized) {
|
||||
@@ -280,7 +283,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if checker.enabled(Rule::DocstringStartsWithThis) {
|
||||
pydocstyle::rules::starts_with_this(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::MissingTerminalPunctuation) {
|
||||
if checker.enabled(Rule::EndsInPunctuation) {
|
||||
pydocstyle::rules::ends_with_punctuation(checker, &docstring);
|
||||
}
|
||||
if checker.enabled(Rule::OverloadWithDocstring) {
|
||||
@@ -288,20 +291,20 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
}
|
||||
|
||||
let enforce_sections = checker.any_enabled(&[
|
||||
Rule::MissingBlankLineAfterLastSection,
|
||||
Rule::BlankLineAfterLastSection,
|
||||
Rule::BlankLinesBetweenHeaderAndContent,
|
||||
Rule::NonCapitalizedSectionName,
|
||||
Rule::MissingDashedUnderlineAfterSection,
|
||||
Rule::CapitalizeSectionName,
|
||||
Rule::DashedUnderlineAfterSection,
|
||||
Rule::EmptyDocstringSection,
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
Rule::MissingNewLineAfterSectionName,
|
||||
Rule::NewLineAfterSectionName,
|
||||
Rule::NoBlankLineAfterSection,
|
||||
Rule::NoBlankLineBeforeSection,
|
||||
Rule::MissingSectionNameColon,
|
||||
Rule::OverindentedSection,
|
||||
Rule::MissingSectionUnderlineAfterName,
|
||||
Rule::MismatchedSectionUnderlineLength,
|
||||
Rule::OverindentedSectionUnderline,
|
||||
Rule::SectionNameEndsInColon,
|
||||
Rule::SectionNotOverIndented,
|
||||
Rule::SectionUnderlineAfterName,
|
||||
Rule::SectionUnderlineMatchesSectionLength,
|
||||
Rule::SectionUnderlineNotOverIndented,
|
||||
Rule::UndocumentedParam,
|
||||
]);
|
||||
if enforce_sections || enforce_pydoclint {
|
||||
|
||||
@@ -279,9 +279,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::Airflow3Removal) {
|
||||
airflow::rules::removed_in_3(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
|
||||
if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
pep8_naming::rules::mixed_case_variable_in_global_scope(
|
||||
@@ -986,7 +983,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, expr, func);
|
||||
}
|
||||
if checker.enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
|
||||
@@ -1282,7 +1282,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
if let Some(arg) = args.next() {
|
||||
self.visit_type_definition(arg);
|
||||
|
||||
if !self.source_type.is_stub() && self.enabled(Rule::RuntimeCastValue) {
|
||||
if self.enabled(Rule::RuntimeCastValue) {
|
||||
flake8_type_checking::rules::runtime_cast_value(self, arg);
|
||||
}
|
||||
}
|
||||
@@ -1505,20 +1505,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
debug!("Found non-Expr::Tuple argument to PEP 593 Annotation.");
|
||||
}
|
||||
}
|
||||
Some(typing::SubscriptKind::TypedDict) => {
|
||||
if let Expr::Dict(ast::ExprDict { items, range: _ }) = slice.as_ref() {
|
||||
for item in items {
|
||||
if let Some(key) = &item.key {
|
||||
self.visit_non_type_definition(key);
|
||||
self.visit_type_definition(&item.value);
|
||||
} else {
|
||||
self.visit_non_type_definition(&item.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.visit_non_type_definition(slice);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.visit_expr(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
|
||||
@@ -545,13 +545,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pydocstyle, "105") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedMagicMethod),
|
||||
(Pydocstyle, "106") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicNestedClass),
|
||||
(Pydocstyle, "107") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicInit),
|
||||
(Pydocstyle, "200") => (RuleGroup::Stable, rules::pydocstyle::rules::UnnecessaryMultilineDocstring),
|
||||
(Pydocstyle, "201") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLineBeforeFunction),
|
||||
(Pydocstyle, "202") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLineAfterFunction),
|
||||
(Pydocstyle, "203") => (RuleGroup::Stable, rules::pydocstyle::rules::IncorrectBlankLineBeforeClass),
|
||||
(Pydocstyle, "204") => (RuleGroup::Stable, rules::pydocstyle::rules::IncorrectBlankLineAfterClass),
|
||||
(Pydocstyle, "205") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingBlankLineAfterSummary),
|
||||
(Pydocstyle, "206") => (RuleGroup::Stable, rules::pydocstyle::rules::DocstringTabIndentation),
|
||||
(Pydocstyle, "200") => (RuleGroup::Stable, rules::pydocstyle::rules::FitsOnOneLine),
|
||||
(Pydocstyle, "201") => (RuleGroup::Stable, rules::pydocstyle::rules::NoBlankLineBeforeFunction),
|
||||
(Pydocstyle, "202") => (RuleGroup::Stable, rules::pydocstyle::rules::NoBlankLineAfterFunction),
|
||||
(Pydocstyle, "203") => (RuleGroup::Stable, rules::pydocstyle::rules::OneBlankLineBeforeClass),
|
||||
(Pydocstyle, "204") => (RuleGroup::Stable, rules::pydocstyle::rules::OneBlankLineAfterClass),
|
||||
(Pydocstyle, "205") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLineAfterSummary),
|
||||
(Pydocstyle, "206") => (RuleGroup::Stable, rules::pydocstyle::rules::IndentWithSpaces),
|
||||
(Pydocstyle, "207") => (RuleGroup::Stable, rules::pydocstyle::rules::UnderIndentation),
|
||||
(Pydocstyle, "208") => (RuleGroup::Stable, rules::pydocstyle::rules::OverIndentation),
|
||||
(Pydocstyle, "209") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterLastParagraph),
|
||||
@@ -559,27 +559,27 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pydocstyle, "211") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLineBeforeClass),
|
||||
(Pydocstyle, "212") => (RuleGroup::Stable, rules::pydocstyle::rules::MultiLineSummaryFirstLine),
|
||||
(Pydocstyle, "213") => (RuleGroup::Stable, rules::pydocstyle::rules::MultiLineSummarySecondLine),
|
||||
(Pydocstyle, "214") => (RuleGroup::Stable, rules::pydocstyle::rules::OverindentedSection),
|
||||
(Pydocstyle, "215") => (RuleGroup::Stable, rules::pydocstyle::rules::OverindentedSectionUnderline),
|
||||
(Pydocstyle, "214") => (RuleGroup::Stable, rules::pydocstyle::rules::SectionNotOverIndented),
|
||||
(Pydocstyle, "215") => (RuleGroup::Stable, rules::pydocstyle::rules::SectionUnderlineNotOverIndented),
|
||||
(Pydocstyle, "300") => (RuleGroup::Stable, rules::pydocstyle::rules::TripleSingleQuotes),
|
||||
(Pydocstyle, "301") => (RuleGroup::Stable, rules::pydocstyle::rules::EscapeSequenceInDocstring),
|
||||
(Pydocstyle, "400") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingTrailingPeriod),
|
||||
(Pydocstyle, "400") => (RuleGroup::Stable, rules::pydocstyle::rules::EndsInPeriod),
|
||||
(Pydocstyle, "401") => (RuleGroup::Stable, rules::pydocstyle::rules::NonImperativeMood),
|
||||
(Pydocstyle, "402") => (RuleGroup::Stable, rules::pydocstyle::rules::SignatureInDocstring),
|
||||
(Pydocstyle, "402") => (RuleGroup::Stable, rules::pydocstyle::rules::NoSignature),
|
||||
(Pydocstyle, "403") => (RuleGroup::Stable, rules::pydocstyle::rules::FirstWordUncapitalized),
|
||||
(Pydocstyle, "404") => (RuleGroup::Stable, rules::pydocstyle::rules::DocstringStartsWithThis),
|
||||
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::NonCapitalizedSectionName),
|
||||
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingNewLineAfterSectionName),
|
||||
(Pydocstyle, "407") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingDashedUnderlineAfterSection),
|
||||
(Pydocstyle, "408") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingSectionUnderlineAfterName),
|
||||
(Pydocstyle, "409") => (RuleGroup::Stable, rules::pydocstyle::rules::MismatchedSectionUnderlineLength),
|
||||
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::CapitalizeSectionName),
|
||||
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterSectionName),
|
||||
(Pydocstyle, "407") => (RuleGroup::Stable, rules::pydocstyle::rules::DashedUnderlineAfterSection),
|
||||
(Pydocstyle, "408") => (RuleGroup::Stable, rules::pydocstyle::rules::SectionUnderlineAfterName),
|
||||
(Pydocstyle, "409") => (RuleGroup::Stable, rules::pydocstyle::rules::SectionUnderlineMatchesSectionLength),
|
||||
(Pydocstyle, "410") => (RuleGroup::Stable, rules::pydocstyle::rules::NoBlankLineAfterSection),
|
||||
(Pydocstyle, "411") => (RuleGroup::Stable, rules::pydocstyle::rules::NoBlankLineBeforeSection),
|
||||
(Pydocstyle, "412") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLinesBetweenHeaderAndContent),
|
||||
(Pydocstyle, "413") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingBlankLineAfterLastSection),
|
||||
(Pydocstyle, "413") => (RuleGroup::Stable, rules::pydocstyle::rules::BlankLineAfterLastSection),
|
||||
(Pydocstyle, "414") => (RuleGroup::Stable, rules::pydocstyle::rules::EmptyDocstringSection),
|
||||
(Pydocstyle, "415") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingTerminalPunctuation),
|
||||
(Pydocstyle, "416") => (RuleGroup::Stable, rules::pydocstyle::rules::MissingSectionNameColon),
|
||||
(Pydocstyle, "415") => (RuleGroup::Stable, rules::pydocstyle::rules::EndsInPunctuation),
|
||||
(Pydocstyle, "416") => (RuleGroup::Stable, rules::pydocstyle::rules::SectionNameEndsInColon),
|
||||
(Pydocstyle, "417") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedParam),
|
||||
(Pydocstyle, "418") => (RuleGroup::Stable, rules::pydocstyle::rules::OverloadWithDocstring),
|
||||
(Pydocstyle, "419") => (RuleGroup::Stable, rules::pydocstyle::rules::EmptyDocstring),
|
||||
|
||||
@@ -142,11 +142,9 @@ fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Orderi
|
||||
.then_with(|| fix1.min_start().cmp(&fix2.min_start()))
|
||||
// Break ties in the event of overlapping rules, for some specific combinations.
|
||||
.then_with(|| match (&rule1, &rule2) {
|
||||
// Apply `MissingTrailingPeriod` fixes before `NewLineAfterLastParagraph` fixes.
|
||||
(Rule::MissingTrailingPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
|
||||
(Rule::NewLineAfterLastParagraph, Rule::MissingTrailingPeriod) => {
|
||||
std::cmp::Ordering::Greater
|
||||
}
|
||||
// Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes.
|
||||
(Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
|
||||
(Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater,
|
||||
// Apply `IfElseBlockInsteadOfDictGet` fixes before `IfElseBlockInsteadOfIfExp` fixes.
|
||||
(Rule::IfElseBlockInsteadOfDictGet, Rule::IfElseBlockInsteadOfIfExp) => {
|
||||
std::cmp::Ordering::Less
|
||||
|
||||
@@ -357,9 +357,9 @@ impl Rule {
|
||||
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
|
||||
(
|
||||
Rule::BlankLineBeforeClass,
|
||||
Rule::IncorrectBlankLineBeforeClass,
|
||||
"`incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are \
|
||||
incompatible. Ignoring `incorrect-blank-line-before-class`.",
|
||||
Rule::OneBlankLineBeforeClass,
|
||||
"`one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are \
|
||||
incompatible. Ignoring `one-blank-line-before-class`.",
|
||||
),
|
||||
(
|
||||
Rule::MultiLineSummaryFirstLine,
|
||||
|
||||
@@ -16,8 +16,6 @@ mod tests {
|
||||
#[test_case(Rule::AirflowDagNoScheduleArgument, Path::new("AIR301.py"))]
|
||||
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_args.py"))]
|
||||
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_names.py"))]
|
||||
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_class_attribute.py"))]
|
||||
#[test_case(Rule::Airflow3Removal, Path::new("AIR302_airflow_plugin.py"))]
|
||||
#[test_case(Rule::Airflow3MovedToProvider, Path::new("AIR303.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
|
||||
@@ -111,7 +111,7 @@ fn moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "auth", "managers", "fab", "fab_auth_manager", "FabAuthManager"] => Some((
|
||||
["airflow","auth","managers","fab","fab_auth_manager", "FabAuthManager"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ProviderName{
|
||||
name: "airflow.providers.fab.auth_manager.security_manager.FabAuthManager",
|
||||
@@ -128,7 +128,7 @@ fn moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "api", "auth", "backend", "kerberos_auth", ..] => Some((
|
||||
["airflow", "api","auth","backend","kerberos_auth", ..] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path:"airflow.api.auth.backend.kerberos_auth",
|
||||
@@ -146,100 +146,16 @@ fn moved_to_provider(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "auth", "managers", "fab", "security_manager", "override", ..] => Some((
|
||||
["airflow","auth","managers","fab","security_manager","override", ..] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.auth.managers.fab.security_managr.override",
|
||||
original_path: "airflow.auth.managers.fab.security_manager.override",
|
||||
new_path: "airflow.providers.fab.auth_manager.security_manager.override",
|
||||
provider: "fab",
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
// apache-airflow-providers-celery
|
||||
["airflow", "config_templates", "default_celery", "DEFAULT_CELERY_CONFIG"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG",
|
||||
new_path: "airflow.providers.celery.executors.default_celery.DEFAULT_CELERY_CONFIG",
|
||||
provider: "celery",
|
||||
version: "3.3.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "executors", "celery_executor", "app"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.executors.celery_executor.app",
|
||||
new_path: "airflow.providers.celery.executors.celery_executor_utils.app",
|
||||
provider: "celery",
|
||||
version: "3.3.0"
|
||||
},
|
||||
)),
|
||||
// apache-airflow-providers-common-sql
|
||||
["airflow", "hooks", "dbapi", "ConnectorProtocol"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.hooks.dbapi.ConnectorProtocol",
|
||||
new_path: "airflow.providers.common.sql.hooks.sql.ConnectorProtocol",
|
||||
provider: "Common SQL",
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "hooks", "dbapi", "DbApiHook"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.hooks.dbapi.DbApiHook",
|
||||
new_path: "airflow.providers.common.sql.hooks.sql.DbApiHook",
|
||||
provider: "Common SQL",
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
// apache-airflow-providers-cncf-kubernetes
|
||||
["airflow", "executors", "kubernetes_executor_types", "ALL_NAMESPACES"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.executors.kubernetes_executor_types.ALL_NAMESPACES",
|
||||
new_path: "airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.ALL_NAMESPACES",
|
||||
provider: "Kubernetes",
|
||||
version: "7.4.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "executors", "kubernetes_executor_types", "POD_EXECUTOR_DONE_KEY"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY",
|
||||
new_path: "airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY",
|
||||
provider: "Kubernetes",
|
||||
version: "7.4.0"
|
||||
},
|
||||
)),
|
||||
// apache-airflow-providers-apache-hive
|
||||
["airflow", "hooks", "hive_hooks", "HIVE_QUEUE_PRIORITIES"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES",
|
||||
new_path: "airflow.providers.apache.hive.hooks.hive.HIVE_QUEUE_PRIORITIES",
|
||||
provider: "Apache Hive",
|
||||
version: "1.0.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "macros", "hive", "closest_ds_partition"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.macros.hive.closest_ds_partition",
|
||||
new_path: "airflow.providers.apache.hive.macros.hive.closest_ds_partition",
|
||||
provider: "Apache Hive",
|
||||
version: "5.1.0"
|
||||
},
|
||||
)),
|
||||
["airflow", "macros", "hive", "max_partition"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::ImportPathMoved{
|
||||
original_path: "airflow.macros.hive.max_partition",
|
||||
new_path: "airflow.providers.apache.hive.macros.hive.max_partition",
|
||||
provider: "Apache Hive",
|
||||
version: "5.1.0"
|
||||
},
|
||||
)),
|
||||
|
||||
_ => None,
|
||||
});
|
||||
if let Some((deprecated, replacement)) = result {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{
|
||||
name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall, ExprContext, ExprName,
|
||||
StmtClassDef,
|
||||
};
|
||||
use ruff_python_ast::{name::QualifiedName, Arguments, Expr, ExprAttribute, ExprCall};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_python_semantic::ScopeKind;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -127,106 +123,38 @@ fn removed_argument(checker: &mut Checker, qualname: &QualifiedName, arguments:
|
||||
None::<&str>,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
if is_airflow_auth_manager(qualname.segments()) {
|
||||
if !arguments.is_empty() {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Airflow3Removal {
|
||||
// deprecated: (*arguments).to_string(),
|
||||
deprecated: "appbuilder".to_string(),
|
||||
replacement: Replacement::Message(
|
||||
"The constructor takes no parameter now.",
|
||||
),
|
||||
},
|
||||
arguments.range(),
|
||||
));
|
||||
}
|
||||
} else if is_airflow_task_handler(qualname.segments()) {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"filename_template",
|
||||
None::<&str>,
|
||||
));
|
||||
} else if is_airflow_operator(qualname.segments()) {
|
||||
checker
|
||||
.diagnostics
|
||||
.extend(diagnostic_for_argument(arguments, "sla", None::<&str>));
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"task_concurrency",
|
||||
Some("max_active_tis_per_dag"),
|
||||
));
|
||||
match qualname.segments() {
|
||||
["airflow", .., "operators", "trigger_dagrun", "TriggerDagRunOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"execution_date",
|
||||
Some("logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "datetime", "BranchDateTimeOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "weekday", "DayOfWeekSensor"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "weekday", "BranchDayOfWeekOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
["airflow", .., "operators", "trigger_dagrun", "TriggerDagRunOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"execution_date",
|
||||
Some("logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "datetime", "BranchDateTimeOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "weekday", "DayOfWeekSensor"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
["airflow", .., "operators", "weekday", "BranchDayOfWeekOperator"] => {
|
||||
checker.diagnostics.extend(diagnostic_for_argument(
|
||||
arguments,
|
||||
"use_task_execution_day",
|
||||
Some("use_task_logical_date"),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn removed_class_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
let Expr::Attribute(ExprAttribute { attr, value, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(qualname) = typing::resolve_assignment(value, checker.semantic()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let replacement = match *qualname.segments() {
|
||||
["airflow", "providers_manager", "ProvidersManager"] => match attr.as_str() {
|
||||
"dataset_factories" => Some(Replacement::Name("asset_factories")),
|
||||
"dataset_uri_handlers" => Some(Replacement::Name("asset_uri_handlers")),
|
||||
"dataset_to_openlineage_converters" => {
|
||||
Some(Replacement::Name("asset_to_openlineage_converters"))
|
||||
}
|
||||
&_ => None,
|
||||
},
|
||||
["airflow", "lineage", "hook"] => match attr.as_str() {
|
||||
"dataset" => Some(Replacement::Name("asset")),
|
||||
&_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
if let Some(replacement) = replacement {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Airflow3Removal {
|
||||
deprecated: attr.to_string(),
|
||||
replacement,
|
||||
},
|
||||
attr.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn removed_method(checker: &mut Checker, expr: &Expr) {
|
||||
let Expr::Call(ExprCall { func, .. }) = expr else {
|
||||
return;
|
||||
@@ -268,27 +196,7 @@ fn removed_method(checker: &mut Checker, expr: &Expr) {
|
||||
)),
|
||||
&_ => None,
|
||||
},
|
||||
["airflow", "datasets", ..] | ["airflow", "Dataset"] => match attr.as_str() {
|
||||
"iter_datasets" => Some(Replacement::Name("iter_assets")),
|
||||
"iter_dataset_aliases" => Some(Replacement::Name("iter_asset_aliases")),
|
||||
&_ => None,
|
||||
},
|
||||
_ => {
|
||||
if is_airflow_secret_backend(qualname.segments()) {
|
||||
match attr.as_str() {
|
||||
"get_conn_uri" => Some(Replacement::Name("get_conn_value")),
|
||||
"get_connections" => Some(Replacement::Name("get_connection")),
|
||||
&_ => None,
|
||||
}
|
||||
} else if is_airflow_hook(qualname.segments()) {
|
||||
match attr.as_str() {
|
||||
"get_connections" => Some(Replacement::Name("get_connection")),
|
||||
&_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(replacement) = replacement {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
@@ -482,11 +390,6 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.lineage.hook.AssetLineageInfo"),
|
||||
)),
|
||||
// airflow.hooks
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => Some((
|
||||
qualname.to_string(),
|
||||
Replacement::Name("airflow.hooks.base.BaseHook"),
|
||||
)),
|
||||
// airflow.operators
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
Some((
|
||||
@@ -743,37 +646,6 @@ fn removed_name(checker: &mut Checker, expr: &Expr, ranged: impl Ranged) {
|
||||
}
|
||||
}
|
||||
|
||||
fn removed_airflow_plugin_extension(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
name: &str,
|
||||
class_def: &StmtClassDef,
|
||||
) {
|
||||
if matches!(name, "executors" | "operators" | "sensors" | "hooks") {
|
||||
if class_def.bases().iter().any(|expr| {
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["airflow", "plugins_manager", "AirflowPlugin"]
|
||||
)
|
||||
})
|
||||
}) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
Airflow3Removal {
|
||||
deprecated: name.to_string(),
|
||||
replacement: Replacement::Message(
|
||||
"This extension should just be imported as a regular python module.",
|
||||
),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AIR302
|
||||
pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
|
||||
if !checker.semantic().seen_module(Modules::AIRFLOW) {
|
||||
@@ -790,104 +662,8 @@ pub(crate) fn removed_in_3(checker: &mut Checker, expr: &Expr) {
|
||||
|
||||
removed_method(checker, expr);
|
||||
}
|
||||
Expr::Attribute(ExprAttribute { attr: ranged, .. }) => {
|
||||
removed_name(checker, expr, ranged);
|
||||
removed_class_attribute(checker, expr);
|
||||
}
|
||||
ranged @ Expr::Name(ExprName { id, ctx, .. }) => {
|
||||
removed_name(checker, expr, ranged);
|
||||
if ctx == &ExprContext::Store {
|
||||
if let ScopeKind::Class(class_def) = &checker.semantic().current_scope().kind {
|
||||
removed_airflow_plugin_extension(checker, expr, id, class_def);
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Attribute(ExprAttribute { attr: ranged, .. }) => removed_name(checker, expr, ranged),
|
||||
ranged @ Expr::Name(_) => removed_name(checker, expr, ranged),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether the symbol is coming from the `secrets` builtin or provider module which ends
|
||||
/// with `Backend`.
|
||||
fn is_airflow_secret_backend(segments: &[&str]) -> bool {
|
||||
is_airflow_builtin_or_provider(segments, "secrets", "Backend")
|
||||
}
|
||||
|
||||
/// Check whether the symbol is coming from the `hooks` builtin or provider module which ends
|
||||
/// with `Hook`.
|
||||
fn is_airflow_hook(segments: &[&str]) -> bool {
|
||||
is_airflow_builtin_or_provider(segments, "hooks", "Hook")
|
||||
}
|
||||
|
||||
/// Check whether the symbol is coming from the `operators` builtin or provider module which ends
|
||||
/// with `Operator`.
|
||||
fn is_airflow_operator(segments: &[&str]) -> bool {
|
||||
is_airflow_builtin_or_provider(segments, "operators", "Operator")
|
||||
}
|
||||
|
||||
/// Check whether the symbol is coming from the `log` builtin or provider module which ends
|
||||
/// with `TaskHandler`.
|
||||
fn is_airflow_task_handler(segments: &[&str]) -> bool {
|
||||
is_airflow_builtin_or_provider(segments, "log", "TaskHandler")
|
||||
}
|
||||
|
||||
/// Check whether the segments corresponding to the fully qualified name points to a symbol that's
|
||||
/// either a builtin or coming from one of the providers in Airflow.
|
||||
///
|
||||
/// The pattern it looks for are:
|
||||
/// - `airflow.providers.**.<module>.**.*<symbol_suffix>` for providers
|
||||
/// - `airflow.<module>.**.*<symbol_suffix>` for builtins
|
||||
///
|
||||
/// where `**` is one or more segments separated by a dot, and `*` is one or more characters.
|
||||
///
|
||||
/// Examples for the above patterns:
|
||||
/// - `airflow.providers.google.cloud.secrets.secret_manager.CloudSecretManagerBackend` (provider)
|
||||
/// - `airflow.secrets.base_secrets.BaseSecretsBackend` (builtin)
|
||||
fn is_airflow_builtin_or_provider(segments: &[&str], module: &str, symbol_suffix: &str) -> bool {
|
||||
match segments {
|
||||
["airflow", "providers", rest @ ..] => {
|
||||
if let (Some(pos), Some(last_element)) =
|
||||
(rest.iter().position(|&s| s == module), rest.last())
|
||||
{
|
||||
// Check that the module is not the last element i.e., there's a symbol that's
|
||||
// being used from the `module` that ends with `symbol_suffix`.
|
||||
pos + 1 < rest.len() && last_element.ends_with(symbol_suffix)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
["airflow", first, rest @ ..] => {
|
||||
if let Some(last) = rest.last() {
|
||||
first == module && last.ends_with(symbol_suffix)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_airflow_auth_manager(segments: &[&str]) -> bool {
|
||||
match segments {
|
||||
["airflow", "auth", "manager", rest @ ..] => {
|
||||
if let Some(last_element) = rest.last() {
|
||||
last_element.ends_with("AuthManager")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
["airflow", "providers", rest @ ..] => {
|
||||
if let (Some(pos), Some(last_element)) =
|
||||
(rest.iter().position(|&s| s == "auth_manager"), rest.last())
|
||||
{
|
||||
pos + 1 < rest.len() && last_element.ends_with("AuthManager")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
AIR302_airflow_plugin.py:7:5: AIR302 `operators` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
|
|
||||
5 | name = "test_plugin"
|
||||
6 | # --- Invalid extensions start
|
||||
7 | operators = [PluginOperator]
|
||||
| ^^^^^^^^^ AIR302
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
|
|
||||
|
||||
AIR302_airflow_plugin.py:8:5: AIR302 `sensors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
|
|
||||
6 | # --- Invalid extensions start
|
||||
7 | operators = [PluginOperator]
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
| ^^^^^^^ AIR302
|
||||
9 | hooks = [PluginHook]
|
||||
10 | executors = [PluginExecutor]
|
||||
|
|
||||
|
||||
AIR302_airflow_plugin.py:9:5: AIR302 `hooks` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
|
|
||||
7 | operators = [PluginOperator]
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
| ^^^^^ AIR302
|
||||
10 | executors = [PluginExecutor]
|
||||
11 | # --- Invalid extensions end
|
||||
|
|
||||
|
||||
AIR302_airflow_plugin.py:10:5: AIR302 `executors` is removed in Airflow 3.0; This extension should just be imported as a regular python module.
|
||||
|
|
||||
8 | sensors = [PluginSensorOperator]
|
||||
9 | hooks = [PluginHook]
|
||||
10 | executors = [PluginExecutor]
|
||||
| ^^^^^^^^^ AIR302
|
||||
11 | # --- Invalid extensions end
|
||||
12 | macros = [plugin_macro]
|
||||
|
|
||||
@@ -2,251 +2,175 @@
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
AIR302_args.py:18:39: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
||||
AIR302_args.py:15:39: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
||||
|
|
||||
16 | DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
17 |
|
||||
18 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
13 | DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
14 |
|
||||
15 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
19 |
|
||||
20 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
16 |
|
||||
17 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
|
|
||||
= help: Use `schedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 |
|
||||
16 16 | DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
17 17 |
|
||||
18 |-DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
18 |+DAG(dag_id="class_schedule_interval", schedule="@hourly")
|
||||
19 19 |
|
||||
20 20 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
21 21 |
|
||||
12 12 |
|
||||
13 13 | DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
14 14 |
|
||||
15 |-DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
15 |+DAG(dag_id="class_schedule_interval", schedule="@hourly")
|
||||
16 16 |
|
||||
17 17 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
18 18 |
|
||||
|
||||
AIR302_args.py:20:31: AIR302 [*] `timetable` is removed in Airflow 3.0
|
||||
AIR302_args.py:17:31: AIR302 [*] `timetable` is removed in Airflow 3.0
|
||||
|
|
||||
18 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
19 |
|
||||
20 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
15 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
16 |
|
||||
17 | DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
| ^^^^^^^^^ AIR302
|
||||
|
|
||||
= help: Use `schedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
17 17 |
|
||||
18 18 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
14 14 |
|
||||
15 15 | DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
|
||||
16 16 |
|
||||
17 |-DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
17 |+DAG(dag_id="class_timetable", schedule=NullTimetable())
|
||||
18 18 |
|
||||
19 19 |
|
||||
20 |-DAG(dag_id="class_timetable", timetable=NullTimetable())
|
||||
20 |+DAG(dag_id="class_timetable", schedule=NullTimetable())
|
||||
21 21 |
|
||||
22 22 |
|
||||
23 23 | def sla_callback(*arg, **kwargs):
|
||||
20 20 | def sla_callback(*arg, **kwargs):
|
||||
|
||||
AIR302_args.py:27:34: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
||||
AIR302_args.py:24:34: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
||||
|
|
||||
27 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback)
|
||||
24 | DAG(dag_id="class_sla_callback", sla_miss_callback=sla_callback)
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
|
|
||||
|
||||
AIR302_args.py:35:6: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
||||
AIR302_args.py:32:6: AIR302 [*] `schedule_interval` is removed in Airflow 3.0
|
||||
|
|
||||
35 | @dag(schedule_interval="0 * * * *")
|
||||
32 | @dag(schedule_interval="0 * * * *")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
36 | def decorator_schedule_interval():
|
||||
37 | pass
|
||||
33 | def decorator_schedule_interval():
|
||||
34 | pass
|
||||
|
|
||||
= help: Use `schedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 34 |
|
||||
35 |-@dag(schedule_interval="0 * * * *")
|
||||
35 |+@dag(schedule="0 * * * *")
|
||||
36 36 | def decorator_schedule_interval():
|
||||
37 37 | pass
|
||||
38 38 |
|
||||
29 29 | pass
|
||||
30 30 |
|
||||
31 31 |
|
||||
32 |-@dag(schedule_interval="0 * * * *")
|
||||
32 |+@dag(schedule="0 * * * *")
|
||||
33 33 | def decorator_schedule_interval():
|
||||
34 34 | pass
|
||||
35 35 |
|
||||
|
||||
AIR302_args.py:40:6: AIR302 [*] `timetable` is removed in Airflow 3.0
|
||||
AIR302_args.py:37:6: AIR302 [*] `timetable` is removed in Airflow 3.0
|
||||
|
|
||||
40 | @dag(timetable=NullTimetable())
|
||||
37 | @dag(timetable=NullTimetable())
|
||||
| ^^^^^^^^^ AIR302
|
||||
41 | def decorator_timetable():
|
||||
42 | pass
|
||||
38 | def decorator_timetable():
|
||||
39 | pass
|
||||
|
|
||||
= help: Use `schedule` instead
|
||||
|
||||
ℹ Safe fix
|
||||
37 37 | pass
|
||||
38 38 |
|
||||
39 39 |
|
||||
40 |-@dag(timetable=NullTimetable())
|
||||
40 |+@dag(schedule=NullTimetable())
|
||||
41 41 | def decorator_timetable():
|
||||
42 42 | pass
|
||||
43 43 |
|
||||
34 34 | pass
|
||||
35 35 |
|
||||
36 36 |
|
||||
37 |-@dag(timetable=NullTimetable())
|
||||
37 |+@dag(schedule=NullTimetable())
|
||||
38 38 | def decorator_timetable():
|
||||
39 39 | pass
|
||||
40 40 |
|
||||
|
||||
AIR302_args.py:45:6: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
||||
AIR302_args.py:42:6: AIR302 `sla_miss_callback` is removed in Airflow 3.0
|
||||
|
|
||||
45 | @dag(sla_miss_callback=sla_callback)
|
||||
42 | @dag(sla_miss_callback=sla_callback)
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
46 | def decorator_sla_callback():
|
||||
47 | pass
|
||||
43 | def decorator_sla_callback():
|
||||
44 | pass
|
||||
|
|
||||
|
||||
AIR302_args.py:50:39: AIR302 [*] `execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
48 | def decorator_deprecated_operator_args():
|
||||
49 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||
50 | task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
| ^^^^^^^^^^^^^^ AIR302
|
||||
51 | )
|
||||
52 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
|
|
||||
= help: Use `logical_date` instead
|
||||
|
||||
ℹ Safe fix
|
||||
47 47 | @dag()
|
||||
48 48 | def decorator_deprecated_operator_args():
|
||||
49 49 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||
50 |- task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
50 |+ task_id="trigger_dagrun_op1", logical_date="2024-12-04"
|
||||
51 51 | )
|
||||
52 52 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
53 53 | task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
|
||||
AIR302_args.py:53:39: AIR302 [*] `execution_date` is removed in Airflow 3.0
|
||||
|
|
||||
51 | def decorator_deprecated_operator_args():
|
||||
52 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||
53 | task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
51 | )
|
||||
52 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
53 | task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
| ^^^^^^^^^^^^^^ AIR302
|
||||
54 | )
|
||||
55 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
|
|
||||
= help: Use `logical_date` instead
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 | @dag()
|
||||
51 51 | def decorator_deprecated_operator_args():
|
||||
52 52 | trigger_dagrun_op = trigger_dagrun.TriggerDagRunOperator(
|
||||
53 |- task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
53 |+ task_id="trigger_dagrun_op1", logical_date="2024-12-04"
|
||||
50 50 | task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
51 51 | )
|
||||
52 52 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
53 |- task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
53 |+ task_id="trigger_dagrun_op2", logical_date="2024-12-04"
|
||||
54 54 | )
|
||||
55 55 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
56 56 | task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
55 55 |
|
||||
56 56 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
|
||||
AIR302_args.py:56:39: AIR302 [*] `execution_date` is removed in Airflow 3.0
|
||||
AIR302_args.py:57:33: AIR302 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||
|
|
||||
54 | )
|
||||
55 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
56 | task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
| ^^^^^^^^^^^^^^ AIR302
|
||||
57 | )
|
||||
|
|
||||
= help: Use `logical_date` instead
|
||||
|
||||
ℹ Safe fix
|
||||
53 53 | task_id="trigger_dagrun_op1", execution_date="2024-12-04"
|
||||
54 54 | )
|
||||
55 55 | trigger_dagrun_op2 = TriggerDagRunOperator(
|
||||
56 |- task_id="trigger_dagrun_op2", execution_date="2024-12-04"
|
||||
56 |+ task_id="trigger_dagrun_op2", logical_date="2024-12-04"
|
||||
57 57 | )
|
||||
58 58 |
|
||||
59 59 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
|
||||
AIR302_args.py:60:33: AIR302 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||
|
|
||||
59 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
60 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||
56 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
57 | task_id="branch_dt_op", use_task_execution_day=True
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
61 | )
|
||||
62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
58 | )
|
||||
59 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
|
|
||||
= help: Use `use_task_logical_date` instead
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | )
|
||||
58 58 |
|
||||
59 59 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
60 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||
60 |+ task_id="branch_dt_op", use_task_logical_date=True, task_concurrency=5
|
||||
61 61 | )
|
||||
62 62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
63 63 | task_id="branch_dt_op2",
|
||||
54 54 | )
|
||||
55 55 |
|
||||
56 56 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
57 |- task_id="branch_dt_op", use_task_execution_day=True
|
||||
57 |+ task_id="branch_dt_op", use_task_logical_date=True
|
||||
58 58 | )
|
||||
59 59 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
60 60 | task_id="branch_dt_op2", use_task_execution_day=True
|
||||
|
||||
AIR302_args.py:60:62: AIR302 [*] `task_concurrency` is removed in Airflow 3.0
|
||||
AIR302_args.py:60:34: AIR302 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||
|
|
||||
59 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
60 | task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||
| ^^^^^^^^^^^^^^^^ AIR302
|
||||
58 | )
|
||||
59 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
60 | task_id="branch_dt_op2", use_task_execution_day=True
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
61 | )
|
||||
62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
|
|
||||
= help: Use `max_active_tis_per_dag` instead
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | )
|
||||
58 58 |
|
||||
59 59 | branch_dt_op = datetime.BranchDateTimeOperator(
|
||||
60 |- task_id="branch_dt_op", use_task_execution_day=True, task_concurrency=5
|
||||
60 |+ task_id="branch_dt_op", use_task_execution_day=True, max_active_tis_per_dag=5
|
||||
61 61 | )
|
||||
62 62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
63 63 | task_id="branch_dt_op2",
|
||||
|
||||
AIR302_args.py:64:9: AIR302 [*] `use_task_execution_day` is removed in Airflow 3.0
|
||||
|
|
||||
62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
63 | task_id="branch_dt_op2",
|
||||
64 | use_task_execution_day=True,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
65 | sla=timedelta(seconds=10),
|
||||
66 | )
|
||||
|
|
||||
= help: Use `use_task_logical_date` instead
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | task_id="branch_dt_op", use_task_execution_day=True
|
||||
58 58 | )
|
||||
59 59 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
60 |- task_id="branch_dt_op2", use_task_execution_day=True
|
||||
60 |+ task_id="branch_dt_op2", use_task_logical_date=True
|
||||
61 61 | )
|
||||
62 62 | branch_dt_op2 = BranchDateTimeOperator(
|
||||
63 63 | task_id="branch_dt_op2",
|
||||
64 |- use_task_execution_day=True,
|
||||
64 |+ use_task_logical_date=True,
|
||||
65 65 | sla=timedelta(seconds=10),
|
||||
66 66 | )
|
||||
67 67 |
|
||||
|
||||
AIR302_args.py:65:9: AIR302 `sla` is removed in Airflow 3.0
|
||||
|
|
||||
63 | task_id="branch_dt_op2",
|
||||
64 | use_task_execution_day=True,
|
||||
65 | sla=timedelta(seconds=10),
|
||||
| ^^^ AIR302
|
||||
66 | )
|
||||
|
|
||||
|
||||
AIR302_args.py:87:15: AIR302 `filename_template` is removed in Airflow 3.0
|
||||
|
|
||||
86 | # deprecated filename_template arugment in FileTaskHandler
|
||||
87 | S3TaskHandler(filename_template="/tmp/test")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
88 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||
89 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||
|
|
||||
|
||||
AIR302_args.py:88:17: AIR302 `filename_template` is removed in Airflow 3.0
|
||||
|
|
||||
86 | # deprecated filename_template arugment in FileTaskHandler
|
||||
87 | S3TaskHandler(filename_template="/tmp/test")
|
||||
88 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
89 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
|
|
||||
|
||||
AIR302_args.py:89:26: AIR302 `filename_template` is removed in Airflow 3.0
|
||||
|
|
||||
87 | S3TaskHandler(filename_template="/tmp/test")
|
||||
88 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||
89 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
|
|
||||
|
||||
AIR302_args.py:90:16: AIR302 `filename_template` is removed in Airflow 3.0
|
||||
|
|
||||
88 | HdfsTaskHandler(filename_template="/tmp/test")
|
||||
89 | ElasticsearchTaskHandler(filename_template="/tmp/test")
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
91 |
|
||||
92 | FabAuthManager(None)
|
||||
|
|
||||
|
||||
AIR302_args.py:92:15: AIR302 `appbuilder` is removed in Airflow 3.0; The constructor takes no parameter now.
|
||||
|
|
||||
90 | GCSTaskHandler(filename_template="/tmp/test")
|
||||
91 |
|
||||
92 | FabAuthManager(None)
|
||||
| ^^^^^^ AIR302
|
||||
|
|
||||
62 62 |
|
||||
63 63 | dof_task_sensor = weekday.DayOfWeekSensor(
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
AIR302_class_attribute.py:13:4: AIR302 `register_dataset_change` is removed in Airflow 3.0
|
||||
|
|
||||
12 | dm = DatasetManager()
|
||||
13 | dm.register_dataset_change()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
14 | dm.create_datasets()
|
||||
15 | dm.notify_dataset_created()
|
||||
|
|
||||
= help: Use `register_asset_change` instead
|
||||
|
||||
AIR302_class_attribute.py:14:4: AIR302 `create_datasets` is removed in Airflow 3.0
|
||||
|
|
||||
12 | dm = DatasetManager()
|
||||
13 | dm.register_dataset_change()
|
||||
14 | dm.create_datasets()
|
||||
| ^^^^^^^^^^^^^^^ AIR302
|
||||
15 | dm.notify_dataset_created()
|
||||
16 | dm.notify_dataset_changed()
|
||||
|
|
||||
= help: Use `create_assets` instead
|
||||
|
||||
AIR302_class_attribute.py:15:4: AIR302 `notify_dataset_created` is removed in Airflow 3.0
|
||||
|
|
||||
13 | dm.register_dataset_change()
|
||||
14 | dm.create_datasets()
|
||||
15 | dm.notify_dataset_created()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
16 | dm.notify_dataset_changed()
|
||||
17 | dm.notify_dataset_alias_created()
|
||||
|
|
||||
= help: Use `notify_asset_created` instead
|
||||
|
||||
AIR302_class_attribute.py:16:4: AIR302 `notify_dataset_changed` is removed in Airflow 3.0
|
||||
|
|
||||
14 | dm.create_datasets()
|
||||
15 | dm.notify_dataset_created()
|
||||
16 | dm.notify_dataset_changed()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
17 | dm.notify_dataset_alias_created()
|
||||
|
|
||||
= help: Use `notify_asset_changed` instead
|
||||
|
||||
AIR302_class_attribute.py:17:4: AIR302 `notify_dataset_alias_created` is removed in Airflow 3.0
|
||||
|
|
||||
15 | dm.notify_dataset_created()
|
||||
16 | dm.notify_dataset_changed()
|
||||
17 | dm.notify_dataset_alias_created()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
18 |
|
||||
19 | hlc = HookLineageCollector()
|
||||
|
|
||||
= help: Use `notify_asset_alias_created` instead
|
||||
|
||||
AIR302_class_attribute.py:20:5: AIR302 `create_dataset` is removed in Airflow 3.0
|
||||
|
|
||||
19 | hlc = HookLineageCollector()
|
||||
20 | hlc.create_dataset()
|
||||
| ^^^^^^^^^^^^^^ AIR302
|
||||
21 | hlc.add_input_dataset()
|
||||
22 | hlc.add_output_dataset()
|
||||
|
|
||||
= help: Use `create_asset` instead
|
||||
|
||||
AIR302_class_attribute.py:21:5: AIR302 `add_input_dataset` is removed in Airflow 3.0
|
||||
|
|
||||
19 | hlc = HookLineageCollector()
|
||||
20 | hlc.create_dataset()
|
||||
21 | hlc.add_input_dataset()
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
22 | hlc.add_output_dataset()
|
||||
23 | hlc.collected_datasets()
|
||||
|
|
||||
= help: Use `add_input_asset` instead
|
||||
|
||||
AIR302_class_attribute.py:22:5: AIR302 `add_output_dataset` is removed in Airflow 3.0
|
||||
|
|
||||
20 | hlc.create_dataset()
|
||||
21 | hlc.add_input_dataset()
|
||||
22 | hlc.add_output_dataset()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR302
|
||||
23 | hlc.collected_datasets()
|
||||
|
|
||||
= help: Use `add_output_asset` instead
|
||||
|
||||
AIR302_class_attribute.py:23:5: AIR302 `collected_datasets` is removed in Airflow 3.0
|
||||
|
|
||||
21 | hlc.add_input_dataset()
|
||||
22 | hlc.add_output_dataset()
|
||||
23 | hlc.collected_datasets()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR302
|
||||
24 |
|
||||
25 | aam = AwsAuthManager()
|
||||
|
|
||||
= help: Use `collected_assets` instead
|
||||
|
||||
AIR302_class_attribute.py:26:5: AIR302 `is_authorized_dataset` is removed in Airflow 3.0
|
||||
|
|
||||
25 | aam = AwsAuthManager()
|
||||
26 | aam.is_authorized_dataset()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
27 |
|
||||
28 | pm = ProvidersManager()
|
||||
|
|
||||
= help: Use `is_authorized_asset` instead
|
||||
|
||||
AIR302_class_attribute.py:30:4: AIR302 `dataset_factories` is removed in Airflow 3.0
|
||||
|
|
||||
28 | pm = ProvidersManager()
|
||||
29 | pm.initialize_providers_asset_uri_resources()
|
||||
30 | pm.dataset_factories
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
31 |
|
||||
32 | base_secret_backend = BaseSecretsBackend()
|
||||
|
|
||||
= help: Use `asset_factories` instead
|
||||
|
||||
AIR302_class_attribute.py:33:21: AIR302 `get_conn_uri` is removed in Airflow 3.0
|
||||
|
|
||||
32 | base_secret_backend = BaseSecretsBackend()
|
||||
33 | base_secret_backend.get_conn_uri()
|
||||
| ^^^^^^^^^^^^ AIR302
|
||||
34 | base_secret_backend.get_connections()
|
||||
|
|
||||
= help: Use `get_conn_value` instead
|
||||
|
||||
AIR302_class_attribute.py:34:21: AIR302 `get_connections` is removed in Airflow 3.0
|
||||
|
|
||||
32 | base_secret_backend = BaseSecretsBackend()
|
||||
33 | base_secret_backend.get_conn_uri()
|
||||
34 | base_secret_backend.get_connections()
|
||||
| ^^^^^^^^^^^^^^^ AIR302
|
||||
35 |
|
||||
36 | csm_backend = CloudSecretManagerBackend()
|
||||
|
|
||||
= help: Use `get_connection` instead
|
||||
|
||||
AIR302_class_attribute.py:37:13: AIR302 `get_conn_uri` is removed in Airflow 3.0
|
||||
|
|
||||
36 | csm_backend = CloudSecretManagerBackend()
|
||||
37 | csm_backend.get_conn_uri()
|
||||
| ^^^^^^^^^^^^ AIR302
|
||||
38 | csm_backend.get_connections()
|
||||
|
|
||||
= help: Use `get_conn_value` instead
|
||||
|
||||
AIR302_class_attribute.py:38:13: AIR302 `get_connections` is removed in Airflow 3.0
|
||||
|
|
||||
36 | csm_backend = CloudSecretManagerBackend()
|
||||
37 | csm_backend.get_conn_uri()
|
||||
38 | csm_backend.get_connections()
|
||||
| ^^^^^^^^^^^^^^^ AIR302
|
||||
39 |
|
||||
40 | vault_backend = VaultBackend()
|
||||
|
|
||||
= help: Use `get_connection` instead
|
||||
|
||||
AIR302_class_attribute.py:41:15: AIR302 `get_conn_uri` is removed in Airflow 3.0
|
||||
|
|
||||
40 | vault_backend = VaultBackend()
|
||||
41 | vault_backend.get_conn_uri()
|
||||
| ^^^^^^^^^^^^ AIR302
|
||||
42 | vault_backend.get_connections()
|
||||
|
|
||||
= help: Use `get_conn_value` instead
|
||||
|
||||
AIR302_class_attribute.py:42:15: AIR302 `get_connections` is removed in Airflow 3.0
|
||||
|
|
||||
40 | vault_backend = VaultBackend()
|
||||
41 | vault_backend.get_conn_uri()
|
||||
42 | vault_backend.get_connections()
|
||||
| ^^^^^^^^^^^^^^^ AIR302
|
||||
43 |
|
||||
44 | not_an_error = NotAir302SecretError()
|
||||
|
|
||||
= help: Use `get_connection` instead
|
||||
|
||||
AIR302_class_attribute.py:54:18: AIR302 `dataset_factories` is removed in Airflow 3.0
|
||||
|
|
||||
53 | provider_manager = ProvidersManager()
|
||||
54 | provider_manager.dataset_factories
|
||||
| ^^^^^^^^^^^^^^^^^ AIR302
|
||||
55 | provider_manager.dataset_uri_handlers
|
||||
56 | provider_manager.dataset_to_openlineage_converters
|
||||
|
|
||||
= help: Use `asset_factories` instead
|
||||
|
||||
AIR302_class_attribute.py:55:18: AIR302 `dataset_uri_handlers` is removed in Airflow 3.0
|
||||
|
|
||||
53 | provider_manager = ProvidersManager()
|
||||
54 | provider_manager.dataset_factories
|
||||
55 | provider_manager.dataset_uri_handlers
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
56 | provider_manager.dataset_to_openlineage_converters
|
||||
|
|
||||
= help: Use `asset_uri_handlers` instead
|
||||
|
||||
AIR302_class_attribute.py:56:18: AIR302 `dataset_to_openlineage_converters` is removed in Airflow 3.0
|
||||
|
|
||||
54 | provider_manager.dataset_factories
|
||||
55 | provider_manager.dataset_uri_handlers
|
||||
56 | provider_manager.dataset_to_openlineage_converters
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR302
|
||||
57 |
|
||||
58 | dl_info = DatasetLineageInfo()
|
||||
|
|
||||
= help: Use `asset_to_openlineage_converters` instead
|
||||
|
||||
AIR302_class_attribute.py:58:11: AIR302 `airflow.lineage.hook.DatasetLineageInfo` is removed in Airflow 3.0
|
||||
|
|
||||
56 | provider_manager.dataset_to_openlineage_converters
|
||||
57 |
|
||||
58 | dl_info = DatasetLineageInfo()
|
||||
| ^^^^^^^^^^^^^^^^^^ AIR302
|
||||
59 | dl_info.dataset
|
||||
|
|
||||
= help: Use `airflow.lineage.hook.AssetLineageInfo` instead
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,163 +2,73 @@
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
AIR303.py:20:1: AIR303 Import path `airflow.api.auth.backend.basic_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:10:1: AIR303 Import path `airflow.api.auth.backend.basic_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
19 | # apache-airflow-providers-fab
|
||||
20 | basic_auth, kerberos_auth
|
||||
8 | from airflow.www.security import FabAirflowSecurityManagerOverride
|
||||
9 |
|
||||
10 | basic_auth, kerberos_auth
|
||||
| ^^^^^^^^^^ AIR303
|
||||
21 | auth_current_user
|
||||
22 | backend_kerberos_auth
|
||||
11 | auth_current_user
|
||||
12 | backend_kerberos_auth
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and import from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
|
||||
|
||||
AIR303.py:20:13: AIR303 Import path `airflow.api.auth.backend.kerberos_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:10:13: AIR303 Import path `airflow.api.auth.backend.kerberos_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
19 | # apache-airflow-providers-fab
|
||||
20 | basic_auth, kerberos_auth
|
||||
8 | from airflow.www.security import FabAirflowSecurityManagerOverride
|
||||
9 |
|
||||
10 | basic_auth, kerberos_auth
|
||||
| ^^^^^^^^^^^^^ AIR303
|
||||
21 | auth_current_user
|
||||
22 | backend_kerberos_auth
|
||||
11 | auth_current_user
|
||||
12 | backend_kerberos_auth
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and import from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
|
||||
|
||||
AIR303.py:21:1: AIR303 Import path `airflow.api.auth.backend.basic_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:11:1: AIR303 Import path `airflow.api.auth.backend.basic_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
19 | # apache-airflow-providers-fab
|
||||
20 | basic_auth, kerberos_auth
|
||||
21 | auth_current_user
|
||||
10 | basic_auth, kerberos_auth
|
||||
11 | auth_current_user
|
||||
| ^^^^^^^^^^^^^^^^^ AIR303
|
||||
22 | backend_kerberos_auth
|
||||
23 | fab_override
|
||||
12 | backend_kerberos_auth
|
||||
13 | fab_override
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and import from `airflow.providers.fab.auth_manager.api.auth.backend.basic_auth` instead.
|
||||
|
||||
AIR303.py:22:1: AIR303 Import path `airflow.auth_manager.api.auth.backend.kerberos_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:12:1: AIR303 Import path `airflow.auth_manager.api.auth.backend.kerberos_auth` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
20 | basic_auth, kerberos_auth
|
||||
21 | auth_current_user
|
||||
22 | backend_kerberos_auth
|
||||
10 | basic_auth, kerberos_auth
|
||||
11 | auth_current_user
|
||||
12 | backend_kerberos_auth
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
23 | fab_override
|
||||
13 | fab_override
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and import from `airflow.providers.fab.auth_manager.api.auth.backend.kerberos_auth` instead.
|
||||
|
||||
AIR303.py:23:1: AIR303 Import path `airflow.auth.managers.fab.security_managr.override` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:13:1: AIR303 Import path `airflow.auth.managers.fab.security_manager.override` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
21 | auth_current_user
|
||||
22 | backend_kerberos_auth
|
||||
23 | fab_override
|
||||
11 | auth_current_user
|
||||
12 | backend_kerberos_auth
|
||||
13 | fab_override
|
||||
| ^^^^^^^^^^^^ AIR303
|
||||
24 |
|
||||
25 | FabAuthManager()
|
||||
14 |
|
||||
15 | FabAuthManager
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and import from `airflow.providers.fab.auth_manager.security_manager.override` instead.
|
||||
|
||||
AIR303.py:25:1: AIR303 `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:15:1: AIR303 `airflow.auth.managers.fab.fab_auth_manager.FabAuthManager` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
23 | fab_override
|
||||
24 |
|
||||
25 | FabAuthManager()
|
||||
13 | fab_override
|
||||
14 |
|
||||
15 | FabAuthManager
|
||||
| ^^^^^^^^^^^^^^ AIR303
|
||||
26 | FabAirflowSecurityManagerOverride()
|
||||
16 | FabAirflowSecurityManagerOverride
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.security_manager.FabAuthManager` instead.
|
||||
|
||||
AIR303.py:26:1: AIR303 `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
|
||||
AIR303.py:16:1: AIR303 `airflow.www.security.FabAirflowSecurityManagerOverride` is moved into `fab` provider in Airflow 3.0;
|
||||
|
|
||||
25 | FabAuthManager()
|
||||
26 | FabAirflowSecurityManagerOverride()
|
||||
15 | FabAuthManager
|
||||
16 | FabAirflowSecurityManagerOverride
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
27 |
|
||||
28 | # apache-airflow-providers-celery
|
||||
|
|
||||
= help: Install `apache-airflow-provider-fab>=1.0.0` and use `airflow.providers.fab.auth_manager.security_manager.override.FabAirflowSecurityManagerOverride` instead.
|
||||
|
||||
AIR303.py:29:1: AIR303 Import path `airflow.config_templates.default_celery.DEFAULT_CELERY_CONFIG` is moved into `celery` provider in Airflow 3.0;
|
||||
|
|
||||
28 | # apache-airflow-providers-celery
|
||||
29 | DEFAULT_CELERY_CONFIG
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
30 | app
|
||||
|
|
||||
= help: Install `apache-airflow-provider-celery>=3.3.0` and import from `airflow.providers.celery.executors.default_celery.DEFAULT_CELERY_CONFIG` instead.
|
||||
|
||||
AIR303.py:30:1: AIR303 Import path `airflow.executors.celery_executor.app` is moved into `celery` provider in Airflow 3.0;
|
||||
|
|
||||
28 | # apache-airflow-providers-celery
|
||||
29 | DEFAULT_CELERY_CONFIG
|
||||
30 | app
|
||||
| ^^^ AIR303
|
||||
31 |
|
||||
32 | # apache-airflow-providers-common-sql
|
||||
|
|
||||
= help: Install `apache-airflow-provider-celery>=3.3.0` and import from `airflow.providers.celery.executors.celery_executor_utils.app` instead.
|
||||
|
||||
AIR303.py:33:1: AIR303 Import path `airflow.hooks.dbapi.ConnectorProtocol` is moved into `Common SQL` provider in Airflow 3.0;
|
||||
|
|
||||
32 | # apache-airflow-providers-common-sql
|
||||
33 | ConnectorProtocol()
|
||||
| ^^^^^^^^^^^^^^^^^ AIR303
|
||||
34 | DbApiHook()
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Common SQL>=1.0.0` and import from `airflow.providers.common.sql.hooks.sql.ConnectorProtocol` instead.
|
||||
|
||||
AIR303.py:34:1: AIR303 Import path `airflow.hooks.dbapi.DbApiHook` is moved into `Common SQL` provider in Airflow 3.0;
|
||||
|
|
||||
32 | # apache-airflow-providers-common-sql
|
||||
33 | ConnectorProtocol()
|
||||
34 | DbApiHook()
|
||||
| ^^^^^^^^^ AIR303
|
||||
35 |
|
||||
36 | # apache-airflow-providers-cncf-kubernetes
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Common SQL>=1.0.0` and import from `airflow.providers.common.sql.hooks.sql.DbApiHook` instead.
|
||||
|
||||
AIR303.py:37:1: AIR303 Import path `airflow.executors.kubernetes_executor_types.ALL_NAMESPACES` is moved into `Kubernetes` provider in Airflow 3.0;
|
||||
|
|
||||
36 | # apache-airflow-providers-cncf-kubernetes
|
||||
37 | ALL_NAMESPACES
|
||||
| ^^^^^^^^^^^^^^ AIR303
|
||||
38 | POD_EXECUTOR_DONE_KEY
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Kubernetes>=7.4.0` and import from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.ALL_NAMESPACES` instead.
|
||||
|
||||
AIR303.py:38:1: AIR303 Import path `airflow.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` is moved into `Kubernetes` provider in Airflow 3.0;
|
||||
|
|
||||
36 | # apache-airflow-providers-cncf-kubernetes
|
||||
37 | ALL_NAMESPACES
|
||||
38 | POD_EXECUTOR_DONE_KEY
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
39 |
|
||||
40 | # apache-airflow-providers-apache-hive
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Kubernetes>=7.4.0` and import from `airflow.providers.cncf.kubernetes.executors.kubernetes_executor_types.POD_EXECUTOR_DONE_KEY` instead.
|
||||
|
||||
AIR303.py:41:1: AIR303 Import path `airflow.hooks.hive_hooks.HIVE_QUEUE_PRIORITIES` is moved into `Apache Hive` provider in Airflow 3.0;
|
||||
|
|
||||
40 | # apache-airflow-providers-apache-hive
|
||||
41 | HIVE_QUEUE_PRIORITIES
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
42 | closest_ds_partition()
|
||||
43 | max_partition()
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Apache Hive>=1.0.0` and import from `airflow.providers.apache.hive.hooks.hive.HIVE_QUEUE_PRIORITIES` instead.
|
||||
|
||||
AIR303.py:42:1: AIR303 Import path `airflow.macros.hive.closest_ds_partition` is moved into `Apache Hive` provider in Airflow 3.0;
|
||||
|
|
||||
40 | # apache-airflow-providers-apache-hive
|
||||
41 | HIVE_QUEUE_PRIORITIES
|
||||
42 | closest_ds_partition()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ AIR303
|
||||
43 | max_partition()
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Apache Hive>=5.1.0` and import from `airflow.providers.apache.hive.macros.hive.closest_ds_partition` instead.
|
||||
|
||||
AIR303.py:43:1: AIR303 Import path `airflow.macros.hive.max_partition` is moved into `Apache Hive` provider in Airflow 3.0;
|
||||
|
|
||||
41 | HIVE_QUEUE_PRIORITIES
|
||||
42 | closest_ds_partition()
|
||||
43 | max_partition()
|
||||
| ^^^^^^^^^^^^^ AIR303
|
||||
|
|
||||
= help: Install `apache-airflow-provider-Apache Hive>=5.1.0` and import from `airflow.providers.apache.hive.macros.hive.max_partition` instead.
|
||||
|
||||
@@ -99,15 +99,9 @@ pub(crate) fn fastapi_non_annotated_dependency(
|
||||
|
||||
let mut updatable_count = 0;
|
||||
let mut has_non_updatable_default = false;
|
||||
let total_params =
|
||||
function_def.parameters.args.len() + function_def.parameters.kwonlyargs.len();
|
||||
let total_params = function_def.parameters.args.len();
|
||||
|
||||
for parameter in function_def
|
||||
.parameters
|
||||
.args
|
||||
.iter()
|
||||
.chain(&function_def.parameters.kwonlyargs)
|
||||
{
|
||||
for parameter in &function_def.parameters.args {
|
||||
let needs_update = matches!(
|
||||
(¶meter.parameter.annotation, ¶meter.default),
|
||||
(Some(_annotation), Some(default)) if is_fastapi_dependency(checker, default)
|
||||
|
||||
@@ -7,7 +7,7 @@ mod fastapi_redundant_response_model;
|
||||
mod fastapi_unused_path_parameter;
|
||||
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_semantic::analyze::typing::resolve_assignment;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
/// Returns `true` if the function is a FastAPI route.
|
||||
@@ -41,11 +41,11 @@ pub(crate) fn is_fastapi_route_call(call_expr: &ast::ExprCall, semantic: &Semant
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return false;
|
||||
};
|
||||
let Some(binding_id) = semantic.resolve_name(name) else {
|
||||
return false;
|
||||
};
|
||||
typing::is_fastapi_route(semantic.binding(binding_id), semantic)
|
||||
|
||||
resolve_assignment(value, semantic).is_some_and(|qualified_name| {
|
||||
matches!(
|
||||
qualified_name.segments(),
|
||||
["fastapi", "FastAPI" | "APIRouter"]
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/fastapi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
FAST002.py:24:5: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
|
|
||||
@@ -321,40 +320,13 @@ FAST002.py:53:5: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
55 56 | limit: int = 10,
|
||||
56 57 | ):
|
||||
|
||||
FAST002.py:61:25: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
FAST002.py:67:5: FAST002 FastAPI dependency without `Annotated`
|
||||
|
|
||||
60 | @app.get("/items/{item_id}")
|
||||
61 | async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002
|
||||
62 | pass
|
||||
|
|
||||
= help: Replace with `typing.Annotated`
|
||||
|
||||
ℹ Unsafe fix
|
||||
12 12 | Security,
|
||||
13 13 | )
|
||||
14 14 | from pydantic import BaseModel
|
||||
15 |+from typing import Annotated
|
||||
15 16 |
|
||||
16 17 | app = FastAPI()
|
||||
17 18 | router = APIRouter()
|
||||
--------------------------------------------------------------------------------
|
||||
58 59 |
|
||||
59 60 |
|
||||
60 61 | @app.get("/items/{item_id}")
|
||||
61 |-async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
|
||||
62 |+async def read_items(*, item_id: Annotated[int, Path(title="The ID of the item to get")], q: str):
|
||||
62 63 | pass
|
||||
63 64 |
|
||||
64 65 | # Non fixable errors
|
||||
|
||||
FAST002.py:70:5: FAST002 FastAPI dependency without `Annotated`
|
||||
|
|
||||
68 | skip: int = 0,
|
||||
69 | limit: int = 10,
|
||||
70 | current_user: User = Depends(get_current_user),
|
||||
65 | skip: int = 0,
|
||||
66 | limit: int = 10,
|
||||
67 | current_user: User = Depends(get_current_user),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002
|
||||
71 | ):
|
||||
72 | pass
|
||||
68 | ):
|
||||
69 | pass
|
||||
|
|
||||
= help: Replace with `typing.Annotated`
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/fastapi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
FAST002.py:24:5: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
|
|
||||
@@ -321,40 +320,13 @@ FAST002.py:53:5: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
55 56 | limit: int = 10,
|
||||
56 57 | ):
|
||||
|
||||
FAST002.py:61:25: FAST002 [*] FastAPI dependency without `Annotated`
|
||||
FAST002.py:67:5: FAST002 FastAPI dependency without `Annotated`
|
||||
|
|
||||
60 | @app.get("/items/{item_id}")
|
||||
61 | async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002
|
||||
62 | pass
|
||||
|
|
||||
= help: Replace with `typing_extensions.Annotated`
|
||||
|
||||
ℹ Unsafe fix
|
||||
12 12 | Security,
|
||||
13 13 | )
|
||||
14 14 | from pydantic import BaseModel
|
||||
15 |+from typing_extensions import Annotated
|
||||
15 16 |
|
||||
16 17 | app = FastAPI()
|
||||
17 18 | router = APIRouter()
|
||||
--------------------------------------------------------------------------------
|
||||
58 59 |
|
||||
59 60 |
|
||||
60 61 | @app.get("/items/{item_id}")
|
||||
61 |-async def read_items(*, item_id: int = Path(title="The ID of the item to get"), q: str):
|
||||
62 |+async def read_items(*, item_id: Annotated[int, Path(title="The ID of the item to get")], q: str):
|
||||
62 63 | pass
|
||||
63 64 |
|
||||
64 65 | # Non fixable errors
|
||||
|
||||
FAST002.py:70:5: FAST002 FastAPI dependency without `Annotated`
|
||||
|
|
||||
68 | skip: int = 0,
|
||||
69 | limit: int = 10,
|
||||
70 | current_user: User = Depends(get_current_user),
|
||||
65 | skip: int = 0,
|
||||
66 | limit: int = 10,
|
||||
67 | current_user: User = Depends(get_current_user),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FAST002
|
||||
71 | ):
|
||||
72 | pass
|
||||
68 | ):
|
||||
69 | pass
|
||||
|
|
||||
= help: Replace with `typing_extensions.Annotated`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/fastapi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
FAST001.py:17:22: FAST001 [*] FastAPI route with redundant `response_model` argument
|
||||
|
|
||||
@@ -171,25 +172,4 @@ FAST001.py:53:24: FAST001 [*] FastAPI route with redundant `response_model` argu
|
||||
53 |+@router.get("/items/")
|
||||
54 54 | async def create_item(item: Item) -> Item:
|
||||
55 55 | return item
|
||||
56 56 |
|
||||
|
||||
FAST001.py:118:23: FAST001 [*] FastAPI route with redundant `response_model` argument
|
||||
|
|
||||
116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
|
||||
117 | # Error
|
||||
118 | @app_arg.get("/", response_model=str)
|
||||
| ^^^^^^^^^^^^^^^^^^ FAST001
|
||||
119 | async def get_root() -> str:
|
||||
120 | return "Hello World!"
|
||||
|
|
||||
= help: Remove argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
115 115 |
|
||||
116 116 | def setup_app(app_arg: FastAPI, non_app: str) -> None:
|
||||
117 117 | # Error
|
||||
118 |- @app_arg.get("/", response_model=str)
|
||||
118 |+ @app_arg.get("/")
|
||||
119 119 | async def get_root() -> str:
|
||||
120 120 | return "Hello World!"
|
||||
121 121 |
|
||||
56 56 |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -115,22 +115,16 @@ pub(crate) fn unnecessary_dict_comprehension(
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &generator.target else {
|
||||
return;
|
||||
};
|
||||
let [Expr::Name(ast::ExprName { id: target_key, .. }), Expr::Name(ast::ExprName {
|
||||
id: target_value, ..
|
||||
})] = elts.as_slice()
|
||||
else {
|
||||
let [target_key, target_value] = elts.as_slice() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Name(ast::ExprName { id: key, .. }) = &key else {
|
||||
if ComparableExpr::from(key) != ComparableExpr::from(target_key) {
|
||||
return;
|
||||
};
|
||||
let Expr::Name(ast::ExprName { id: value, .. }) = &value else {
|
||||
return;
|
||||
};
|
||||
|
||||
if target_key == key && target_value == value {
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
if ComparableExpr::from(value) != ComparableExpr::from(target_value) {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
|
||||
/// C416
|
||||
@@ -146,83 +140,10 @@ pub(crate) fn unnecessary_list_set_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
if is_dict_items(checker, &generator.iter) {
|
||||
match (&generator.target, elt) {
|
||||
// [(k, v) for k, v in dict.items()] or [(k, v) for [k, v] in dict.items()]
|
||||
(
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: target_elts, ..
|
||||
})
|
||||
| Expr::List(ast::ExprList {
|
||||
elts: target_elts, ..
|
||||
}),
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }),
|
||||
) => {
|
||||
let [Expr::Name(ast::ExprName { id: target_key, .. }), Expr::Name(ast::ExprName {
|
||||
id: target_value, ..
|
||||
})] = target_elts.as_slice()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let [Expr::Name(ast::ExprName { id: key, .. }), Expr::Name(ast::ExprName { id: value, .. })] =
|
||||
elts.as_slice()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if target_key == key && target_value == value {
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
}
|
||||
// [x for x in dict.items()]
|
||||
(
|
||||
Expr::Name(ast::ExprName {
|
||||
id: target_name, ..
|
||||
}),
|
||||
Expr::Name(ast::ExprName { id: elt_name, .. }),
|
||||
) if target_name == elt_name => {
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// [x for x in iterable]
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: target_name, ..
|
||||
}) = &generator.target
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Expr::Name(ast::ExprName { id: elt_name, .. }) = &elt else {
|
||||
return;
|
||||
};
|
||||
if elt_name == target_name {
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
if ComparableExpr::from(elt) != ComparableExpr::from(&generator.target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dict_items(checker: &Checker, expr: &Expr) -> bool {
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if attr.as_str() != "items" {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Expr::Name(name) = value.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(id) = checker.semantic().resolve_name(name) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
typing::is_dict(checker.semantic().binding(id), checker.semantic())
|
||||
add_diagnostic(checker, expr);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
C416.py:6:1: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
|
|
||||
@@ -62,7 +61,7 @@ C416.py:8:1: C416 [*] Unnecessary dict comprehension (rewrite using `dict()`)
|
||||
8 |+dict(y)
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | [(k, v) for [k, v] in d.items()]
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
C416.py:9:1: C416 [*] Unnecessary dict comprehension (rewrite using `dict()`)
|
||||
|
|
||||
@@ -71,7 +70,7 @@ C416.py:9:1: C416 [*] Unnecessary dict comprehension (rewrite using `dict()`)
|
||||
9 | {k: v for k, v in d.items()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
11 | [(k, v) for [k, v] in d.items()]
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
@@ -82,8 +81,8 @@ C416.py:9:1: C416 [*] Unnecessary dict comprehension (rewrite using `dict()`)
|
||||
9 |-{k: v for k, v in d.items()}
|
||||
9 |+dict(d.items())
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | [(k, v) for [k, v] in d.items()]
|
||||
12 12 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
12 12 |
|
||||
|
||||
C416.py:10:1: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
|
|
||||
@@ -91,8 +90,7 @@ C416.py:10:1: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
9 | {k: v for k, v in d.items()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
11 | [(k, v) for [k, v] in d.items()]
|
||||
12 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
@@ -102,46 +100,42 @@ C416.py:10:1: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 |-[(k, v) for k, v in d.items()]
|
||||
10 |+list(d.items())
|
||||
11 11 | [(k, v) for [k, v] in d.items()]
|
||||
12 12 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
13 13 |
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
12 12 |
|
||||
13 13 | [i for i, in z]
|
||||
|
||||
C416.py:11:1: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
C416.py:11:1: C416 [*] Unnecessary dict comprehension (rewrite using `dict()`)
|
||||
|
|
||||
9 | {k: v for k, v in d.items()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
11 | [(k, v) for [k, v] in d.items()]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
12 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
12 |
|
||||
13 | [i for i, in z]
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | {k: v for k, v in y}
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 |-[(k, v) for [k, v] in d.items()]
|
||||
11 |+list(d.items())
|
||||
12 12 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
13 13 |
|
||||
14 14 | [i for i, in z]
|
||||
11 |-{k: (a, b) for k, (a, b) in d.items()}
|
||||
11 |+dict(d.items())
|
||||
12 12 |
|
||||
13 13 | [i for i, in z]
|
||||
14 14 | [i for i, j in y]
|
||||
|
||||
C416.py:25:70: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
C416.py:24:70: C416 [*] Unnecessary list comprehension (rewrite using `list()`)
|
||||
|
|
||||
24 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
25 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
24 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
26 |
|
||||
27 | zz = [[1], [2], [3]]
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 | {k: v if v else None for k, v in y}
|
||||
23 23 |
|
||||
24 24 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
25 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
25 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))
|
||||
26 26 |
|
||||
27 27 | zz = [[1], [2], [3]]
|
||||
28 28 | [(i,) for (i,) in zz] # != list(zz)
|
||||
21 21 | {k: v if v else None for k, v in y}
|
||||
22 22 |
|
||||
23 23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
24 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
24 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))
|
||||
|
||||
@@ -101,13 +101,13 @@ impl From<&Expr> for ConstantLikelihood {
|
||||
.map(ConstantLikelihood::from)
|
||||
.min()
|
||||
.unwrap_or(ConstantLikelihood::Definitely),
|
||||
Expr::Dict(dict) => dict
|
||||
.items
|
||||
.iter()
|
||||
.flat_map(|item| std::iter::once(&item.value).chain(item.key.as_ref()))
|
||||
.map(ConstantLikelihood::from)
|
||||
.min()
|
||||
.unwrap_or(ConstantLikelihood::Definitely),
|
||||
Expr::Dict(dict) => {
|
||||
if dict.is_empty() {
|
||||
ConstantLikelihood::Definitely
|
||||
} else {
|
||||
ConstantLikelihood::Probably
|
||||
}
|
||||
}
|
||||
Expr::BinOp(ast::ExprBinOp { left, right, .. }) => cmp::min(
|
||||
ConstantLikelihood::from(&**left),
|
||||
ConstantLikelihood::from(&**right),
|
||||
|
||||
@@ -269,7 +269,7 @@ SIM300.py:14:1: SIM300 [*] Yoda condition detected
|
||||
14 |+(number - 100) > 0 # SIM300
|
||||
15 15 | B<A[0][0]or B
|
||||
16 16 | B or(B)<A[0][0]
|
||||
17 17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
17 17 |
|
||||
|
||||
SIM300.py:15:1: SIM300 [*] Yoda condition detected
|
||||
|
|
||||
@@ -278,7 +278,6 @@ SIM300.py:15:1: SIM300 [*] Yoda condition detected
|
||||
15 | B<A[0][0]or B
|
||||
| ^^^^^^^^^ SIM300
|
||||
16 | B or(B)<A[0][0]
|
||||
17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
|
|
||||
= help: Rewrite as `A[0][0] > B`
|
||||
|
||||
@@ -289,8 +288,8 @@ SIM300.py:15:1: SIM300 [*] Yoda condition detected
|
||||
15 |-B<A[0][0]or B
|
||||
15 |+A[0][0] > B or B
|
||||
16 16 | B or(B)<A[0][0]
|
||||
17 17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
18 18 |
|
||||
17 17 |
|
||||
18 18 | # Errors in preview
|
||||
|
||||
SIM300.py:16:5: SIM300 [*] Yoda condition detected
|
||||
|
|
||||
@@ -298,7 +297,8 @@ SIM300.py:16:5: SIM300 [*] Yoda condition detected
|
||||
15 | B<A[0][0]or B
|
||||
16 | B or(B)<A[0][0]
|
||||
| ^^^^^^^^^^^ SIM300
|
||||
17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
17 |
|
||||
18 | # Errors in preview
|
||||
|
|
||||
= help: Rewrite as `A[0][0] > (B)`
|
||||
|
||||
@@ -308,67 +308,46 @@ SIM300.py:16:5: SIM300 [*] Yoda condition detected
|
||||
15 15 | B<A[0][0]or B
|
||||
16 |-B or(B)<A[0][0]
|
||||
16 |+B or A[0][0] > (B)
|
||||
17 17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
18 18 |
|
||||
19 19 | # Errors in preview
|
||||
17 17 |
|
||||
18 18 | # Errors in preview
|
||||
19 19 | ['upper'] == UPPER_LIST
|
||||
|
||||
SIM300.py:17:1: SIM300 [*] Yoda condition detected
|
||||
SIM300.py:19:1: SIM300 [*] Yoda condition detected
|
||||
|
|
||||
15 | B<A[0][0]or B
|
||||
16 | B or(B)<A[0][0]
|
||||
17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM300
|
||||
18 |
|
||||
19 | # Errors in preview
|
||||
|
|
||||
= help: Rewrite as `DummyHandler.CONFIG == {"non-empty-dict": "is-ok"}`
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | 0 < (number - 100) # SIM300
|
||||
15 15 | B<A[0][0]or B
|
||||
16 16 | B or(B)<A[0][0]
|
||||
17 |-{"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
17 |+DummyHandler.CONFIG == {"non-empty-dict": "is-ok"}
|
||||
18 18 |
|
||||
19 19 | # Errors in preview
|
||||
20 20 | ['upper'] == UPPER_LIST
|
||||
|
||||
SIM300.py:20:1: SIM300 [*] Yoda condition detected
|
||||
|
|
||||
19 | # Errors in preview
|
||||
20 | ['upper'] == UPPER_LIST
|
||||
18 | # Errors in preview
|
||||
19 | ['upper'] == UPPER_LIST
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM300
|
||||
21 | {} == DummyHandler.CONFIG
|
||||
20 | {} == DummyHandler.CONFIG
|
||||
|
|
||||
= help: Rewrite as `UPPER_LIST == ['upper']`
|
||||
|
||||
ℹ Safe fix
|
||||
17 17 | {"non-empty-dict": "is-ok"} == DummyHandler.CONFIG
|
||||
18 18 |
|
||||
19 19 | # Errors in preview
|
||||
20 |-['upper'] == UPPER_LIST
|
||||
20 |+UPPER_LIST == ['upper']
|
||||
21 21 | {} == DummyHandler.CONFIG
|
||||
22 22 |
|
||||
23 23 | # Errors in stable
|
||||
16 16 | B or(B)<A[0][0]
|
||||
17 17 |
|
||||
18 18 | # Errors in preview
|
||||
19 |-['upper'] == UPPER_LIST
|
||||
19 |+UPPER_LIST == ['upper']
|
||||
20 20 | {} == DummyHandler.CONFIG
|
||||
21 21 |
|
||||
22 22 | # Errors in stable
|
||||
|
||||
SIM300.py:21:1: SIM300 [*] Yoda condition detected
|
||||
SIM300.py:20:1: SIM300 [*] Yoda condition detected
|
||||
|
|
||||
19 | # Errors in preview
|
||||
20 | ['upper'] == UPPER_LIST
|
||||
21 | {} == DummyHandler.CONFIG
|
||||
18 | # Errors in preview
|
||||
19 | ['upper'] == UPPER_LIST
|
||||
20 | {} == DummyHandler.CONFIG
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM300
|
||||
22 |
|
||||
23 | # Errors in stable
|
||||
21 |
|
||||
22 | # Errors in stable
|
||||
|
|
||||
= help: Rewrite as `DummyHandler.CONFIG == {}`
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 |
|
||||
19 19 | # Errors in preview
|
||||
20 20 | ['upper'] == UPPER_LIST
|
||||
21 |-{} == DummyHandler.CONFIG
|
||||
21 |+DummyHandler.CONFIG == {}
|
||||
22 22 |
|
||||
23 23 | # Errors in stable
|
||||
24 24 | UPPER_LIST == ['upper']
|
||||
17 17 |
|
||||
18 18 | # Errors in preview
|
||||
19 19 | ['upper'] == UPPER_LIST
|
||||
20 |-{} == DummyHandler.CONFIG
|
||||
20 |+DummyHandler.CONFIG == {}
|
||||
21 21 |
|
||||
22 22 | # Errors in stable
|
||||
23 23 | UPPER_LIST == ['upper']
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{AstNode, Expr, ExprBinOp, ExprCall, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::{remove_argument, Parentheses};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pathlib.Path` objects that are initialized with the current
|
||||
@@ -50,17 +43,7 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
|
||||
}
|
||||
|
||||
/// PTH201
|
||||
pub(crate) fn path_constructor_current_directory(checker: &mut Checker, call: &ExprCall) {
|
||||
let applicability = |range| {
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
}
|
||||
};
|
||||
|
||||
let (func, arguments) = (&call.func, &call.arguments);
|
||||
|
||||
pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
@@ -71,75 +54,21 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, call: &E
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Call(ExprCall { arguments, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [Expr::StringLiteral(arg)] = &*arguments.args else {
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !matches!(arg.value.to_str(), "" | ".") {
|
||||
return;
|
||||
if matches!(value.to_str(), "" | ".") {
|
||||
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(*range)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, arg.range());
|
||||
|
||||
match parent_and_next_path_fragment_range(
|
||||
checker.semantic(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
Some((parent_range, next_fragment_range)) => {
|
||||
let next_fragment_expr = checker.locator().slice(next_fragment_range);
|
||||
let call_expr = checker.locator().slice(call.range());
|
||||
|
||||
let relative_argument_range: Range<usize> = {
|
||||
let range = arg.range() - call.start();
|
||||
range.start().into()..range.end().into()
|
||||
};
|
||||
|
||||
let mut new_call_expr = call_expr.to_string();
|
||||
new_call_expr.replace_range(relative_argument_range, next_fragment_expr);
|
||||
|
||||
let edit = Edit::range_replacement(new_call_expr, parent_range);
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(edit, applicability(parent_range)));
|
||||
}
|
||||
None => diagnostic.try_set_fix(|| {
|
||||
let edit = remove_argument(arg, arguments, Parentheses::Preserve, checker.source())?;
|
||||
Ok(Fix::applicable_edit(edit, applicability(call.range())))
|
||||
}),
|
||||
};
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn parent_and_next_path_fragment_range(
|
||||
semantic: &SemanticModel,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Option<(TextRange, TextRange)> {
|
||||
let parent = semantic.current_expression_parent()?;
|
||||
|
||||
let Expr::BinOp(parent @ ExprBinOp { op, right, .. }) = parent else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let range = right.range();
|
||||
|
||||
if !matches!(op, Operator::Div) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
parent.range(),
|
||||
parenthesized_range(
|
||||
right.into(),
|
||||
parent.as_any_node_ref(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(range),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2,329 +2,84 @@
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PTH201.py:6:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:5:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
5 | # match
|
||||
6 | _ = Path(".")
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
| ^^^ PTH201
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from pathlib import Path as pth
|
||||
3 3 |
|
||||
4 4 | # match
|
||||
5 |-_ = Path(".")
|
||||
5 |+_ = Path()
|
||||
6 6 | _ = pth(".")
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 | _ = Path("")
|
||||
|
||||
PTH201.py:6:9: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
| ^^^ PTH201
|
||||
7 | _ = PurePath(".")
|
||||
8 | _ = Path("")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 |
|
||||
5 5 | # match
|
||||
6 |-_ = Path(".")
|
||||
6 |+_ = Path()
|
||||
7 7 | _ = pth(".")
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 |-_ = pth(".")
|
||||
6 |+_ = pth()
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 | _ = Path("")
|
||||
9 9 |
|
||||
|
||||
PTH201.py:7:9: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:7:14: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
5 | # match
|
||||
6 | _ = Path(".")
|
||||
7 | _ = pth(".")
|
||||
| ^^^ PTH201
|
||||
8 | _ = PurePath(".")
|
||||
9 | _ = Path("")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 | # match
|
||||
6 6 | _ = Path(".")
|
||||
7 |-_ = pth(".")
|
||||
7 |+_ = pth()
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
|
||||
PTH201.py:8:14: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
6 | _ = Path(".")
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
| ^^^ PTH201
|
||||
9 | _ = Path("")
|
||||
8 | _ = Path("")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | # match
|
||||
6 6 | _ = Path(".")
|
||||
7 7 | _ = pth(".")
|
||||
8 |-_ = PurePath(".")
|
||||
8 |+_ = PurePath()
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 6 | _ = pth(".")
|
||||
7 |-_ = PurePath(".")
|
||||
7 |+_ = PurePath()
|
||||
8 8 | _ = Path("")
|
||||
9 9 |
|
||||
10 10 | # no match
|
||||
|
||||
PTH201.py:9:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:8:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
9 | _ = Path("")
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
8 | _ = Path("")
|
||||
| ^^ PTH201
|
||||
10 |
|
||||
11 | Path('', )
|
||||
9 |
|
||||
10 | # no match
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | _ = Path(".")
|
||||
7 7 | _ = pth(".")
|
||||
8 8 | _ = PurePath(".")
|
||||
9 |-_ = Path("")
|
||||
9 |+_ = Path()
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
12 12 |
|
||||
|
||||
PTH201.py:11:6: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
9 | _ = Path("")
|
||||
10 |
|
||||
11 | Path('', )
|
||||
| ^^ PTH201
|
||||
12 |
|
||||
13 | Path(
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
11 |-Path('', )
|
||||
11 |+Path()
|
||||
12 12 |
|
||||
13 13 | Path(
|
||||
14 14 | '',
|
||||
|
||||
PTH201.py:14:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
13 | Path(
|
||||
14 | '',
|
||||
| ^^ PTH201
|
||||
15 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
12 12 |
|
||||
13 |-Path(
|
||||
14 |- '',
|
||||
15 |-)
|
||||
13 |+Path()
|
||||
16 14 |
|
||||
17 15 | Path( # Comment before argument
|
||||
18 16 | '',
|
||||
|
||||
PTH201.py:18:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
17 | Path( # Comment before argument
|
||||
18 | '',
|
||||
| ^^ PTH201
|
||||
19 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
14 14 | '',
|
||||
15 15 | )
|
||||
16 16 |
|
||||
17 |-Path( # Comment before argument
|
||||
18 |- '',
|
||||
19 |-)
|
||||
17 |+Path()
|
||||
20 18 |
|
||||
21 19 | Path(
|
||||
22 20 | '', # EOL comment
|
||||
|
||||
PTH201.py:22:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
21 | Path(
|
||||
22 | '', # EOL comment
|
||||
| ^^ PTH201
|
||||
23 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
18 18 | '',
|
||||
19 19 | )
|
||||
20 20 |
|
||||
21 |-Path(
|
||||
22 |- '', # EOL comment
|
||||
23 |-)
|
||||
21 |+Path()
|
||||
24 22 |
|
||||
25 23 | Path(
|
||||
26 24 | '' # Comment in the middle of implicitly concatenated string
|
||||
|
||||
PTH201.py:26:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
25 | Path(
|
||||
26 | '' # Comment in the middle of implicitly concatenated string
|
||||
| _____^
|
||||
27 | | ".",
|
||||
| |_______^ PTH201
|
||||
28 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 | '', # EOL comment
|
||||
23 23 | )
|
||||
24 24 |
|
||||
25 |-Path(
|
||||
26 |- '' # Comment in the middle of implicitly concatenated string
|
||||
27 |- ".",
|
||||
28 |-)
|
||||
25 |+Path()
|
||||
29 26 |
|
||||
30 27 | Path(
|
||||
31 28 | '' # Comment before comma
|
||||
|
||||
PTH201.py:31:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
30 | Path(
|
||||
31 | '' # Comment before comma
|
||||
| ^^ PTH201
|
||||
32 | ,
|
||||
33 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
27 27 | ".",
|
||||
28 28 | )
|
||||
29 29 |
|
||||
30 |-Path(
|
||||
31 |- '' # Comment before comma
|
||||
32 |- ,
|
||||
33 |-)
|
||||
30 |+Path()
|
||||
34 31 |
|
||||
35 32 | Path(
|
||||
36 33 | '',
|
||||
|
||||
PTH201.py:36:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
35 | Path(
|
||||
36 | '',
|
||||
| ^^ PTH201
|
||||
37 | ) / "bare"
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | )
|
||||
34 34 |
|
||||
35 35 | Path(
|
||||
36 |- '',
|
||||
37 |-) / "bare"
|
||||
36 |+ "bare",
|
||||
37 |+)
|
||||
38 38 |
|
||||
39 39 | Path( # Comment before argument
|
||||
40 40 | '',
|
||||
|
||||
PTH201.py:40:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
39 | Path( # Comment before argument
|
||||
40 | '',
|
||||
| ^^ PTH201
|
||||
41 | ) / ("parenthesized")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 | ) / "bare"
|
||||
38 38 |
|
||||
39 39 | Path( # Comment before argument
|
||||
40 |- '',
|
||||
41 |-) / ("parenthesized")
|
||||
40 |+ ("parenthesized"),
|
||||
41 |+)
|
||||
42 42 |
|
||||
43 43 | Path(
|
||||
44 44 | '', # EOL comment
|
||||
|
||||
PTH201.py:44:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
43 | Path(
|
||||
44 | '', # EOL comment
|
||||
| ^^ PTH201
|
||||
45 | ) / ( ("double parenthesized" ) )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
41 41 | ) / ("parenthesized")
|
||||
42 42 |
|
||||
43 43 | Path(
|
||||
44 |- '', # EOL comment
|
||||
45 |-) / ( ("double parenthesized" ) )
|
||||
44 |+ ( ("double parenthesized" ) ), # EOL comment
|
||||
45 |+)
|
||||
46 46 |
|
||||
47 47 | ( Path(
|
||||
48 48 | '' # Comment in the middle of implicitly concatenated string
|
||||
|
||||
PTH201.py:48:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
47 | ( Path(
|
||||
48 | '' # Comment in the middle of implicitly concatenated string
|
||||
| _____^
|
||||
49 | | ".",
|
||||
| |_______^ PTH201
|
||||
50 | ) )/ (("parenthesized path call")
|
||||
51 | # Comment between closing parentheses
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
44 44 | '', # EOL comment
|
||||
45 45 | ) / ( ("double parenthesized" ) )
|
||||
46 46 |
|
||||
47 |-( Path(
|
||||
48 |- '' # Comment in the middle of implicitly concatenated string
|
||||
49 |- ".",
|
||||
50 |-) )/ (("parenthesized path call")
|
||||
47 |+Path(
|
||||
48 |+ (("parenthesized path call")
|
||||
51 49 | # Comment between closing parentheses
|
||||
50 |+),
|
||||
52 51 | )
|
||||
53 52 |
|
||||
54 53 | Path(
|
||||
|
||||
PTH201.py:55:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
54 | Path(
|
||||
55 | '' # Comment before comma
|
||||
| ^^ PTH201
|
||||
56 | ,
|
||||
57 | ) / "multiple" / (
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
52 52 | )
|
||||
53 53 |
|
||||
54 54 | Path(
|
||||
55 |- '' # Comment before comma
|
||||
55 |+ "multiple" # Comment before comma
|
||||
56 56 | ,
|
||||
57 |-) / "multiple" / (
|
||||
57 |+) / (
|
||||
58 58 | "frag" # Comment
|
||||
59 59 | 'ment'
|
||||
60 60 | )
|
||||
5 5 | _ = Path(".")
|
||||
6 6 | _ = pth(".")
|
||||
7 7 | _ = PurePath(".")
|
||||
8 |-_ = Path("")
|
||||
8 |+_ = Path()
|
||||
9 9 |
|
||||
10 10 | # no match
|
||||
11 11 | _ = Path()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{
|
||||
self as ast, AstNode, Expr, ExprEllipsisLiteral, ExprLambda, Identifier, Parameter,
|
||||
ParameterWithDefault, Parameters, Stmt,
|
||||
self as ast, Expr, Identifier, Parameter, ParameterWithDefault, Parameters, Stmt,
|
||||
};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_trivia::{has_leading_content, has_trailing_content, leading_indentation};
|
||||
use ruff_source_file::UniversalNewlines;
|
||||
@@ -66,7 +65,10 @@ pub(crate) fn lambda_assignment(
|
||||
return;
|
||||
};
|
||||
|
||||
let Expr::Lambda(lambda) = value else {
|
||||
let Expr::Lambda(ast::ExprLambda {
|
||||
parameters, body, ..
|
||||
}) = value
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -83,9 +85,16 @@ pub(crate) fn lambda_assignment(
|
||||
let first_line = checker.locator().line_str(stmt.start());
|
||||
let indentation = leading_indentation(first_line);
|
||||
let mut indented = String::new();
|
||||
for (idx, line) in function(id, lambda, annotation, checker)
|
||||
.universal_newlines()
|
||||
.enumerate()
|
||||
for (idx, line) in function(
|
||||
id,
|
||||
parameters.as_deref(),
|
||||
body,
|
||||
annotation,
|
||||
checker.semantic(),
|
||||
checker.generator(),
|
||||
)
|
||||
.universal_newlines()
|
||||
.enumerate()
|
||||
{
|
||||
if idx == 0 {
|
||||
indented.push_str(&line);
|
||||
@@ -177,21 +186,19 @@ fn extract_types(annotation: &Expr, semantic: &SemanticModel) -> Option<(Vec<Exp
|
||||
/// Generate a function definition from a `lambda` expression.
|
||||
fn function(
|
||||
name: &str,
|
||||
lambda: &ExprLambda,
|
||||
parameters: Option<&Parameters>,
|
||||
body: &Expr,
|
||||
annotation: Option<&Expr>,
|
||||
checker: &Checker,
|
||||
semantic: &SemanticModel,
|
||||
generator: Generator,
|
||||
) -> String {
|
||||
// Use a dummy body. It gets replaced at the end with the actual body.
|
||||
// This allows preserving the source formatting for the body.
|
||||
let body = Stmt::Return(ast::StmtReturn {
|
||||
value: Some(Box::new(Expr::EllipsisLiteral(
|
||||
ExprEllipsisLiteral::default(),
|
||||
))),
|
||||
value: Some(Box::new(body.clone())),
|
||||
range: TextRange::default(),
|
||||
});
|
||||
let parameters = lambda.parameters.as_deref().cloned().unwrap_or_default();
|
||||
let parameters = parameters.cloned().unwrap_or_default();
|
||||
if let Some(annotation) = annotation {
|
||||
if let Some((arg_types, return_type)) = extract_types(annotation, checker.semantic()) {
|
||||
if let Some((arg_types, return_type)) = extract_types(annotation, semantic) {
|
||||
// A `lambda` expression can only have positional-only and positional-or-keyword
|
||||
// arguments. The order is always positional-only first, then positional-or-keyword.
|
||||
let new_posonlyargs = parameters
|
||||
@@ -236,12 +243,10 @@ fn function(
|
||||
type_params: None,
|
||||
range: TextRange::default(),
|
||||
});
|
||||
let generated = checker.generator().stmt(&func);
|
||||
|
||||
return replace_trailing_ellipsis_with_original_expr(generated, lambda, checker);
|
||||
return generator.stmt(&func);
|
||||
}
|
||||
}
|
||||
let function = Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
let func = Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
is_async: false,
|
||||
name: Identifier::new(name.to_string(), TextRange::default()),
|
||||
parameters: Box::new(parameters),
|
||||
@@ -251,32 +256,5 @@ fn function(
|
||||
type_params: None,
|
||||
range: TextRange::default(),
|
||||
});
|
||||
let generated = checker.generator().stmt(&function);
|
||||
|
||||
replace_trailing_ellipsis_with_original_expr(generated, lambda, checker)
|
||||
}
|
||||
|
||||
fn replace_trailing_ellipsis_with_original_expr(
|
||||
mut generated: String,
|
||||
lambda: &ExprLambda,
|
||||
checker: &Checker,
|
||||
) -> String {
|
||||
let original_expr_range = parenthesized_range(
|
||||
(&lambda.body).into(),
|
||||
lambda.as_any_node_ref(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(lambda.body.range());
|
||||
|
||||
let original_expr_in_source = checker.locator().slice(original_expr_range);
|
||||
|
||||
let placeholder_ellipsis_start = generated.rfind("...").unwrap();
|
||||
let placeholder_ellipsis_end = placeholder_ellipsis_start + "...".len();
|
||||
|
||||
generated.replace_range(
|
||||
placeholder_ellipsis_start..placeholder_ellipsis_end,
|
||||
original_expr_in_source,
|
||||
);
|
||||
generated
|
||||
generator.stmt(&func)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ impl<'a> IntoIterator for &'a LogicalLines<'a> {
|
||||
/// line breaks.
|
||||
///
|
||||
/// ## Examples
|
||||
/// This expression forms one logical line because the array elements are parenthesized.
|
||||
/// This expression forms one logical line because because the array elements are parenthesized.
|
||||
///
|
||||
/// ```python
|
||||
/// a = [
|
||||
|
||||
@@ -279,7 +279,7 @@ E731.py:127:5: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
126 126 |
|
||||
127 |- f: Callable[[str, int], tuple[str, int]] = lambda a, b: (a, b)
|
||||
127 |+ def f(a: str, b: int) -> tuple[str, int]:
|
||||
128 |+ return (a, b)
|
||||
128 |+ return a, b
|
||||
128 129 |
|
||||
129 130 |
|
||||
130 131 | def scope():
|
||||
@@ -364,103 +364,7 @@ E731.py:147:5: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
148 |- i := 1,
|
||||
149 |- )
|
||||
147 |+ def f():
|
||||
148 |+ return (
|
||||
149 |+ i := 1,
|
||||
150 |+ )
|
||||
150 151 |
|
||||
151 152 |
|
||||
152 153 | from dataclasses import dataclass
|
||||
|
||||
E731.py:163:1: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
|
|
||||
161 | # Regression tests for:
|
||||
162 | # * https://github.com/astral-sh/ruff/issues/7720
|
||||
163 | / x = lambda: """
|
||||
164 | | a
|
||||
165 | | b
|
||||
166 | | """
|
||||
| |___^ E731
|
||||
167 |
|
||||
168 | # * https://github.com/astral-sh/ruff/issues/10277
|
||||
|
|
||||
= help: Rewrite `x` as a `def`
|
||||
|
||||
ℹ Unsafe fix
|
||||
160 160 |
|
||||
161 161 | # Regression tests for:
|
||||
162 162 | # * https://github.com/astral-sh/ruff/issues/7720
|
||||
163 |-x = lambda: """
|
||||
163 |+def x():
|
||||
164 |+ return """
|
||||
164 165 | a
|
||||
165 166 | b
|
||||
166 167 | """
|
||||
|
||||
E731.py:169:1: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
|
|
||||
168 | # * https://github.com/astral-sh/ruff/issues/10277
|
||||
169 | at_least_one_million = lambda _: _ >= 1_000_000
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ E731
|
||||
170 |
|
||||
171 | x = lambda: (
|
||||
|
|
||||
= help: Rewrite `at_least_one_million` as a `def`
|
||||
|
||||
ℹ Unsafe fix
|
||||
166 166 | """
|
||||
167 167 |
|
||||
168 168 | # * https://github.com/astral-sh/ruff/issues/10277
|
||||
169 |-at_least_one_million = lambda _: _ >= 1_000_000
|
||||
169 |+def at_least_one_million(_):
|
||||
170 |+ return _ >= 1_000_000
|
||||
170 171 |
|
||||
171 172 | x = lambda: (
|
||||
172 173 | # comment
|
||||
|
||||
E731.py:171:1: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
|
|
||||
169 | at_least_one_million = lambda _: _ >= 1_000_000
|
||||
170 |
|
||||
171 | / x = lambda: (
|
||||
172 | | # comment
|
||||
173 | | 5 + 10
|
||||
174 | | )
|
||||
| |_^ E731
|
||||
175 |
|
||||
176 | x = lambda: (
|
||||
|
|
||||
= help: Rewrite `x` as a `def`
|
||||
|
||||
ℹ Unsafe fix
|
||||
168 168 | # * https://github.com/astral-sh/ruff/issues/10277
|
||||
169 169 | at_least_one_million = lambda _: _ >= 1_000_000
|
||||
170 170 |
|
||||
171 |-x = lambda: (
|
||||
171 |+def x():
|
||||
172 |+ return (
|
||||
172 173 | # comment
|
||||
173 174 | 5 + 10
|
||||
174 175 | )
|
||||
|
||||
E731.py:176:1: E731 [*] Do not assign a `lambda` expression, use a `def`
|
||||
|
|
||||
174 | )
|
||||
175 |
|
||||
176 | / x = lambda: (
|
||||
177 | | # comment
|
||||
178 | | y := 10
|
||||
179 | | )
|
||||
| |_^ E731
|
||||
|
|
||||
= help: Rewrite `x` as a `def`
|
||||
|
||||
ℹ Unsafe fix
|
||||
173 173 | 5 + 10
|
||||
174 174 | )
|
||||
175 175 |
|
||||
176 |-x = lambda: (
|
||||
176 |+def x():
|
||||
177 |+ return (
|
||||
177 178 | # comment
|
||||
178 179 | y := 10
|
||||
179 180 | )
|
||||
148 |+ return (i := 1),
|
||||
150 149 |
|
||||
151 150 |
|
||||
152 151 | from dataclasses import dataclass
|
||||
|
||||
@@ -17,41 +17,41 @@ mod tests {
|
||||
|
||||
use super::settings::{Convention, Settings};
|
||||
|
||||
#[test_case(Rule::MissingBlankLineAfterLastSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::BlankLineAfterLastSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::NoBlankLineAfterSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::MissingBlankLineAfterLastSection, Path::new("D413.py"))]
|
||||
#[test_case(Rule::MissingBlankLineAfterSummary, Path::new("D.py"))]
|
||||
#[test_case(Rule::BlankLineAfterLastSection, Path::new("D413.py"))]
|
||||
#[test_case(Rule::BlankLineAfterSummary, Path::new("D.py"))]
|
||||
#[test_case(Rule::NoBlankLineBeforeSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::NonCapitalizedSectionName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::MissingDashedUnderlineAfterSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::CapitalizeSectionName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::DashedUnderlineAfterSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::UndocumentedParam, Path::new("canonical_google_examples.py"))]
|
||||
#[test_case(Rule::UndocumentedParam, Path::new("canonical_numpy_examples.py"))]
|
||||
#[test_case(Rule::UndocumentedParam, Path::new("sections.py"))]
|
||||
#[test_case(Rule::MissingTrailingPeriod, Path::new("D.py"))]
|
||||
#[test_case(Rule::MissingTrailingPeriod, Path::new("D400.py"))]
|
||||
#[test_case(Rule::MissingTrailingPeriod, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::MissingTerminalPunctuation, Path::new("D.py"))]
|
||||
#[test_case(Rule::MissingTerminalPunctuation, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400.py"))]
|
||||
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
|
||||
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
|
||||
#[test_case(Rule::FirstWordUncapitalized, Path::new("D.py"))]
|
||||
#[test_case(Rule::FirstWordUncapitalized, Path::new("D403.py"))]
|
||||
#[test_case(Rule::UnnecessaryMultilineDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::DocstringTabIndentation, Path::new("D.py"))]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
|
||||
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
|
||||
#[test_case(Rule::MultiLineSummaryFirstLine, Path::new("D.py"))]
|
||||
#[test_case(Rule::MultiLineSummarySecondLine, Path::new("D.py"))]
|
||||
#[test_case(Rule::NewLineAfterLastParagraph, Path::new("D.py"))]
|
||||
#[test_case(Rule::MissingNewLineAfterSectionName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::BlankLineAfterFunction, Path::new("D.py"))]
|
||||
#[test_case(Rule::UnnecessaryMultilineDocstring, Path::new("D200.py"))]
|
||||
#[test_case(Rule::BlankLineAfterFunction, Path::new("D202.py"))]
|
||||
#[test_case(Rule::NewLineAfterSectionName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D.py"))]
|
||||
#[test_case(Rule::FitsOnOneLine, Path::new("D200.py"))]
|
||||
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D202.py"))]
|
||||
#[test_case(Rule::BlankLineBeforeClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::BlankLineBeforeFunction, Path::new("D.py"))]
|
||||
#[test_case(Rule::NoBlankLineBeforeFunction, Path::new("D.py"))]
|
||||
#[test_case(Rule::BlankLinesBetweenHeaderAndContent, Path::new("sections.py"))]
|
||||
#[test_case(Rule::BlankLinesBetweenHeaderAndContent, Path::new("sphinx.py"))]
|
||||
#[test_case(Rule::OverIndentation, Path::new("D.py"))]
|
||||
#[test_case(Rule::OverIndentation, Path::new("D208.py"))]
|
||||
#[test_case(Rule::SignatureInDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::SignatureInDocstring, Path::new("D402.py"))]
|
||||
#[test_case(Rule::NoSignature, Path::new("D.py"))]
|
||||
#[test_case(Rule::NoSignature, Path::new("D402.py"))]
|
||||
#[test_case(Rule::SurroundingWhitespace, Path::new("D.py"))]
|
||||
#[test_case(Rule::DocstringStartsWithThis, Path::new("D.py"))]
|
||||
#[test_case(Rule::UnderIndentation, Path::new("D.py"))]
|
||||
@@ -59,8 +59,8 @@ mod tests {
|
||||
#[test_case(Rule::EmptyDocstringSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::NonImperativeMood, Path::new("D401.py"))]
|
||||
#[test_case(Rule::NoBlankLineAfterSection, Path::new("D410.py"))]
|
||||
#[test_case(Rule::IncorrectBlankLineAfterClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::IncorrectBlankLineBeforeClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::OneBlankLineAfterClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::OneBlankLineBeforeClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedPublicClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedPublicFunction, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedPublicInit, Path::new("D.py"))]
|
||||
@@ -83,13 +83,13 @@ mod tests {
|
||||
#[test_case(Rule::UndocumentedPublicNestedClass, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedPublicPackage, Path::new("D.py"))]
|
||||
#[test_case(Rule::UndocumentedPublicPackage, Path::new("D104/__init__.py"))]
|
||||
#[test_case(Rule::MissingSectionNameColon, Path::new("D.py"))]
|
||||
#[test_case(Rule::OverindentedSection, Path::new("sections.py"))]
|
||||
#[test_case(Rule::OverindentedSection, Path::new("D214_module.py"))]
|
||||
#[test_case(Rule::OverindentedSectionUnderline, Path::new("D215.py"))]
|
||||
#[test_case(Rule::MissingSectionUnderlineAfterName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::MismatchedSectionUnderlineLength, Path::new("sections.py"))]
|
||||
#[test_case(Rule::OverindentedSectionUnderline, Path::new("sections.py"))]
|
||||
#[test_case(Rule::SectionNameEndsInColon, Path::new("D.py"))]
|
||||
#[test_case(Rule::SectionNotOverIndented, Path::new("sections.py"))]
|
||||
#[test_case(Rule::SectionNotOverIndented, Path::new("D214_module.py"))]
|
||||
#[test_case(Rule::SectionUnderlineNotOverIndented, Path::new("D215.py"))]
|
||||
#[test_case(Rule::SectionUnderlineAfterName, Path::new("sections.py"))]
|
||||
#[test_case(Rule::SectionUnderlineMatchesSectionLength, Path::new("sections.py"))]
|
||||
#[test_case(Rule::SectionUnderlineNotOverIndented, Path::new("sections.py"))]
|
||||
#[test_case(Rule::OverloadWithDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D.py"))]
|
||||
#[test_case(Rule::EscapeSequenceInDocstring, Path::new("D301.py"))]
|
||||
@@ -171,7 +171,7 @@ mod tests {
|
||||
Path::new("pydocstyle/D209_D400.py"),
|
||||
&settings::LinterSettings::for_rules([
|
||||
Rule::NewLineAfterLastParagraph,
|
||||
Rule::MissingTrailingPeriod,
|
||||
Rule::EndsInPeriod,
|
||||
]),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
|
||||
@@ -41,16 +41,16 @@ use crate::docstrings::Docstring;
|
||||
///
|
||||
/// [PEP 257]: https://peps.python.org/pep-0257/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingBlankLineAfterSummary {
|
||||
pub(crate) struct BlankLineAfterSummary {
|
||||
num_lines: usize,
|
||||
}
|
||||
|
||||
impl Violation for MissingBlankLineAfterSummary {
|
||||
impl Violation for BlankLineAfterSummary {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingBlankLineAfterSummary { num_lines } = self;
|
||||
let BlankLineAfterSummary { num_lines } = self;
|
||||
if *num_lines == 0 {
|
||||
"1 blank line required between summary line and description".to_string()
|
||||
} else {
|
||||
@@ -85,7 +85,7 @@ pub(crate) fn blank_after_summary(checker: &mut Checker, docstring: &Docstring)
|
||||
}
|
||||
if lines_count > 1 && blanks_count != 1 {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
MissingBlankLineAfterSummary {
|
||||
BlankLineAfterSummary {
|
||||
num_lines: blanks_count,
|
||||
},
|
||||
docstring.range(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user