Compare commits

..

1 Commits

Author SHA1 Message Date
Micha Reiser
242a1bcccb Union builder perf improvements 2024-11-20 21:46:35 +01:00
213 changed files with 1626 additions and 8850 deletions

View File

@@ -157,33 +157,6 @@ jobs:
name: ruff
path: target/debug/ruff
cargo-test-linux-release:
name: "cargo test (linux, release)"
runs-on: depot-ubuntu-22.04-16
needs: determine_changes
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@v2
with:
tool: cargo-insta
- uses: Swatinem/rust-cache@v2
- name: "Run tests"
shell: bash
env:
NEXTEST_PROFILE: "ci"
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
cargo-test-windows:
name: "cargo test (windows)"
runs-on: windows-latest-xlarge
@@ -239,6 +212,7 @@ jobs:
cargo-build-release:
name: "cargo build (release)"
runs-on: macos-latest
needs: determine_changes
if: ${{ github.ref == 'refs/heads/main' }}
timeout-minutes: 20
steps:
@@ -335,7 +309,7 @@ jobs:
# Make executable, since artifact download doesn't preserve this
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
python scripts/fuzz-parser/fuzz.py --bin ruff 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
python scripts/fuzz-parser/fuzz.py 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
scripts:
name: "test scripts"

View File

@@ -49,7 +49,7 @@ jobs:
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
run: cargo build --locked
- name: Fuzz
run: python scripts/fuzz-parser/fuzz.py --bin ruff $(shuf -i 0-9999999999999999999 -n 1000) --test-executable target/debug/ruff
run: python scripts/fuzz-parser/fuzz.py $(shuf -i 0-9999999999999999999 -n 1000) --test-executable target/debug/ruff
create-issue-on-failure:
name: Create an issue if the daily fuzz surfaced any bugs

View File

@@ -1,30 +1,5 @@
# Breaking Changes
## 0.8.0
- **Default to Python 3.9**
Ruff now defaults to Python 3.9 instead of 3.8 if no explicit Python version is configured using [`ruff.target-version`](https://docs.astral.sh/ruff/settings/#target-version) or [`project.requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) ([#13896](https://github.com/astral-sh/ruff/pull/13896))
- **Changed location of `pydoclint` diagnostics**
[`pydoclint`](https://docs.astral.sh/ruff/rules/#pydoclint-doc) diagnostics now point to the first-line of the problematic docstring. Previously, this was not the case.
If you've opted into these preview rules but have them suppressed using
[`noqa`](https://docs.astral.sh/ruff/linter/#error-suppression) comments in
some places, this change may mean that you need to move the `noqa` suppression
comments. Most users should be unaffected by this change.
- **Use XDG (i.e. `~/.local/bin`) instead of the Cargo home directory in the standalone installer**
Previously, Ruff's installer used `$CARGO_HOME` or `~/.cargo/bin` for its target install directory. Now, Ruff will be installed into `$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin` (in that order).
This change is only relevant to users of the standalone Ruff installer (using the shell or PowerShell script). If you installed Ruff using uv or pip, you should be unaffected.
- **Changes to the line width calculation**
Ruff now uses a new version of the [unicode-width](https://github.com/unicode-rs/unicode-width) Rust crate to calculate the line width. In very rare cases, this may lead to lines containing Unicode characters being reformatted, or being considered too long when they were not before ([`E501`](https://docs.astral.sh/ruff/rules/line-too-long/)).
## 0.7.0
- The pytest rules `PT001` and `PT023` now default to omitting the decorator parentheses when there are no arguments

View File

@@ -1,104 +1,5 @@
# Changelog
## 0.8.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.8.0) for a migration guide and overview of the changes!
### Breaking changes
See also, the "Remapped rules" section which may result in disabled rules.
- **Default to Python 3.9**
Ruff now defaults to Python 3.9 instead of 3.8 if no explicit Python version is configured using [`ruff.target-version`](https://docs.astral.sh/ruff/settings/#target-version) or [`project.requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) ([#13896](https://github.com/astral-sh/ruff/pull/13896))
- **Changed location of `pydoclint` diagnostics**
[`pydoclint`](https://docs.astral.sh/ruff/rules/#pydoclint-doc) diagnostics now point to the first-line of the problematic docstring. Previously, this was not the case.
If you've opted into these preview rules but have them suppressed using
[`noqa`](https://docs.astral.sh/ruff/linter/#error-suppression) comments in
some places, this change may mean that you need to move the `noqa` suppression
comments. Most users should be unaffected by this change.
- **Use XDG (i.e. `~/.local/bin`) instead of the Cargo home directory in the standalone installer**
Previously, Ruff's installer used `$CARGO_HOME` or `~/.cargo/bin` for its target install directory. Now, Ruff will be installed into `$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin` (in that order).
This change is only relevant to users of the standalone Ruff installer (using the shell or PowerShell script). If you installed Ruff using uv or pip, you should be unaffected.
- **Changes to the line width calculation**
Ruff now uses a new version of the [unicode-width](https://github.com/unicode-rs/unicode-width) Rust crate to calculate the line width. In very rare cases, this may lead to lines containing Unicode characters being reformatted, or being considered too long when they were not before ([`E501`](https://docs.astral.sh/ruff/rules/line-too-long/)).
### Removed Rules
The following deprecated rules have been removed:
- [`missing-type-self`](https://docs.astral.sh/ruff/rules/missing-type-self/) (`ANN101`)
- [`missing-type-cls`](https://docs.astral.sh/ruff/rules/missing-type-cls/) (`ANN102`)
- [`syntax-error`](https://docs.astral.sh/ruff/rules/syntax-error/) (`E999`)
- [`pytest-missing-fixture-name-underscore`](https://docs.astral.sh/ruff/rules/pytest-missing-fixture-name-underscore/) (`PT004`)
- [`pytest-incorrect-fixture-name-underscore`](https://docs.astral.sh/ruff/rules/pytest-incorrect-fixture-name-underscore/) (`PT005`)
- [`unpacked-list-comprehension`](https://docs.astral.sh/ruff/rules/unpacked-list-comprehension/) (`UP027`)
### Remapped rules
The following rules have been remapped to new rule codes:
- [`flake8-type-checking`](https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc): `TCH` to `TC`
### Stabilization
The following rules have been stabilized and are no longer in preview:
- [`builtin-import-shadowing`](https://docs.astral.sh/ruff/rules/builtin-import-shadowing/) (`A004`)
- [`mutable-contextvar-default`](https://docs.astral.sh/ruff/rules/mutable-contextvar-default/) (`B039`)
- [`fast-api-redundant-response-model`](https://docs.astral.sh/ruff/rules/fast-api-redundant-response-model/) (`FAST001`)
- [`fast-api-non-annotated-dependency`](https://docs.astral.sh/ruff/rules/fast-api-non-annotated-dependency/) (`FAST002`)
- [`dict-index-missing-items`](https://docs.astral.sh/ruff/rules/dict-index-missing-items/) (`PLC0206`)
- [`pep484-style-positional-only-parameter`](https://docs.astral.sh/ruff/rules/pep484-style-positional-only-parameter/) (`PYI063`)
- [`redundant-final-literal`](https://docs.astral.sh/ruff/rules/redundant-final-literal/) (`PYI064`)
- [`bad-version-info-order`](https://docs.astral.sh/ruff/rules/bad-version-info-order/) (`PYI066`)
- [`parenthesize-chained-operators`](https://docs.astral.sh/ruff/rules/parenthesize-chained-operators/) (`RUF021`)
- [`unsorted-dunder-all`](https://docs.astral.sh/ruff/rules/unsorted-dunder-all/) (`RUF022`)
- [`unsorted-dunder-slots`](https://docs.astral.sh/ruff/rules/unsorted-dunder-slots/) (`RUF023`)
- [`assert-with-print-message`](https://docs.astral.sh/ruff/rules/assert-with-print-message/) (`RUF030`)
- [`unnecessary-default-type-args`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/) (`UP043`)
The following behaviors have been stabilized:
- [`ambiguous-variable-name`](https://docs.astral.sh/ruff/rules/ambiguous-variable-name/) (`E741`): Violations in stub files are now ignored. Stub authors typically don't control variable names.
- [`printf-string-formatting`](https://docs.astral.sh/ruff/rules/printf-string-formatting/) (`UP031`): Report all `printf`-like usages even if no autofix is available
The following fixes have been stabilized:
- [`zip-instead-of-pairwise`](https://docs.astral.sh/ruff/rules/zip-instead-of-pairwise/) (`RUF007`)
### Preview features
- \[`flake8-datetimez`\] Exempt `min.time()` and `max.time()` (`DTZ901`) ([#14394](https://github.com/astral-sh/ruff/pull/14394))
- \[`flake8-pie`\] Mark fix as unsafe if the following statement is a string literal (`PIE790`) ([#14393](https://github.com/astral-sh/ruff/pull/14393))
- \[`flake8-pyi`\] New rule `redundant-none-literal` (`PYI061`) ([#14316](https://github.com/astral-sh/ruff/pull/14316))
- \[`flake8-pyi`\] Add autofix for `redundant-numeric-union` (`PYI041`) ([#14273](https://github.com/astral-sh/ruff/pull/14273))
- \[`ruff`\] New rule `map-int-version-parsing` (`RUF048`) ([#14373](https://github.com/astral-sh/ruff/pull/14373))
- \[`ruff`\] New rule `redundant-bool-literal` (`RUF038`) ([#14319](https://github.com/astral-sh/ruff/pull/14319))
- \[`ruff`\] New rule `unraw-re-pattern` (`RUF039`) ([#14446](https://github.com/astral-sh/ruff/pull/14446))
- \[`pycodestyle`\] Exempt `pytest.importorskip()` calls (`E402`) ([#14474](https://github.com/astral-sh/ruff/pull/14474))
- \[`pylint`\] Autofix suggests using sets when possible (`PLR1714`) ([#14372](https://github.com/astral-sh/ruff/pull/14372))
### Rule changes
- [`invalid-pyproject-toml`](https://docs.astral.sh/ruff/rules/invalid-pyproject-toml/) (`RUF200`): Updated to reflect the provisionally accepted [PEP 639](https://peps.python.org/pep-0639/).
- \[`flake8-pyi`\] Avoid panic in unfixable case (`PYI041`) ([#14402](https://github.com/astral-sh/ruff/pull/14402))
- \[`flake8-type-checking`\] Correctly handle quotes in subscript expression when generating an autofix ([#14371](https://github.com/astral-sh/ruff/pull/14371))
- \[`pylint`\] Suggest correct autofix for `__contains__` (`PLC2801`) ([#14424](https://github.com/astral-sh/ruff/pull/14424))
### Configuration
- Ruff now emits a warning instead of an error when a configuration [`ignore`](https://docs.astral.sh/ruff/settings/#lint_ignore)s a rule that has been removed ([#14435](https://github.com/astral-sh/ruff/pull/14435))
- Ruff now validates that `lint.flake8-import-conventions.aliases` only uses valid module names and aliases ([#14477](https://github.com/astral-sh/ruff/pull/14477))
## 0.7.4
### Preview features
@@ -1077,7 +978,7 @@ The following deprecated CLI commands have been removed:
### Preview features
- \[`flake8-bugbear`\] Implement `return-in-generator` (`B901`) ([#11644](https://github.com/astral-sh/ruff/pull/11644))
- \[`flake8-pyi`\] Implement `pep484-style-positional-only-parameter` (`PYI063`) ([#11699](https://github.com/astral-sh/ruff/pull/11699))
- \[`flake8-pyi`\] Implement `PYI063` ([#11699](https://github.com/astral-sh/ruff/pull/11699))
- \[`pygrep_hooks`\] Check blanket ignores via file-level pragmas (`PGH004`) ([#11540](https://github.com/astral-sh/ruff/pull/11540))
### Rule changes

130
Cargo.lock generated
View File

@@ -413,7 +413,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -693,7 +693,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -704,7 +704,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -758,23 +758,23 @@ dependencies = [
[[package]]
name = "dir-test"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12781621d53fd9087021f5a338df5c57c04f84a6231c1f4726f45e2e333470b"
checksum = "5c44bdf9319ad5223afb7eb15a7110452b0adf0373ea6756561b2c708eef0dd1"
dependencies = [
"dir-test-macros",
]
[[package]]
name = "dir-test-macros"
version = "0.4.0"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1340852f50b2285d01a7f598cc5d08b572669c3e09e614925175cc3c26787b91"
checksum = "644f96047137dfaa7a09e34d4623f9e52a1926ecc25ba32ad2ba3fc422536b25"
dependencies = [
"glob",
"proc-macro2",
"quote",
"syn",
"syn 1.0.109",
]
[[package]]
@@ -826,7 +826,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -1068,9 +1068,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
[[package]]
name = "hashlink"
@@ -1246,7 +1246,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -1319,7 +1319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
"hashbrown 0.15.1",
"serde",
]
@@ -1420,7 +1420,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -1526,9 +1526,9 @@ checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "libcst"
version = "1.5.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa3e60579a8cba3d86aa4a5f7fc98973cc0fd2ac270bf02f85a9bef09700b075"
checksum = "1586dd7a857d8a61a577afde1a24cc9573ff549eff092d5ce968b1ec93cc61b6"
dependencies = [
"chic",
"libcst_derive",
@@ -1546,7 +1546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
dependencies = [
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -1710,9 +1710,9 @@ checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
[[package]]
name = "newtype-uuid"
version = "1.1.3"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8781e2ef64806278a55ad223f0bc875772fd40e1fe6e73e8adbf027817229d"
checksum = "3526cb7c660872e401beaf3297f95f548ce3b4b4bdd8121b7c0713771d7c4a6e"
dependencies = [
"uuid",
]
@@ -2012,7 +2012,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -2127,9 +2127,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.92"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
@@ -2150,24 +2150,24 @@ dependencies = [
[[package]]
name = "quick-junit"
version = "0.5.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7"
checksum = "62ffd2f9a162cfae131bed6d9d1ed60adced33be340a94f96952897d7cb0c240"
dependencies = [
"chrono",
"indexmap",
"newtype-uuid",
"quick-xml",
"strip-ansi-escapes",
"thiserror 2.0.3",
"thiserror 1.0.67",
"uuid",
]
[[package]]
name = "quick-xml"
version = "0.37.1"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
dependencies = [
"memchr",
]
@@ -2266,7 +2266,7 @@ dependencies = [
"compact_str",
"countme",
"dir-test",
"hashbrown 0.15.2",
"hashbrown 0.15.1",
"indexmap",
"insta",
"itertools 0.13.0",
@@ -2489,7 +2489,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.8.0"
version = "0.7.4"
dependencies = [
"anyhow",
"argfile",
@@ -2708,7 +2708,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.8.0"
version = "0.7.4"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2775,7 +2775,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3023,7 +3023,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.8.0"
version = "0.7.4"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3072,7 +3072,6 @@ dependencies = [
"ruff_python_ast",
"ruff_python_formatter",
"ruff_python_semantic",
"ruff_python_stdlib",
"ruff_source_file",
"rustc-hash 2.0.0",
"schemars",
@@ -3195,7 +3194,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"synstructure",
]
@@ -3229,7 +3228,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3278,7 +3277,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3289,7 +3288,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3312,7 +3311,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3353,7 +3352,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3461,7 +3460,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3472,9 +3471,20 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
version = "2.0.89"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@@ -3489,7 +3499,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3552,7 +3562,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3563,7 +3573,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"test-case-core",
]
@@ -3593,7 +3603,7 @@ checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3604,7 +3614,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3726,7 +3736,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -3865,9 +3875,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.14"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
@@ -3942,9 +3952,9 @@ dependencies = [
[[package]]
name = "url"
version = "2.5.4"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
dependencies = [
"form_urlencoded",
"idna",
@@ -3996,7 +4006,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -4091,7 +4101,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"wasm-bindgen-shared",
]
@@ -4125,7 +4135,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4159,7 +4169,7 @@ checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -4462,7 +4472,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"synstructure",
]
@@ -4483,7 +4493,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]
@@ -4503,7 +4513,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
"synstructure",
]
@@ -4532,7 +4542,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.87",
]
[[package]]

View File

@@ -65,7 +65,7 @@ compact_str = "0.8.0"
criterion = { version = "0.5.1", default-features = false }
crossbeam = { version = "0.8.4" }
dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dir-test = { version = "0.3.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.11.0" }

View File

@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.8.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.8.0/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.7.4/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.7.4/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.8.0
rev: v0.7.4
hooks:
# Run the linter.
- id: ruff

View File

@@ -4,6 +4,7 @@ use std::io::Write;
use std::time::Duration;
use anyhow::{anyhow, Context};
use red_knot_python_semantic::{resolve_module, ModuleName, Program, PythonVersion, SitePackages};
use red_knot_workspace::db::{Db, RootDatabase};
use red_knot_workspace::watch;
@@ -13,7 +14,7 @@ use red_knot_workspace::workspace::WorkspaceMetadata;
use ruff_db::files::{system_path_to_file, File, FileError};
use ruff_db::source::source_text;
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
use ruff_db::testing::{setup_logging, setup_logging_with_filter};
use ruff_db::testing::setup_logging;
use ruff_db::Upcast;
struct TestCase {
@@ -46,8 +47,6 @@ impl TestCase {
}
fn try_stop_watch(&mut self, timeout: Duration) -> Option<Vec<watch::ChangeEvent>> {
tracing::debug!("Try stopping watch with timeout {:?}", timeout);
let watcher = self
.watcher
.take()
@@ -57,11 +56,8 @@ impl TestCase {
.changes_receiver
.recv_timeout(timeout)
.unwrap_or_default();
watcher.flush();
tracing::debug!("Flushed file watcher");
watcher.stop();
tracing::debug!("Stopping file watcher");
for event in &self.changes_receiver {
all_events.extend(event);
@@ -604,8 +600,6 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
#[test]
fn directory_renamed() -> anyhow::Result<()> {
let _tracing = setup_logging_with_filter("file_watching=TRACE,red_knot=TRACE");
let mut case = setup([
("bar.py", "import sub.a"),
("sub/__init__.py", ""),
@@ -646,10 +640,6 @@ fn directory_renamed() -> anyhow::Result<()> {
let changes = case.stop_watch();
for event in &changes {
tracing::debug!("Event: {:?}", event);
}
case.apply_changes(changes);
// `import sub.a` should no longer resolve

View File

@@ -1,62 +0,0 @@
# NoReturn & Never
`NoReturn` is used to annotate the return type for functions that never return. `Never` is the
bottom type, representing the empty set of Python objects. These two annotations can be used
interchangeably.
## Function Return Type Annotation
```py
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError("no way")
# revealed: Never
reveal_type(stop())
```
## Assignment
```py
from typing import NoReturn, Never, Any
# error: [invalid-type-parameter] "Type `typing.Never` expected no type parameter"
x: Never[int]
a1: NoReturn
# TODO: Test `Never` is only available in python >= 3.11
a2: Never
b1: Any
b2: int
def f():
# revealed: Never
reveal_type(a1)
# revealed: Never
reveal_type(a2)
# Never is assignable to all types.
v1: int = a1
v2: str = a1
# Other types are not assignable to Never except for Never (and Any).
v3: Never = b1
v4: Never = a2
v5: Any = b2
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Never`"
v6: Never = 1
```
## Typing Extensions
```py
from typing_extensions import NoReturn, Never
x: NoReturn
y: Never
def f():
# revealed: Never
reveal_type(x)
# revealed: Never
reveal_type(y)
```

View File

@@ -9,10 +9,10 @@ Ts = TypeVarTuple("Ts")
def append_int(*args: *Ts) -> tuple[*Ts, int]:
# TODO: should show some representation of the variadic generic type
reveal_type(args) # revealed: @Todo(function parameter type)
reveal_type(args) # revealed: @Todo
return (*args, 1)
# TODO should be tuple[Literal[True], Literal["a"], int]
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
reveal_type(append_int(True, "a")) # revealed: @Todo
```

View File

@@ -125,6 +125,7 @@ def f7() -> "\x69nt":
def f8() -> """int""":
return 1
# error: [annotation-byte-string] "Type expressions cannot use bytes literal"
def f9() -> "b'int'":
return 1
@@ -188,31 +189,3 @@ reveal_type(d) # revealed: Foo
## Parameter
TODO: Add tests once parameter inference is supported
## Invalid expressions
The expressions in these string annotations aren't valid expressions in this context but we
shouldn't panic.
```py
a: "1 or 2"
b: "(x := 1)"
c: "1 + 2"
d: "lambda x: x"
e: "x if True else y"
f: "{'a': 1, 'b': 2}"
g: "{1, 2}"
h: "[i for i in range(5)]"
i: "{i for i in range(5)}"
j: "{i: i for i in range(5)}"
k: "(i for i in range(5))"
l: "await 1"
# error: [forward-annotation-syntax-error]
m: "yield 1"
# error: [forward-annotation-syntax-error]
n: "yield from 1"
o: "1 < 2"
p: "call()"
r: "[1, 2]"
s: "(1, 2)"
```

View File

@@ -1,61 +0,0 @@
# Union
## Annotation
`typing.Union` can be used to construct union types same as `|` operator.
```py
from typing import Union
a: Union[int, str]
a1: Union[int, bool]
a2: Union[int, Union[float, str]]
a3: Union[int, None]
a4: Union[Union[float, str]]
a5: Union[int]
a6: Union[()]
def f():
# revealed: int | str
reveal_type(a)
# Since bool is a subtype of int we simplify to int here. But we do allow assigning boolean values (see below).
# revealed: int
reveal_type(a1)
# revealed: int | float | str
reveal_type(a2)
# revealed: int | None
reveal_type(a3)
# revealed: float | str
reveal_type(a4)
# revealed: int
reveal_type(a5)
# revealed: Never
reveal_type(a6)
```
## Assignment
```py
from typing import Union
a: Union[int, str]
a = 1
a = ""
a1: Union[int, bool]
a1 = 1
a1 = True
# error: [invalid-assignment] "Object of type `Literal[b""]` is not assignable to `int | str`"
a = b""
```
## Typing Extensions
```py
from typing_extensions import Union
a: Union[int, str]
def f():
# revealed: int | str
reveal_type(a)
```

View File

@@ -51,12 +51,12 @@ reveal_type(c) # revealed: tuple[str, int]
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
# TODO: homogenous tuples, PEP-646 tuples
reveal_type(e) # revealed: @Todo(full tuple[...] support)
reveal_type(f) # revealed: @Todo(full tuple[...] support)
reveal_type(g) # revealed: @Todo(full tuple[...] support)
reveal_type(e) # revealed: @Todo
reveal_type(f) # revealed: @Todo
reveal_type(g) # revealed: @Todo
# TODO: support more kinds of type expressions in annotations
reveal_type(h) # revealed: @Todo(full tuple[...] support)
reveal_type(h) # revealed: @Todo
reveal_type(i) # revealed: tuple[str | int, str | int]
reveal_type(j) # revealed: tuple[str | int]

View File

@@ -317,7 +317,7 @@ reveal_type(1 + A()) # revealed: int
reveal_type(A() + "foo") # revealed: A
# TODO should be `A` since `str.__add__` doesn't support `A` instances
# TODO overloads
reveal_type("foo" + A()) # revealed: @Todo(return type)
reveal_type("foo" + A()) # revealed: @Todo
reveal_type(A() + b"foo") # revealed: A
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
@@ -325,7 +325,7 @@ reveal_type(b"foo" + A()) # revealed: bytes
reveal_type(A() + ()) # revealed: A
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
reveal_type(() + A()) # revealed: @Todo(return type)
reveal_type(() + A()) # revealed: @Todo
literal_string_instance = "foo" * 1_000_000_000
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:
@@ -334,7 +334,7 @@ reveal_type(literal_string_instance) # revealed: LiteralString
reveal_type(A() + literal_string_instance) # revealed: A
# TODO should be `A` since `str.__add__` doesn't support `A` instances
# TODO overloads
reveal_type(literal_string_instance + A()) # revealed: @Todo(return type)
reveal_type(literal_string_instance + A()) # revealed: @Todo
```
## Operations involving instances of classes inheriting from `Any`

View File

@@ -16,7 +16,7 @@ async def get_int_async() -> int:
return 42
# TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]`
reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
reveal_type(get_int_async()) # revealed: @Todo
```
## Generic
@@ -36,8 +36,6 @@ from typing import Callable
def foo() -> int:
return 42
# TODO: This should be resolved once we understand `Callable` annotations
# error: [annotation-with-invalid-expression]
def decorator(func) -> Callable[[], int]:
return foo
@@ -46,7 +44,7 @@ def bar() -> str:
return "bar"
# TODO: should reveal `int`, as the decorator replaces `bar` with `foo`
reveal_type(bar()) # revealed: @Todo(return type)
reveal_type(bar()) # revealed: @Todo
```
## Invalid callable

View File

@@ -50,11 +50,11 @@ def foo(
help()
except x as e:
# TODO: should be `AttributeError`
reveal_type(e) # revealed: @Todo(exception type)
reveal_type(e) # revealed: @Todo
except y as f:
# TODO: should be `OSError | RuntimeError`
reveal_type(f) # revealed: @Todo(exception type)
reveal_type(f) # revealed: @Todo
except z as g:
# TODO: should be `BaseException`
reveal_type(g) # revealed: @Todo(exception type)
reveal_type(g) # revealed: @Todo
```

View File

@@ -22,22 +22,3 @@ reveal_type(1 if None else 2) # revealed: Literal[2]
reveal_type(1 if "" else 2) # revealed: Literal[2]
reveal_type(1 if 0 else 2) # revealed: Literal[2]
```
## Leaked Narrowing Constraint
(issue #14588)
The test inside an if expression should not affect code outside of the expression.
```py
def bool_instance() -> bool:
return True
x: Literal[42, "hello"] = 42 if bool_instance() else "hello"
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
_ = ... if isinstance(x, str) else ...
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
```

View File

@@ -18,7 +18,7 @@ box: MyBox[int] = MyBox(5)
wrong_innards: MyBox[int] = MyBox("five")
# TODO reveal int
reveal_type(box.data) # revealed: @Todo(instance attributes)
reveal_type(box.data) # revealed: @Todo
reveal_type(MyBox.box_model_number) # revealed: Literal[695]
```
@@ -39,7 +39,7 @@ class MySecureBox[T](MyBox[T]): ...
secure_box: MySecureBox[int] = MySecureBox(5)
reveal_type(secure_box) # revealed: MySecureBox
# TODO reveal int
reveal_type(secure_box.data) # revealed: @Todo(instance attributes)
reveal_type(secure_box.data) # revealed: @Todo
```
## Cyclical class definition
@@ -60,20 +60,52 @@ reveal_type(S) # revealed: Literal[S]
## Type params
A PEP695 type variable defines a value of type `typing.TypeVar`.
A PEP695 type variable defines a value of type `typing.TypeVar` with attributes `__name__`,
`__bounds__`, `__constraints__`, and `__default__` (the latter three all lazily evaluated):
```py
def f[T]():
def f[T, U: A, V: (A, B), W = A, X: A = A1]():
reveal_type(T) # revealed: T
reveal_type(T.__name__) # revealed: Literal["T"]
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
reveal_type(T.__default__) # revealed: NoDefault
reveal_type(U) # revealed: U
reveal_type(U.__name__) # revealed: Literal["U"]
reveal_type(U.__bound__) # revealed: type[A]
reveal_type(U.__constraints__) # revealed: tuple[()]
reveal_type(U.__default__) # revealed: NoDefault
reveal_type(V) # revealed: V
reveal_type(V.__name__) # revealed: Literal["V"]
reveal_type(V.__bound__) # revealed: None
reveal_type(V.__constraints__) # revealed: tuple[type[A], type[B]]
reveal_type(V.__default__) # revealed: NoDefault
reveal_type(W) # revealed: W
reveal_type(W.__name__) # revealed: Literal["W"]
reveal_type(W.__bound__) # revealed: None
reveal_type(W.__constraints__) # revealed: tuple[()]
reveal_type(W.__default__) # revealed: type[A]
reveal_type(X) # revealed: X
reveal_type(X.__name__) # revealed: Literal["X"]
reveal_type(X.__bound__) # revealed: type[A]
reveal_type(X.__constraints__) # revealed: tuple[()]
reveal_type(X.__default__) # revealed: type[A1]
class A: ...
class B: ...
class A1(A): ...
```
## Minimum two constraints
A typevar with less than two constraints emits a diagnostic:
A typevar with less than two constraints emits a diagnostic and is treated as unconstrained:
```py
# error: [invalid-typevar-constraints] "TypeVar must have at least two constrained types"
def f[T: (int,)]():
pass
reveal_type(T.__constraints__) # revealed: tuple[()]
```

View File

@@ -75,11 +75,10 @@ Literal: _SpecialForm
```py
from other import Literal
# error: [annotation-with-invalid-expression]
a1: Literal[26]
def f():
reveal_type(a1) # revealed: @Todo(generics)
reveal_type(a1) # revealed: @Todo
```
## Detecting typing_extensions.Literal

View File

@@ -18,7 +18,7 @@ async def foo():
pass
# TODO: should reveal `Unknown` because `__aiter__` is not defined
# revealed: @Todo(async iterables/iterators)
# revealed: @Todo
# error: [possibly-unresolved-reference]
reveal_type(x)
```
@@ -40,6 +40,6 @@ async def foo():
pass
# error: [possibly-unresolved-reference]
# revealed: @Todo(async iterables/iterators)
# revealed: @Todo
reveal_type(x)
```

View File

@@ -52,29 +52,3 @@ else:
reveal_type(x) # revealed: Literal[2, 3]
reveal_type(y) # revealed: Literal[1, 2, 4]
```
## Nested while loops
```py
def flag() -> bool:
return True
x = 1
while flag():
x = 2
while flag():
x = 3
if flag():
break
else:
x = 4
if flag():
break
else:
x = 5
reveal_type(x) # revealed: Literal[3, 4, 5]
```

View File

@@ -171,7 +171,7 @@ def f(*args, **kwargs) -> int: ...
class A(metaclass=f): ...
# TODO should be `type[int]`
reveal_type(A.__class__) # revealed: @Todo(metaclass not a class)
reveal_type(A.__class__) # revealed: @Todo
```
## Cyclic

View File

@@ -17,7 +17,8 @@ reveal_type(__doc__) # revealed: str | None
# (needs support for `*` imports)
reveal_type(__spec__) # revealed: Unknown | None
reveal_type(__path__) # revealed: @Todo(generics)
# TODO: generics
reveal_type(__path__) # revealed: @Todo
class X:
reveal_type(__name__) # revealed: str
@@ -63,7 +64,7 @@ reveal_type(typing.__class__) # revealed: Literal[type]
# TODO: needs support for attribute access on instances, properties and generics;
# should be `dict[str, Any]`
reveal_type(typing.__dict__) # revealed: @Todo(instance attributes)
reveal_type(typing.__dict__) # revealed: @Todo
```
Typeshed includes a fake `__getattr__` method in the stub for `types.ModuleType` to help out with
@@ -95,8 +96,8 @@ from foo import __dict__ as foo_dict
# TODO: needs support for attribute access on instances, properties, and generics;
# should be `dict[str, Any]` for both of these:
reveal_type(foo.__dict__) # revealed: @Todo(instance attributes)
reveal_type(foo_dict) # revealed: @Todo(instance attributes)
reveal_type(foo.__dict__) # revealed: @Todo
reveal_type(foo_dict) # revealed: @Todo
```
## Conditionally global or `ModuleType` attribute

View File

@@ -27,7 +27,7 @@ def int_instance() -> int:
a = b"abcde"[int_instance()]
# TODO: Support overloads... Should be `bytes`
reveal_type(a) # revealed: @Todo(return type)
reveal_type(a) # revealed: @Todo
```
## Slices
@@ -47,11 +47,11 @@ def int_instance() -> int: ...
byte_slice1 = b[int_instance() : int_instance()]
# TODO: Support overloads... Should be `bytes`
reveal_type(byte_slice1) # revealed: @Todo(return type)
reveal_type(byte_slice1) # revealed: @Todo
def bytes_instance() -> bytes: ...
byte_slice2 = bytes_instance()[0:5]
# TODO: Support overloads... Should be `bytes`
reveal_type(byte_slice2) # revealed: @Todo(return type)
reveal_type(byte_slice2) # revealed: @Todo
```

View File

@@ -12,13 +12,13 @@ x = [1, 2, 3]
reveal_type(x) # revealed: list
# TODO reveal int
reveal_type(x[0]) # revealed: @Todo(return type)
reveal_type(x[0]) # revealed: @Todo
# TODO reveal list
reveal_type(x[0:1]) # revealed: @Todo(return type)
reveal_type(x[0:1]) # revealed: @Todo
# TODO error
reveal_type(x["a"]) # revealed: @Todo(return type)
reveal_type(x["a"]) # revealed: @Todo
```
## Assignments within list assignment

View File

@@ -23,7 +23,7 @@ def int_instance() -> int: ...
a = "abcde"[int_instance()]
# TODO: Support overloads... Should be `str`
reveal_type(a) # revealed: @Todo(return type)
reveal_type(a) # revealed: @Todo
```
## Slices
@@ -78,13 +78,13 @@ def int_instance() -> int: ...
substring1 = s[int_instance() : int_instance()]
# TODO: Support overloads... Should be `LiteralString`
reveal_type(substring1) # revealed: @Todo(return type)
reveal_type(substring1) # revealed: @Todo
def str_instance() -> str: ...
substring2 = str_instance()[0:5]
# TODO: Support overloads... Should be `str`
reveal_type(substring2) # revealed: @Todo(return type)
reveal_type(substring2) # revealed: @Todo
```
## Unsupported slice types

View File

@@ -71,5 +71,5 @@ def int_instance() -> int: ...
tuple_slice = t[int_instance() : int_instance()]
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
reveal_type(tuple_slice) # revealed: @Todo(return type)
reveal_type(tuple_slice) # revealed: @Todo
```

View File

@@ -112,9 +112,9 @@ properties on instance types:
```py path=b.py
import sys
reveal_type(sys.version_info.micro) # revealed: @Todo(instance attributes)
reveal_type(sys.version_info.releaselevel) # revealed: @Todo(instance attributes)
reveal_type(sys.version_info.serial) # revealed: @Todo(instance attributes)
reveal_type(sys.version_info.micro) # revealed: @Todo
reveal_type(sys.version_info.releaselevel) # revealed: @Todo
reveal_type(sys.version_info.serial) # revealed: @Todo
```
## Accessing fields by index/slice

View File

@@ -1,71 +0,0 @@
# Type aliases
## Basic
```py
type IntOrStr = int | str
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
x: IntOrStr = 1
reveal_type(x) # revealed: Literal[1]
def f() -> None:
reveal_type(x) # revealed: int | str
```
## `__value__` attribute
```py
type IntOrStr = int | str
# TODO: This should either fall back to the specified type from typeshed,
# which is `Any`, or be the actual type of the runtime value expression
# `int | str`, i.e. `types.UnionType`.
reveal_type(IntOrStr.__value__) # revealed: @Todo(instance attributes)
```
## Invalid assignment
```py
type OptionalInt = int | None
# error: [invalid-assignment]
x: OptionalInt = "1"
```
## Type aliases in type aliases
```py
type IntOrStr = int | str
type IntOrStrOrBytes = IntOrStr | bytes
x: IntOrStrOrBytes = 1
def f() -> None:
reveal_type(x) # revealed: int | str | bytes
```
## Aliased type aliases
```py
type IntOrStr = int | str
MyIntOrStr = IntOrStr
x: MyIntOrStr = 1
# error: [invalid-assignment]
y: MyIntOrStr = None
```
## Generic type aliases
```py
type ListOrSet[T] = list[T] | set[T]
# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`,
# as specified in the `typeshed` stubs.
reveal_type(ListOrSet.__type_params__) # revealed: @Todo(instance attributes)
```

View File

@@ -84,7 +84,7 @@ reveal_type(b) # revealed: Literal[2]
[a, *b, c, d] = (1, 2)
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[2]
reveal_type(d) # revealed: Unknown
```
@@ -95,7 +95,7 @@ reveal_type(d) # revealed: Unknown
[a, *b, c] = (1, 2)
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[2]
```
@@ -105,7 +105,7 @@ reveal_type(c) # revealed: Literal[2]
[a, *b, c] = (1, 2, 3)
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[3]
```
@@ -115,7 +115,7 @@ reveal_type(c) # revealed: Literal[3]
[a, *b, c, d] = (1, 2, 3, 4, 5, 6)
reveal_type(a) # revealed: Literal[1]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: Literal[5]
reveal_type(d) # revealed: Literal[6]
```
@@ -127,7 +127,7 @@ reveal_type(d) # revealed: Literal[6]
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(c) # revealed: @Todo(starred unpacking)
reveal_type(c) # revealed: @Todo
```
### Starred expression (6)
@@ -138,7 +138,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Unknown
reveal_type(c) # revealed: Unknown
reveal_type(d) # revealed: @Todo(starred unpacking)
reveal_type(d) # revealed: @Todo
reveal_type(e) # revealed: Unknown
reveal_type(f) # revealed: Unknown
```
@@ -222,7 +222,7 @@ reveal_type(b) # revealed: LiteralString
(a, *b, c, d) = "ab"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
reveal_type(d) # revealed: Unknown
```
@@ -233,7 +233,7 @@ reveal_type(d) # revealed: Unknown
(a, *b, c) = "ab"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[Any] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
```
@@ -243,7 +243,7 @@ reveal_type(c) # revealed: LiteralString
(a, *b, c) = "abc"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
```
@@ -253,7 +253,7 @@ reveal_type(c) # revealed: LiteralString
(a, *b, c, d) = "abcdef"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(b) # revealed: @Todo
reveal_type(c) # revealed: LiteralString
reveal_type(d) # revealed: LiteralString
```
@@ -265,5 +265,5 @@ reveal_type(d) # revealed: LiteralString
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
# TODO: Should be list[int] once support for assigning to starred expression is added
reveal_type(c) # revealed: @Todo(starred unpacking)
reveal_type(c) # revealed: @Todo
```

View File

@@ -17,5 +17,5 @@ class Manager:
async def test():
async with Manager() as f:
reveal_type(f) # revealed: @Todo(async with statement)
reveal_type(f) # revealed: @Todo
```

View File

@@ -36,25 +36,12 @@ use super::definition::{
mod except_handlers;
/// Are we in a state where a `break` statement is allowed?
#[derive(Clone, Copy, Debug)]
enum LoopState {
InLoop,
NotInLoop,
}
impl LoopState {
fn is_inside(self) -> bool {
matches!(self, LoopState::InLoop)
}
}
pub(super) struct SemanticIndexBuilder<'db> {
// Builder state
db: &'db dyn Db,
file: File,
module: &'db ParsedModule,
scope_stack: Vec<(FileScopeId, LoopState)>,
scope_stack: Vec<FileScopeId>,
/// The assignments we're currently visiting, with
/// the most recent visit at the end of the Vec
current_assignments: Vec<CurrentAssignment<'db>>,
@@ -116,24 +103,9 @@ impl<'db> SemanticIndexBuilder<'db> {
*self
.scope_stack
.last()
.map(|(scope, _)| scope)
.expect("Always to have a root scope")
}
fn loop_state(&self) -> LoopState {
self.scope_stack
.last()
.expect("Always to have a root scope")
.1
}
fn set_inside_loop(&mut self, state: LoopState) {
self.scope_stack
.last_mut()
.expect("Always to have a root scope")
.1 = state;
}
fn push_scope(&mut self, node: NodeWithScopeRef) {
let parent = self.current_scope();
self.push_scope_with_parent(node, Some(parent));
@@ -159,16 +131,15 @@ impl<'db> SemanticIndexBuilder<'db> {
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
self.scope_ids_by_scope.push(scope_id);
let previous = self.scopes_by_node.insert(node.node_key(), file_scope_id);
debug_assert_eq!(previous, None);
self.scopes_by_node.insert(node.node_key(), file_scope_id);
debug_assert_eq!(ast_id_scope, file_scope_id);
self.scope_stack.push((file_scope_id, LoopState::NotInLoop));
self.scope_stack.push(file_scope_id);
}
fn pop_scope(&mut self) -> FileScopeId {
let (id, _) = self.scope_stack.pop().expect("Root scope to be present");
let id = self.scope_stack.pop().expect("Root scope to be present");
let children_end = self.scopes.next_index();
let scope = &mut self.scopes[id];
scope.descendents = scope.descendents.start..children_end;
@@ -618,27 +589,6 @@ where
},
);
}
ast::Stmt::TypeAlias(type_alias) => {
let symbol = self.add_symbol(
type_alias
.name
.as_name_expr()
.map(|name| name.id.clone())
.unwrap_or("<unknown>".into()),
);
self.add_definition(symbol, type_alias);
self.visit_expr(&type_alias.name);
self.with_type_params(
NodeWithScopeRef::TypeAliasTypeParameters(type_alias),
type_alias.type_params.as_ref(),
|builder| {
builder.push_scope(NodeWithScopeRef::TypeAlias(type_alias));
builder.visit_expr(&type_alias.value);
builder.pop_scope()
},
);
}
ast::Stmt::Import(node) => {
for alias in &node.names {
let symbol_name = if let Some(asname) = &alias.asname {
@@ -813,10 +763,7 @@ where
// TODO: definitions created inside the body should be fully visible
// to other statements/expressions inside the body --Alex/Carl
let outer_loop_state = self.loop_state();
self.set_inside_loop(LoopState::InLoop);
self.visit_body(body);
self.set_inside_loop(outer_loop_state);
// Get the break states from the body of this loop, and restore the saved outer
// ones.
@@ -855,9 +802,7 @@ where
self.visit_body(body);
}
ast::Stmt::Break(_) => {
if self.loop_state().is_inside() {
self.loop_break_states.push(self.flow_snapshot());
}
self.loop_break_states.push(self.flow_snapshot());
}
ast::Stmt::For(
@@ -884,10 +829,7 @@ where
// TODO: Definitions created by loop variables
// (and definitions created inside the body)
// are fully visible to other statements/expressions inside the body --Alex/Carl
let outer_loop_state = self.loop_state();
self.set_inside_loop(LoopState::InLoop);
self.visit_body(body);
self.set_inside_loop(outer_loop_state);
let break_states =
std::mem::replace(&mut self.loop_break_states, saved_break_states);
@@ -1189,8 +1131,8 @@ where
// AST inspection, so we can't simplify here, need to record test expression for
// later checking)
self.visit_expr(test);
let pre_if = self.flow_snapshot();
let constraint = self.record_expression_constraint(test);
let pre_if = self.flow_snapshot();
self.visit_expr(body);
let post_body = self.flow_snapshot();
self.flow_restore(pre_if);

View File

@@ -83,7 +83,6 @@ pub(crate) enum DefinitionNodeRef<'a> {
For(ForStmtDefinitionNodeRef<'a>),
Function(&'a ast::StmtFunctionDef),
Class(&'a ast::StmtClassDef),
TypeAlias(&'a ast::StmtTypeAlias),
NamedExpression(&'a ast::ExprNamed),
Assignment(AssignmentDefinitionNodeRef<'a>),
AnnotatedAssignment(&'a ast::StmtAnnAssign),
@@ -110,12 +109,6 @@ impl<'a> From<&'a ast::StmtClassDef> for DefinitionNodeRef<'a> {
}
}
impl<'a> From<&'a ast::StmtTypeAlias> for DefinitionNodeRef<'a> {
fn from(node: &'a ast::StmtTypeAlias) -> Self {
Self::TypeAlias(node)
}
}
impl<'a> From<&'a ast::ExprNamed> for DefinitionNodeRef<'a> {
fn from(node: &'a ast::ExprNamed) -> Self {
Self::NamedExpression(node)
@@ -272,9 +265,6 @@ impl<'db> DefinitionNodeRef<'db> {
DefinitionNodeRef::Class(class) => {
DefinitionKind::Class(AstNodeRef::new(parsed, class))
}
DefinitionNodeRef::TypeAlias(type_alias) => {
DefinitionKind::TypeAlias(AstNodeRef::new(parsed, type_alias))
}
DefinitionNodeRef::NamedExpression(named) => {
DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named))
}
@@ -368,7 +358,6 @@ impl<'db> DefinitionNodeRef<'db> {
}
Self::Function(node) => node.into(),
Self::Class(node) => node.into(),
Self::TypeAlias(node) => node.into(),
Self::NamedExpression(node) => node.into(),
Self::Assignment(AssignmentDefinitionNodeRef {
value: _,
@@ -445,7 +434,6 @@ pub enum DefinitionKind<'db> {
ImportFrom(ImportFromDefinitionKind),
Function(AstNodeRef<ast::StmtFunctionDef>),
Class(AstNodeRef<ast::StmtClassDef>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
NamedExpression(AstNodeRef<ast::ExprNamed>),
Assignment(AssignmentDefinitionKind<'db>),
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
@@ -468,7 +456,6 @@ impl DefinitionKind<'_> {
// functions, classes, and imports always bind, and we consider them declarations
DefinitionKind::Function(_)
| DefinitionKind::Class(_)
| DefinitionKind::TypeAlias(_)
| DefinitionKind::Import(_)
| DefinitionKind::ImportFrom(_)
| DefinitionKind::TypeVar(_)
@@ -695,12 +682,6 @@ impl From<&ast::StmtClassDef> for DefinitionNodeKey {
}
}
impl From<&ast::StmtTypeAlias> for DefinitionNodeKey {
fn from(node: &ast::StmtTypeAlias) -> Self {
Self(NodeKey::from_node(node))
}
}
impl From<&ast::ExprName> for DefinitionNodeKey {
fn from(node: &ast::ExprName) -> Self {
Self(NodeKey::from_node(node))

View File

@@ -116,11 +116,14 @@ impl<'db> ScopeId<'db> {
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
// symbol table also uses the term "function-like" for these scopes.
matches!(
self.node(db).scope_kind(),
ScopeKind::Annotation
| ScopeKind::Function
| ScopeKind::TypeAlias
| ScopeKind::Comprehension
self.node(db),
NodeWithScopeKind::ClassTypeParameters(_)
| NodeWithScopeKind::FunctionTypeParameters(_)
| NodeWithScopeKind::Function(_)
| NodeWithScopeKind::ListComprehension(_)
| NodeWithScopeKind::SetComprehension(_)
| NodeWithScopeKind::DictComprehension(_)
| NodeWithScopeKind::GeneratorExpression(_)
)
}
@@ -141,12 +144,6 @@ impl<'db> ScopeId<'db> {
}
NodeWithScopeKind::Function(function)
| NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(),
NodeWithScopeKind::TypeAlias(type_alias)
| NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias
.name
.as_name_expr()
.map(|name| name.id.as_str())
.unwrap_or("<type alias>"),
NodeWithScopeKind::Lambda(_) => "<lambda>",
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
@@ -204,7 +201,6 @@ pub enum ScopeKind {
Class,
Function,
Comprehension,
TypeAlias,
}
impl ScopeKind {
@@ -330,8 +326,6 @@ pub(crate) enum NodeWithScopeRef<'a> {
Lambda(&'a ast::ExprLambda),
FunctionTypeParameters(&'a ast::StmtFunctionDef),
ClassTypeParameters(&'a ast::StmtClassDef),
TypeAlias(&'a ast::StmtTypeAlias),
TypeAliasTypeParameters(&'a ast::StmtTypeAlias),
ListComprehension(&'a ast::ExprListComp),
SetComprehension(&'a ast::ExprSetComp),
DictComprehension(&'a ast::ExprDictComp),
@@ -353,12 +347,6 @@ impl NodeWithScopeRef<'_> {
NodeWithScopeRef::Function(function) => {
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
}
NodeWithScopeRef::TypeAlias(type_alias) => {
NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias))
}
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias))
}
NodeWithScopeRef::Lambda(lambda) => {
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
}
@@ -399,12 +387,6 @@ impl NodeWithScopeRef<'_> {
NodeWithScopeRef::ClassTypeParameters(class) => {
NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class))
}
NodeWithScopeRef::TypeAlias(type_alias) => {
NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias))
}
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias))
}
NodeWithScopeRef::ListComprehension(comprehension) => {
NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension))
}
@@ -429,8 +411,6 @@ pub enum NodeWithScopeKind {
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
Function(AstNodeRef<ast::StmtFunctionDef>),
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
Lambda(AstNodeRef<ast::ExprLambda>),
ListComprehension(AstNodeRef<ast::ExprListComp>),
SetComprehension(AstNodeRef<ast::ExprSetComp>),
@@ -443,11 +423,9 @@ impl NodeWithScopeKind {
match self {
Self::Module => ScopeKind::Module,
Self::Class(_) => ScopeKind::Class,
Self::Function(_) | Self::Lambda(_) => ScopeKind::Function,
Self::FunctionTypeParameters(_)
| Self::ClassTypeParameters(_)
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
Self::TypeAlias(_) => ScopeKind::TypeAlias,
Self::Function(_) => ScopeKind::Function,
Self::Lambda(_) => ScopeKind::Function,
Self::FunctionTypeParameters(_) | Self::ClassTypeParameters(_) => ScopeKind::Annotation,
Self::ListComprehension(_)
| Self::SetComprehension(_)
| Self::DictComprehension(_)
@@ -468,13 +446,6 @@ impl NodeWithScopeKind {
_ => panic!("expected function"),
}
}
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
match self {
Self::TypeAlias(type_alias) => type_alias.node(),
_ => panic!("expected type alias"),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@@ -484,8 +455,6 @@ pub(crate) enum NodeWithScopeKey {
ClassTypeParameters(NodeKey),
Function(NodeKey),
FunctionTypeParameters(NodeKey),
TypeAlias(NodeKey),
TypeAliasTypeParameters(NodeKey),
Lambda(NodeKey),
ListComprehension(NodeKey),
SetComprehension(NodeKey),

View File

@@ -324,61 +324,6 @@ fn declarations_ty<'db>(
}
}
/// Meta data for `Type::Todo`, which represents a known limitation in red-knot.
#[cfg(debug_assertions)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum TodoType {
FileAndLine(&'static str, u32),
Message(&'static str),
}
#[cfg(debug_assertions)]
impl std::fmt::Display for TodoType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TodoType::FileAndLine(file, line) => write!(f, "[{file}:{line}]"),
TodoType::Message(msg) => write!(f, "({msg})"),
}
}
}
#[cfg(not(debug_assertions))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TodoType;
#[cfg(not(debug_assertions))]
impl std::fmt::Display for TodoType {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
/// Create a `Type::Todo` variant to represent a known limitation in the type system.
///
/// It can be used with a custom message (preferred): `todo_type!("PEP 604 not supported")`,
/// or simply using `todo_type!()`, which will include information about the file and line.
#[cfg(debug_assertions)]
macro_rules! todo_type {
() => {
Type::Todo(crate::types::TodoType::FileAndLine(file!(), line!()))
};
($message:literal) => {
Type::Todo(crate::types::TodoType::Message($message))
};
}
#[cfg(not(debug_assertions))]
macro_rules! todo_type {
() => {
Type::Todo(crate::types::TodoType)
};
($message:literal) => {
Type::Todo(crate::types::TodoType)
};
}
pub(crate) use todo_type;
/// Representation of a type: a set of possible values at runtime.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
pub enum Type<'db> {
@@ -395,9 +340,7 @@ pub enum Type<'db> {
/// General rule: `Todo` should only propagate when the presence of the input `Todo` caused the
/// output to be unknown. An output should only be `Todo` if fixing all `Todo` inputs to be not
/// `Todo` would change the output type.
///
/// This variant should be created with the `todo_type!` macro.
Todo(TodoType),
Todo,
/// The empty set of values
Never,
/// A specific function object
@@ -441,7 +384,7 @@ impl<'db> Type<'db> {
}
pub const fn is_todo(&self) -> bool {
matches!(self, Type::Todo(_))
matches!(self, Type::Todo)
}
pub const fn class_literal(class: Class<'db>) -> Self {
@@ -537,19 +480,6 @@ impl<'db> Type<'db> {
.expect("Expected a Type::IntLiteral variant")
}
pub const fn into_known_instance(self) -> Option<KnownInstanceType<'db>> {
match self {
Type::KnownInstance(known_instance) => Some(known_instance),
_ => None,
}
}
#[track_caller]
pub fn expect_known_instance(self) -> KnownInstanceType<'db> {
self.into_known_instance()
.expect("Expected a Type::KnownInstance variant")
}
pub const fn is_boolean_literal(&self) -> bool {
matches!(self, Type::BooleanLiteral(..))
}
@@ -600,8 +530,8 @@ impl<'db> Type<'db> {
return true;
}
match (self, target) {
(Type::Unknown | Type::Any | Type::Todo(_), _) => false,
(_, Type::Unknown | Type::Any | Type::Todo(_)) => false,
(Type::Unknown | Type::Any | Type::Todo, _) => false,
(_, Type::Unknown | Type::Any | Type::Todo) => false,
(Type::Never, _) => true,
(_, Type::Never) => false,
(_, Type::Instance(InstanceType { class }))
@@ -736,8 +666,8 @@ impl<'db> Type<'db> {
return true;
}
match (self, target) {
(Type::Unknown | Type::Any | Type::Todo(_), _) => true,
(_, Type::Unknown | Type::Any | Type::Todo(_)) => true,
(Type::Unknown | Type::Any | Type::Todo, _) => true,
(_, Type::Unknown | Type::Any | Type::Todo) => true,
(Type::Union(union), ty) => union
.elements(db)
.iter()
@@ -773,7 +703,6 @@ impl<'db> Type<'db> {
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
// we understand `sys.version_info` branches.
self == other
|| matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|| matches!((self, other),
(
Type::Instance(InstanceType { class: self_class }),
@@ -797,7 +726,7 @@ impl<'db> Type<'db> {
(Type::Any, _) | (_, Type::Any) => false,
(Type::Unknown, _) | (_, Type::Unknown) => false,
(Type::Todo(_), _) | (_, Type::Todo(_)) => false,
(Type::Todo, _) | (_, Type::Todo) => false,
(Type::Union(union), other) | (other, Type::Union(union)) => union
.elements(db)
@@ -1002,7 +931,7 @@ impl<'db> Type<'db> {
Type::Any
| Type::Never
| Type::Unknown
| Type::Todo(_)
| Type::Todo
| Type::IntLiteral(..)
| Type::StringLiteral(..)
| Type::BytesLiteral(..)
@@ -1078,10 +1007,7 @@ impl<'db> Type<'db> {
Type::Instance(InstanceType { class }) => match class.known(db) {
Some(
KnownClass::NoneType
| KnownClass::NoDefaultType
| KnownClass::VersionInfo
| KnownClass::TypeAliasType,
KnownClass::NoneType | KnownClass::NoDefaultType | KnownClass::VersionInfo,
) => true,
Some(
KnownClass::Bool
@@ -1108,7 +1034,7 @@ impl<'db> Type<'db> {
Type::Any
| Type::Never
| Type::Unknown
| Type::Todo(_)
| Type::Todo
| Type::Union(..)
| Type::Intersection(..)
| Type::LiteralString => false,
@@ -1126,12 +1052,12 @@ impl<'db> Type<'db> {
Type::Any => Type::Any.into(),
Type::Never => {
// TODO: attribute lookup on Never type
todo_type!().into()
Type::Todo.into()
}
Type::Unknown => Type::Unknown.into(),
Type::FunctionLiteral(_) => {
// TODO: attribute lookup on function type
todo_type!().into()
Type::Todo.into()
}
Type::ModuleLiteral(file) => {
// `__dict__` is a very special member that is never overridden by module globals;
@@ -1181,7 +1107,7 @@ impl<'db> Type<'db> {
Type::IntLiteral(Program::get(db).target_version(db).minor.into())
}
// TODO MRO? get_own_instance_member, get_instance_member
_ => todo_type!("instance attributes"),
_ => Type::Todo,
};
ty.into()
}
@@ -1223,36 +1149,36 @@ impl<'db> Type<'db> {
Type::Intersection(_) => {
// TODO perform the get_member on each type in the intersection
// TODO return the intersection of those results
todo_type!().into()
Type::Todo.into()
}
Type::IntLiteral(_) => {
// TODO raise error
todo_type!().into()
Type::Todo.into()
}
Type::BooleanLiteral(_) => todo_type!().into(),
Type::BooleanLiteral(_) => Type::Todo.into(),
Type::StringLiteral(_) => {
// TODO defer to `typing.LiteralString`/`builtins.str` methods
// from typeshed's stubs
todo_type!().into()
Type::Todo.into()
}
Type::LiteralString => {
// TODO defer to `typing.LiteralString`/`builtins.str` methods
// from typeshed's stubs
todo_type!().into()
Type::Todo.into()
}
Type::BytesLiteral(_) => {
// TODO defer to Type::Instance(<bytes from typeshed>).member
todo_type!().into()
Type::Todo.into()
}
Type::SliceLiteral(_) => {
// TODO defer to `builtins.slice` methods
todo_type!().into()
Type::Todo.into()
}
Type::Tuple(_) => {
// TODO: implement tuple methods
todo_type!().into()
Type::Todo.into()
}
&todo @ Type::Todo(_) => todo.into(),
Type::Todo => Type::Todo.into(),
}
}
@@ -1262,7 +1188,7 @@ impl<'db> Type<'db> {
/// when `bool(x)` is called on an object `x`.
fn bool(&self, db: &'db dyn Db) -> Truthiness {
match self {
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
Type::Any | Type::Todo | Type::Never | Type::Unknown => Truthiness::Ambiguous,
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
Type::ClassLiteral(_) => {
@@ -1403,7 +1329,7 @@ impl<'db> Type<'db> {
// `Any` is callable, and its return type is also `Any`.
Type::Any => CallOutcome::callable(Type::Any),
Type::Todo(_) => CallOutcome::callable(todo_type!()),
Type::Todo => CallOutcome::callable(Type::Todo),
Type::Unknown => CallOutcome::callable(Type::Unknown),
@@ -1416,7 +1342,7 @@ impl<'db> Type<'db> {
),
// TODO: intersection types
Type::Intersection(_) => CallOutcome::callable(todo_type!()),
Type::Intersection(_) => CallOutcome::callable(Type::Todo),
_ => CallOutcome::not_callable(self),
}
@@ -1455,7 +1381,7 @@ impl<'db> Type<'db> {
};
}
if matches!(self, Type::Unknown | Type::Any | Type::Todo(_)) {
if matches!(self, Type::Unknown | Type::Any | Type::Todo) {
// Explicit handling of `Unknown` and `Any` necessary until `type[Unknown]` and
// `type[Any]` are not defined as `Todo` anymore.
return IterationOutcome::Iterable { element_ty: self };
@@ -1514,14 +1440,14 @@ impl<'db> Type<'db> {
pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> {
match self {
Type::Any => Type::Any,
todo @ Type::Todo(_) => *todo,
Type::Todo => Type::Todo,
Type::Unknown => Type::Unknown,
Type::Never => Type::Never,
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class),
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
// TODO: we can probably do better here: --Alex
Type::Intersection(_) => todo_type!(),
Type::Intersection(_) => Type::Todo,
// TODO: calling `.to_instance()` on any of these should result in a diagnostic,
// since they already indicate that the object is an instance of some kind:
Type::BooleanLiteral(_)
@@ -1552,11 +1478,7 @@ impl<'db> Type<'db> {
Type::Unknown => Type::Unknown,
// TODO map this to a new `Type::TypeVar` variant
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
Type::Never
}
_ => todo_type!(),
_ => Type::Todo,
}
}
@@ -1631,8 +1553,8 @@ impl<'db> Type<'db> {
// TODO: `type[Unknown]`?
Type::Unknown => Type::Unknown,
// TODO intersections
Type::Intersection(_) => todo_type!(),
todo @ Type::Todo(_) => *todo,
Type::Intersection(_) => Type::Todo,
Type::Todo => Type::Todo,
}
}
@@ -1720,7 +1642,6 @@ pub enum KnownClass {
// Typing
SpecialForm,
TypeVar,
TypeAliasType,
NoDefaultType,
// sys
VersionInfo,
@@ -1747,7 +1668,6 @@ impl<'db> KnownClass {
Self::NoneType => "NoneType",
Self::SpecialForm => "_SpecialForm",
Self::TypeVar => "TypeVar",
Self::TypeAliasType => "TypeAliasType",
Self::NoDefaultType => "_NoDefaultType",
// This is the name the type of `sys.version_info` has in typeshed,
// which is different to what `type(sys.version_info).__name__` is at runtime.
@@ -1786,7 +1706,7 @@ impl<'db> KnownClass {
Self::VersionInfo => CoreStdlibModule::Sys,
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
Self::NoneType => CoreStdlibModule::Typeshed,
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
Self::SpecialForm | Self::TypeVar => CoreStdlibModule::Typing,
// TODO when we understand sys.version_info, we will need an explicit fallback here,
// because typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
// singleton, but not for `typing._NoDefaultType`
@@ -1800,7 +1720,7 @@ impl<'db> KnownClass {
const fn is_singleton(self) -> bool {
// TODO there are other singleton types (EllipsisType, NotImplementedType)
match self {
Self::NoneType | Self::NoDefaultType | Self::VersionInfo | Self::TypeAliasType => true,
Self::NoneType | Self::NoDefaultType | Self::VersionInfo => true,
Self::Bool
| Self::Object
| Self::Bytes
@@ -1842,7 +1762,6 @@ impl<'db> KnownClass {
"NoneType" => Self::NoneType,
"ModuleType" => Self::ModuleType,
"FunctionType" => Self::FunctionType,
"TypeAliasType" => Self::TypeAliasType,
"_SpecialForm" => Self::SpecialForm,
"_NoDefaultType" => Self::NoDefaultType,
"_version_info" => Self::VersionInfo,
@@ -1876,7 +1795,7 @@ impl<'db> KnownClass {
| Self::VersionInfo
| Self::FunctionType => module.name() == self.canonical_module().as_str(),
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => {
matches!(module.name().as_str(), "typing" | "typing_extensions")
}
}
@@ -1890,42 +1809,24 @@ pub enum KnownInstanceType<'db> {
Literal,
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
Optional,
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
Union,
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
NoReturn,
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
Never,
/// A single instance of `typing.TypeVar`
TypeVar(TypeVarInstance<'db>),
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
TypeAliasType(TypeAliasType<'db>),
// TODO: fill this enum out with more special forms, etc.
}
impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
Self::Literal => "Literal",
Self::Optional => "Optional",
Self::Union => "Union",
Self::TypeVar(_) => "TypeVar",
Self::NoReturn => "NoReturn",
Self::Never => "Never",
Self::TypeAliasType(_) => "TypeAliasType",
KnownInstanceType::Literal => "Literal",
KnownInstanceType::Optional => "Optional",
KnownInstanceType::TypeVar(_) => "TypeVar",
}
}
/// Evaluate the known instance in boolean context
pub const fn bool(self) -> Truthiness {
match self {
Self::Literal
| Self::Optional
| Self::TypeVar(_)
| Self::Union
| Self::NoReturn
| Self::Never
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
Self::Literal | Self::Optional | Self::TypeVar(_) => Truthiness::AlwaysTrue,
}
}
@@ -1934,11 +1835,7 @@ impl<'db> KnownInstanceType<'db> {
match self {
Self::Literal => "typing.Literal",
Self::Optional => "typing.Optional",
Self::Union => "typing.Union",
Self::NoReturn => "typing.NoReturn",
Self::Never => "typing.Never",
Self::TypeVar(typevar) => typevar.name(db),
Self::TypeAliasType(_) => "typing.TypeAliasType",
}
}
@@ -1947,11 +1844,7 @@ impl<'db> KnownInstanceType<'db> {
match self {
Self::Literal => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
Self::Union => KnownClass::SpecialForm,
Self::NoReturn => KnownClass::SpecialForm,
Self::Never => KnownClass::SpecialForm,
Self::TypeVar(_) => KnownClass::TypeVar,
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
}
}
@@ -1971,9 +1864,6 @@ impl<'db> KnownInstanceType<'db> {
match (module.name().as_str(), instance_name) {
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
("typing" | "typing_extensions", "Union") => Some(Self::Union),
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
("typing" | "typing_extensions", "Never") => Some(Self::Never),
_ => None,
}
}
@@ -1981,7 +1871,23 @@ impl<'db> KnownInstanceType<'db> {
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
let ty = match (self, name) {
(Self::TypeVar(typevar), "__name__") => Type::string_literal(db, typevar.name(db)),
(Self::TypeAliasType(alias), "__name__") => Type::string_literal(db, alias.name(db)),
(Self::TypeVar(typevar), "__bound__") => typevar
.upper_bound(db)
.map(|ty| ty.to_meta_type(db))
.unwrap_or_else(|| KnownClass::NoneType.to_instance(db)),
(Self::TypeVar(typevar), "__constraints__") => {
let tuple_elements: Vec<Type<'db>> = typevar
.constraints(db)
.unwrap_or_default()
.iter()
.map(|ty| ty.to_meta_type(db))
.collect();
Type::tuple(db, &tuple_elements)
}
(Self::TypeVar(typevar), "__default__") => typevar
.default_ty(db)
.map(|ty| ty.to_meta_type(db))
.unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)),
_ => return self.instance_fallback(db).member(db, name),
};
ty.into()
@@ -2013,7 +1919,6 @@ pub struct TypeVarInstance<'db> {
}
impl<'db> TypeVarInstance<'db> {
#[allow(unused)]
pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option<Type<'db>> {
if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) {
Some(ty)
@@ -2022,7 +1927,6 @@ impl<'db> TypeVarInstance<'db> {
}
}
#[allow(unused)]
pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&[Type<'db>]> {
if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
Some(tuple.elements(db))
@@ -2695,7 +2599,7 @@ impl<'db> Class<'db> {
// TODO: If the metaclass is not a class, we should verify that it's a callable
// which accepts the same arguments as `type.__new__` (otherwise error), and return
// the meta-type of its return type. (And validate that is a class type?)
return Ok(todo_type!("metaclass not a class"));
return Ok(Type::Todo);
};
// Reconcile all base classes' metaclasses with the candidate metaclass.
@@ -2809,27 +2713,6 @@ impl<'db> Class<'db> {
}
}
#[salsa::interned]
pub struct TypeAliasType<'db> {
#[return_ref]
pub name: ast::name::Name,
rhs_scope: ScopeId<'db>,
}
#[salsa::tracked]
impl<'db> TypeAliasType<'db> {
#[salsa::tracked]
pub fn value_ty(self, db: &'db dyn Db) -> Type<'db> {
let scope = self.rhs_scope(db);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
let definition = semantic_index(db, scope.file(db)).definition(type_alias_stmt_node);
definition_expression_ty(db, definition, &type_alias_stmt_node.value)
}
}
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct MetaclassCandidate<'db> {
@@ -3016,11 +2899,6 @@ impl<'db> TupleType<'db> {
}
}
// Make sure that the `Type` enum does not grow unexpectedly.
#[cfg(not(debug_assertions))]
#[cfg(target_pointer_width = "64")]
static_assertions::assert_eq_size!(Type, [u8; 16]);
#[cfg(test)]
pub(crate) mod tests {
use super::*;
@@ -3036,6 +2914,13 @@ pub(crate) mod tests {
use ruff_python_ast as ast;
use test_case::test_case;
#[cfg(target_pointer_width = "64")]
#[test]
fn no_bloat_enum_sizes() {
use std::mem::size_of;
assert_eq!(size_of::<Type>(), 16);
}
pub(crate) fn setup_db() -> TestDb {
let db = TestDb::new();
@@ -3089,7 +2974,7 @@ pub(crate) mod tests {
Ty::Unknown => Type::Unknown,
Ty::None => Type::none(db),
Ty::Any => Type::Any,
Ty::Todo => todo_type!("Ty::Todo"),
Ty::Todo => Type::Todo,
Ty::IntLiteral(n) => Type::IntLiteral(n),
Ty::StringLiteral(s) => Type::string_literal(db, s),
Ty::BooleanLiteral(b) => Type::BooleanLiteral(b),
@@ -3680,95 +3565,4 @@ pub(crate) mod tests {
Ok(())
}
#[test]
fn type_alias_types() -> anyhow::Result<()> {
let mut db = setup_db();
db.write_dedented(
"src/mod.py",
r#"
type Alias1 = int
type Alias2 = int
"#,
)?;
let mod_py = system_path_to_file(&db, "src/mod.py")?;
let ty_alias1 = global_symbol(&db, mod_py, "Alias1").expect_type();
let ty_alias2 = global_symbol(&db, mod_py, "Alias2").expect_type();
let Type::KnownInstance(KnownInstanceType::TypeAliasType(alias1)) = ty_alias1 else {
panic!("Expected TypeAliasType, got {ty_alias1:?}");
};
assert_eq!(alias1.name(&db), "Alias1");
assert_eq!(alias1.value_ty(&db), KnownClass::Int.to_instance(&db));
// Two type aliases are distinct and disjoint, even if they refer to the same type
assert!(!ty_alias1.is_equivalent_to(&db, ty_alias2));
assert!(ty_alias1.is_disjoint_from(&db, ty_alias2));
Ok(())
}
/// All other tests also make sure that `Type::Todo` works as expected. This particular
/// test makes sure that we handle `Todo` types correctly, even if they originate from
/// different sources.
#[test]
fn todo_types() {
let db = setup_db();
let todo1 = todo_type!("1");
let todo2 = todo_type!("2");
let todo3 = todo_type!();
let todo4 = todo_type!();
assert!(todo1.is_equivalent_to(&db, todo2));
assert!(todo3.is_equivalent_to(&db, todo4));
assert!(todo1.is_equivalent_to(&db, todo3));
assert!(todo1.is_subtype_of(&db, todo2));
assert!(todo2.is_subtype_of(&db, todo1));
assert!(todo3.is_subtype_of(&db, todo4));
assert!(todo4.is_subtype_of(&db, todo3));
assert!(todo1.is_subtype_of(&db, todo3));
assert!(todo3.is_subtype_of(&db, todo1));
let int = KnownClass::Int.to_instance(&db);
assert!(int.is_assignable_to(&db, todo1));
assert!(int.is_assignable_to(&db, todo3));
assert!(todo1.is_assignable_to(&db, int));
assert!(todo3.is_assignable_to(&db, int));
// We lose information when combining several `Todo` types. This is an
// acknowledged limitation of the current implementation. We can not
// easily store the meta information of several `Todo`s in a single
// variant, as `TodoType` needs to implement `Copy`, meaning it can't
// contain `Vec`/`Box`/etc., and can't be boxed itself.
//
// Lifting this restriction would require us to intern `TodoType` in
// salsa, but that would mean we would have to pass in `db` everywhere.
// A union of several `Todo` types collapses to a single `Todo` type:
assert!(UnionType::from_elements(&db, vec![todo1, todo2, todo3, todo4]).is_todo());
// And similar for intersection types:
assert!(IntersectionBuilder::new(&db)
.add_positive(todo1)
.add_positive(todo2)
.add_positive(todo3)
.add_positive(todo4)
.build()
.is_todo());
assert!(IntersectionBuilder::new(&db)
.add_positive(todo1)
.add_negative(todo2)
.add_positive(todo3)
.add_negative(todo4)
.build()
.is_todo());
}
}

View File

@@ -31,7 +31,7 @@ use crate::{Db, FxOrderSet};
use smallvec::SmallVec;
pub(crate) struct UnionBuilder<'db> {
elements: Vec<Type<'db>>,
elements: SmallVec<[Type<'db>; 1]>,
db: &'db dyn Db,
}
@@ -39,7 +39,7 @@ impl<'db> UnionBuilder<'db> {
pub(crate) fn new(db: &'db dyn Db) -> Self {
Self {
db,
elements: vec![],
elements: SmallVec::new(),
}
}
@@ -48,13 +48,22 @@ impl<'db> UnionBuilder<'db> {
match ty {
Type::Union(union) => {
let new_elements = union.elements(self.db);
self.elements.reserve(new_elements.len());
for element in new_elements {
self = self.add(*element);
if self.elements.is_empty() {
self.elements.extend_from_slice(new_elements);
} else {
self.elements.reserve(new_elements.len());
for element in new_elements {
self = self.add(*element);
}
}
}
Type::Never => {}
_ => {
if self.elements.is_empty() {
self.elements.push(ty);
return self;
}
let bool_pair = if let Type::BooleanLiteral(b) = ty {
Some(Type::BooleanLiteral(!b))
} else {
@@ -309,7 +318,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.add_positive(db, *neg);
}
}
ty @ (Type::Any | Type::Unknown | Type::Todo(_)) => {
ty @ (Type::Any | Type::Unknown | Type::Todo) => {
// Adding any of these types to the negative side of an intersection
// is equivalent to adding it to the positive side. We do this to
// simplify the representation.
@@ -379,7 +388,7 @@ mod tests {
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::stdlib::typing_symbol;
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
use crate::types::{global_symbol, KnownClass, UnionBuilder};
use crate::ProgramSettings;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
@@ -987,7 +996,7 @@ mod tests {
#[test_case(Type::Any)]
#[test_case(Type::Unknown)]
#[test_case(todo_type!())]
#[test_case(Type::Todo)]
fn build_intersection_t_and_negative_t_does_not_simplify(ty: Type) {
let db = setup_db();

View File

@@ -77,7 +77,7 @@ impl Display for DisplayRepresentation<'_> {
}
// `[Type::Todo]`'s display should be explicit that is not a valid display of
// any other type
Type::Todo(todo) => write!(f, "@Todo{todo}"),
Type::Todo => f.write_str("@Todo"),
Type::ModuleLiteral(file) => {
write!(f, "<module '{:?}'>", file.path(self.db))
}

View File

@@ -31,7 +31,6 @@ use std::num::NonZeroU32;
use itertools::Itertools;
use ruff_db::files::File;
use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::{self, Visitor};
use ruff_python_ast::{self as ast, AnyNodeRef, Expr, ExprContext, UnaryOp};
use rustc_hash::{FxHashMap, FxHashSet};
use salsa;
@@ -53,12 +52,11 @@ use crate::types::diagnostic::{TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder
use crate::types::mro::MroErrorKind;
use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type,
typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType,
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol,
Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints,
TypeVarInstance, UnionBuilder, UnionType,
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol,
Boundness, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder,
IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType,
MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, Truthiness, TupleType, Type,
TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@@ -440,12 +438,6 @@ impl<'db> TypeInferenceBuilder<'db> {
NodeWithScopeKind::FunctionTypeParameters(function) => {
self.infer_function_type_params(function.node());
}
NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => {
self.infer_type_alias_type_params(type_alias.node());
}
NodeWithScopeKind::TypeAlias(type_alias) => {
self.infer_type_alias(type_alias.node());
}
NodeWithScopeKind::ListComprehension(comprehension) => {
self.infer_list_comprehension_expression_scope(comprehension.node());
}
@@ -613,9 +605,6 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_function_definition(function.node(), definition);
}
DefinitionKind::Class(class) => self.infer_class_definition(class.node(), definition),
DefinitionKind::TypeAlias(type_alias) => {
self.infer_type_alias_definition(type_alias.node(), definition);
}
DefinitionKind::Import(import) => {
self.infer_import_definition(import.node(), definition);
}
@@ -858,19 +847,6 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_parameters(&function.parameters);
}
fn infer_type_alias_type_params(&mut self, type_alias: &ast::StmtTypeAlias) {
let type_params = type_alias
.type_params
.as_ref()
.expect("type alias type params scope without type params");
self.infer_type_parameters(type_params);
}
fn infer_type_alias(&mut self, type_alias: &ast::StmtTypeAlias) {
self.infer_annotation_expression(&type_alias.value, DeferredExpressionState::Deferred);
}
fn infer_function_body(&mut self, function: &ast::StmtFunctionDef) {
self.infer_body(&function.body);
}
@@ -1051,7 +1027,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) {
// TODO(dhruvmanila): Annotation expression is resolved at the enclosing scope, infer the
// parameter type from there
let annotated_ty = todo_type!("function parameter type");
let annotated_ty = Type::Todo;
if parameter.annotation.is_some() {
self.add_declaration_with_binding(
parameter.into(),
@@ -1131,33 +1107,6 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
fn infer_type_alias_definition(
&mut self,
type_alias: &ast::StmtTypeAlias,
definition: Definition<'db>,
) {
self.infer_expression(&type_alias.name);
let rhs_scope = self
.index
.node_scope(NodeWithScopeRef::TypeAlias(type_alias))
.to_scope_id(self.db, self.file);
let type_alias_ty =
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::new(
self.db,
&type_alias.name.as_name_expr().unwrap().id,
rhs_scope,
)));
self.add_declaration_with_binding(
type_alias.into(),
definition,
type_alias_ty,
type_alias_ty,
);
}
fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) {
let ast::StmtIf {
range: _,
@@ -1287,7 +1236,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) -> Type<'db> {
// TODO: Handle async with statements (they use `aenter` and `aexit`)
if is_async {
return todo_type!("async with statement");
return Type::Todo;
}
let context_manager_ty = context_expression_ty.to_meta_type(self.db);
@@ -1437,12 +1386,12 @@ impl<'db> TypeInferenceBuilder<'db> {
self.db,
tuple.elements(self.db).iter().map(|ty| {
ty.into_class_literal()
.map_or(todo_type!(), |ClassLiteralType { class }| {
.map_or(Type::Todo, |ClassLiteralType { class }| {
Type::instance(class)
})
}),
),
_ => todo_type!("exception type"),
_ => Type::Todo,
}
};
@@ -1512,7 +1461,7 @@ impl<'db> TypeInferenceBuilder<'db> {
default,
} = node;
self.infer_optional_expression(default.as_deref());
self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!());
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
}
fn infer_typevartuple_definition(
@@ -1526,7 +1475,7 @@ impl<'db> TypeInferenceBuilder<'db> {
default,
} = node;
self.infer_optional_expression(default.as_deref());
self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!());
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
}
fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
@@ -1561,7 +1510,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// against the subject expression type (which we can query via `infer_expression_types`)
// and extract the type at the `index` position if the pattern matches. This will be
// similar to the logic in `self.infer_assignment_definition`.
self.add_binding(pattern.into(), definition, todo_type!());
self.add_binding(pattern.into(), definition, Type::Todo);
}
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
@@ -1880,8 +1829,17 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_augmented_op(assignment, target_type, value_type)
}
fn infer_type_alias_statement(&mut self, node: &ast::StmtTypeAlias) {
self.infer_definition(node);
fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) {
let ast::StmtTypeAlias {
range: _,
name,
type_params: _,
value,
} = type_alias_statement;
self.infer_expression(value);
self.infer_expression(name);
// TODO: properly handle generic type aliases, which need their own annotation scope
}
fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) {
@@ -1916,7 +1874,8 @@ impl<'db> TypeInferenceBuilder<'db> {
let iterable_ty = self.infer_standalone_expression(iterable);
let loop_var_value_ty = if is_async {
todo_type!("async iterables/iterators")
// TODO(Alex): async iterables/iterators!
Type::Todo
} else {
iterable_ty
.iterate(self.db)
@@ -2244,7 +2203,7 @@ impl<'db> TypeInferenceBuilder<'db> {
ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression),
ast::Expr::IpyEscapeCommand(_) => {
// TODO Implement Ipy escape command support
todo_type!()
Type::Todo
}
};
@@ -2438,7 +2397,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators);
// TODO generator type
todo_type!()
Type::Todo
}
fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> {
@@ -2451,7 +2410,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators);
// TODO list type
todo_type!()
Type::Todo
}
fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> {
@@ -2465,7 +2424,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators);
// TODO dict type
todo_type!()
Type::Todo
}
fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> {
@@ -2478,7 +2437,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators);
// TODO set type
todo_type!()
Type::Todo
}
fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) {
@@ -2593,7 +2552,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let target_ty = if is_async {
// TODO: async iterables/iterators! -- Alex
todo_type!("async iterables/iterators")
Type::Todo
} else {
iterable_ty
.iterate(self.db)
@@ -2683,7 +2642,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
// TODO function type
todo_type!()
Type::Todo
}
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
@@ -2714,7 +2673,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
// TODO
todo_type!()
Type::Todo
}
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
@@ -2723,7 +2682,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_optional_expression(value.as_deref());
// TODO awaitable type
todo_type!()
Type::Todo
}
fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
@@ -2735,7 +2694,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
// TODO get type from `ReturnType` of generator
todo_type!()
Type::Todo
}
fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> {
@@ -2744,7 +2703,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_expression(value);
// TODO awaitable type
todo_type!()
Type::Todo
}
/// Look up a name reference that isn't bound in the local scope.
@@ -3020,7 +2979,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::Unknown
}
}
_ => todo_type!(), // TODO other unary op types
_ => Type::Todo, // TODO other unary op types
}
}
@@ -3268,7 +3227,7 @@ impl<'db> TypeInferenceBuilder<'db> {
(left, Type::BooleanLiteral(bool_value), op) => {
self.infer_binary_expression_type(left, Type::IntLiteral(i64::from(bool_value)), op)
}
_ => Some(todo_type!()), // TODO
_ => Some(Type::Todo), // TODO
}
}
@@ -3706,7 +3665,7 @@ impl<'db> TypeInferenceBuilder<'db> {
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
match eq_result {
todo @ Type::Todo(_) => return Ok(todo),
Type::Todo => return Ok(Type::Todo),
ty => match ty.bool(self.db) {
Truthiness::AlwaysTrue => eq_count += 1,
Truthiness::AlwaysFalse => not_eq_count += 1,
@@ -3731,7 +3690,7 @@ impl<'db> TypeInferenceBuilder<'db> {
);
Ok(match eq_result {
todo @ Type::Todo(_) => todo,
Type::Todo => Type::Todo,
ty => match ty.bool(self.db) {
Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()),
_ => KnownClass::Bool.to_instance(self.db),
@@ -3786,7 +3745,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: handle more types
_ => match op {
ast::CmpOp::Is | ast::CmpOp::IsNot => Ok(KnownClass::Bool.to_instance(self.db)),
_ => Ok(todo_type!()),
_ => Ok(Type::Todo),
},
}
}
@@ -3814,7 +3773,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match pairwise_eq_result {
// If propagation is required, return the result as is
todo @ Type::Todo(_) => return Ok(todo),
Type::Todo => return Ok(Type::Todo),
ty => match ty.bool(self.db) {
// - AlwaysTrue : Continue to the next pair for lexicographic comparison
Truthiness::AlwaysTrue => continue,
@@ -4215,6 +4174,24 @@ impl<'db> TypeInferenceBuilder<'db> {
// Annotation expressions also get special handling for `*args` and `**kwargs`.
ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
ast::Expr::BytesLiteral(bytes) => {
self.diagnostics.add(
bytes.into(),
"annotation-byte-string",
format_args!("Type expressions cannot use bytes literal"),
);
Type::Unknown
}
ast::Expr::FString(fstring) => {
self.diagnostics.add(
fstring.into(),
"annotation-f-string",
format_args!("Type expressions cannot use f-strings"),
);
Type::Unknown
}
// All other annotation expressions are (possibly) valid type expressions, so handle
// them there instead.
type_expr => self.infer_type_expression_no_store(type_expr),
@@ -4225,72 +4202,6 @@ impl<'db> TypeInferenceBuilder<'db> {
annotation_ty
}
/// Walk child expressions of the given AST node and store the given type for each of them.
/// Does not store a type for the root expression. Resets to normal type inference for all
/// expressions with a nested scope (lambda, named expression, comprehensions, generators).
fn store_type_for_sub_expressions_of(&mut self, expression: &ast::Expr, ty: Type<'db>) {
struct StoreTypeVisitor<'a, 'db> {
builder: &'a mut TypeInferenceBuilder<'db>,
ty: Type<'db>,
is_root_node: bool,
}
impl<'a, 'db> StoreTypeVisitor<'a, 'db> {
fn new(builder: &'a mut TypeInferenceBuilder<'db>, ty: Type<'db>) -> Self {
Self {
builder,
ty,
is_root_node: true,
}
}
fn store(&mut self, expr: &ast::Expr) {
if self.is_root_node {
self.is_root_node = false;
} else {
self.builder.store_expression_type(expr, self.ty);
}
}
}
impl<'a, 'db, 'ast> Visitor<'ast> for StoreTypeVisitor<'a, 'db> {
fn visit_expr(&mut self, expr: &'ast Expr) {
match expr {
ast::Expr::Lambda(lambda) => {
self.builder.infer_lambda_expression(lambda);
self.store(expr);
}
ast::Expr::Named(named) => {
self.builder.infer_named_expression(named);
self.store(expr);
}
ast::Expr::ListComp(list_comp) => {
self.builder.infer_list_comprehension_expression(list_comp);
self.store(expr);
}
ast::Expr::SetComp(set_comp) => {
self.builder.infer_set_comprehension_expression(set_comp);
self.store(expr);
}
ast::Expr::DictComp(dict_comp) => {
self.builder.infer_dict_comprehension_expression(dict_comp);
self.store(expr);
}
ast::Expr::Generator(generator) => {
self.builder.infer_generator_expression(generator);
self.store(expr);
}
_ => {
self.store(expr);
visitor::walk_expr(self, expr);
}
}
}
}
StoreTypeVisitor::new(self, ty).visit_expr(expression);
}
/// Infer the type of a string annotation expression.
fn infer_string_annotation_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
match parse_string_annotation(self.db, self.file, string) {
@@ -4352,7 +4263,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_name_expression(name).in_type_expression(self.db)
}
ast::ExprContext::Invalid => Type::Unknown,
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(),
ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo,
},
ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx {
@@ -4360,7 +4271,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.infer_attribute_expression(attribute_expression)
.in_type_expression(self.db),
ast::ExprContext::Invalid => Type::Unknown,
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(),
ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo,
},
ast::Expr::NoneLiteral(_literal) => Type::none(self.db),
@@ -4370,7 +4281,14 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: an Ellipsis literal *on its own* does not have any meaning in annotation
// expressions, but is meaningful in the context of a number of special forms.
ast::Expr::EllipsisLiteral(_literal) => todo_type!(),
ast::Expr::EllipsisLiteral(_literal) => Type::Todo,
// Other literals do not have meaningful values in the annotation expression context.
// However, we will we want to handle these differently when working with special forms,
// since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
ast::Expr::BytesLiteral(_literal) => Type::Todo,
ast::Expr::NumberLiteral(_literal) => Type::Todo,
ast::Expr::BooleanLiteral(_literal) => Type::Todo,
ast::Expr::Subscript(subscript) => {
let ast::ExprSubscript {
@@ -4413,69 +4331,94 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO PEP 646
ast::Expr::Starred(starred) => {
self.infer_starred_expression(starred);
todo_type!()
Type::Todo
}
// Avoid inferring the types of invalid type expressions that have been parsed from a
// string annotation, as they are not present in the semantic index.
_ if self.deferred_state.in_string_annotation() => Type::Unknown,
// Forms which are invalid in the context of annotation expressions: we store a type of
// `Type::Unknown` for these expressions (and their sub-expressions) to avoid problems
// with invalid-syntax examples like `x: f"{x}"` or `x: lambda y: x`, for which we can
// not infer a meaningful type for the inner `x` expression. The top-level expression
// is also `Type::Unknown` in these cases.
ast::Expr::BoolOp(_)
| ast::Expr::Named(_)
| ast::Expr::UnaryOp(_)
| ast::Expr::Lambda(_)
| ast::Expr::If(_)
| ast::Expr::Dict(_)
| ast::Expr::Set(_)
| ast::Expr::ListComp(_)
| ast::Expr::SetComp(_)
| ast::Expr::DictComp(_)
| ast::Expr::Generator(_)
| ast::Expr::Await(_)
| ast::Expr::Yield(_)
| ast::Expr::YieldFrom(_)
| ast::Expr::Compare(_)
| ast::Expr::Call(_)
| ast::Expr::FString(_)
| ast::Expr::List(_)
| ast::Expr::Tuple(_)
| ast::Expr::Slice(_)
| ast::Expr::IpyEscapeCommand(_)
| ast::Expr::BytesLiteral(_)
| ast::Expr::NumberLiteral(_)
| ast::Expr::BooleanLiteral(_) => {
match expression {
ast::Expr::BytesLiteral(bytes) => {
self.diagnostics.add(
bytes.into(),
"annotation-byte-string",
format_args!("Type expressions cannot use bytes literal"),
);
}
ast::Expr::FString(fstring) => {
self.diagnostics.add(
fstring.into(),
"annotation-f-string",
format_args!("Type expressions cannot use f-strings"),
);
}
_ => {
self.diagnostics.add(
expression.into(),
"annotation-with-invalid-expression",
format_args!("Invalid expression in type expression"),
);
}
}
self.store_type_for_sub_expressions_of(expression, Type::Unknown);
// Forms which are invalid in the context of annotation expressions: we infer their
// nested expressions as normal expressions, but the type of the top-level expression is
// always `Type::Unknown` in these cases.
ast::Expr::BoolOp(bool_op) => {
self.infer_boolean_expression(bool_op);
Type::Unknown
}
ast::Expr::Named(named) => {
self.infer_named_expression(named);
Type::Unknown
}
ast::Expr::UnaryOp(unary) => {
self.infer_unary_expression(unary);
Type::Unknown
}
ast::Expr::Lambda(lambda_expression) => {
self.infer_lambda_expression(lambda_expression);
Type::Unknown
}
ast::Expr::If(if_expression) => {
self.infer_if_expression(if_expression);
Type::Unknown
}
ast::Expr::Dict(dict) => {
self.infer_dict_expression(dict);
Type::Unknown
}
ast::Expr::Set(set) => {
self.infer_set_expression(set);
Type::Unknown
}
ast::Expr::ListComp(listcomp) => {
self.infer_list_comprehension_expression(listcomp);
Type::Unknown
}
ast::Expr::SetComp(setcomp) => {
self.infer_set_comprehension_expression(setcomp);
Type::Unknown
}
ast::Expr::DictComp(dictcomp) => {
self.infer_dict_comprehension_expression(dictcomp);
Type::Unknown
}
ast::Expr::Generator(generator) => {
self.infer_generator_expression(generator);
Type::Unknown
}
ast::Expr::Await(await_expression) => {
self.infer_await_expression(await_expression);
Type::Unknown
}
ast::Expr::Yield(yield_expression) => {
self.infer_yield_expression(yield_expression);
Type::Unknown
}
ast::Expr::YieldFrom(yield_from) => {
self.infer_yield_from_expression(yield_from);
Type::Unknown
}
ast::Expr::Compare(compare) => {
self.infer_compare_expression(compare);
Type::Unknown
}
ast::Expr::Call(call_expr) => {
self.infer_call_expression(call_expr);
Type::Unknown
}
ast::Expr::FString(fstring) => {
self.infer_fstring_expression(fstring);
Type::Unknown
}
ast::Expr::List(list) => {
self.infer_list_expression(list);
Type::Unknown
}
ast::Expr::Tuple(tuple) => {
self.infer_tuple_expression(tuple);
Type::Unknown
}
ast::Expr::Slice(slice) => {
self.infer_slice_expression(slice);
Type::Unknown
}
ast::Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),
}
}
@@ -4533,7 +4476,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
let ty = if return_todo {
todo_type!("full tuple[...] support")
Type::Todo
} else {
Type::tuple(self.db, &element_types)
};
@@ -4548,7 +4491,7 @@ impl<'db> TypeInferenceBuilder<'db> {
single_element => {
let single_element_ty = self.infer_type_expression(single_element);
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) {
todo_type!()
Type::Todo
} else {
Type::tuple(self.db, &[single_element_ty])
}
@@ -4564,13 +4507,13 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
Type::subclass_of(class)
} else {
todo_type!()
Type::Todo
}
}
// TODO: attributes, unions, subscripts, etc.
_ => {
self.infer_type_expression(slice);
todo_type!()
Type::Todo
}
}
}
@@ -4589,21 +4532,20 @@ impl<'db> TypeInferenceBuilder<'db> {
match value_ty {
Type::KnownInstance(known_instance) => {
self.infer_parameterized_known_instance_type_expression(subscript, known_instance)
self.infer_parameterized_known_instance_type_expression(known_instance, slice)
}
_ => {
self.infer_type_expression(slice);
todo_type!("generics")
Type::Todo // TODO: generics
}
}
}
fn infer_parameterized_known_instance_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
known_instance: KnownInstanceType,
parameters: &ast::Expr,
) -> Type<'db> {
let parameters = &*subscript.slice;
match known_instance {
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
Ok(ty) => ty,
@@ -4625,36 +4567,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let param_type = self.infer_type_expression(parameters);
UnionType::from_elements(self.db, [param_type, Type::none(self.db)])
}
KnownInstanceType::Union => match parameters {
ast::Expr::Tuple(t) => {
let union_ty = UnionType::from_elements(
self.db,
t.iter().map(|elt| self.infer_type_expression(elt)),
);
self.store_expression_type(parameters, union_ty);
union_ty
}
_ => self.infer_type_expression(parameters),
},
KnownInstanceType::TypeVar(_) => {
self.infer_type_expression(parameters);
todo_type!()
}
KnownInstanceType::TypeAliasType(_) => {
self.infer_type_expression(parameters);
todo_type!("generic type alias")
}
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
self.diagnostics.add(
subscript.into(),
"invalid-type-parameter",
format_args!(
"Type `{}` expected no type parameter",
known_instance.repr(self.db)
),
);
Type::Unknown
}
KnownInstanceType::TypeVar(_) => Type::Todo,
}
}
@@ -5022,8 +4935,8 @@ fn perform_membership_test_comparison<'db>(
compare_result_opt
.map(|ty| {
if matches!(ty, Type::Todo(_)) {
return ty;
if matches!(ty, Type::Todo) {
return Type::Todo;
}
match op {
@@ -5994,17 +5907,7 @@ mod tests {
// We currently return `Todo` for all async comprehensions,
// including comprehensions that have invalid syntax
assert_scope_ty(
&db,
"src/a.py",
&["foo", "<listcomp>"],
"x",
if cfg!(debug_assertions) {
"@Todo(async iterables/iterators)"
} else {
"@Todo"
},
);
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
Ok(())
}
@@ -6028,17 +5931,7 @@ mod tests {
)?;
// TODO async iterables/iterators! --Alex
assert_scope_ty(
&db,
"src/a.py",
&["foo", "<listcomp>"],
"x",
if cfg!(debug_assertions) {
"@Todo(async iterables/iterators)"
} else {
"@Todo"
},
);
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
Ok(())
}
@@ -6072,72 +5965,6 @@ mod tests {
);
}
#[test]
fn pep695_type_params() {
let mut db = setup_db();
db.write_dedented(
"src/a.py",
"
def f[T, U: A, V: (A, B), W = A, X: A = A1, Y: (int,)]():
pass
class A: ...
class B: ...
class A1(A): ...
",
)
.unwrap();
let check_typevar = |var: &'static str,
upper_bound: Option<&'static str>,
constraints: Option<&[&'static str]>,
default: Option<&'static str>| {
let var_ty = get_symbol(&db, "src/a.py", &["f"], var).expect_type();
assert_eq!(var_ty.display(&db).to_string(), var);
let expected_name_ty = format!(r#"Literal["{var}"]"#);
let name_ty = var_ty.member(&db, "__name__").expect_type();
assert_eq!(name_ty.display(&db).to_string(), expected_name_ty);
let KnownInstanceType::TypeVar(typevar) = var_ty.expect_known_instance() else {
panic!("expected TypeVar");
};
assert_eq!(
typevar
.upper_bound(&db)
.map(|ty| ty.display(&db).to_string()),
upper_bound.map(std::borrow::ToOwned::to_owned)
);
assert_eq!(
typevar.constraints(&db).map(|tys| tys
.iter()
.map(|ty| ty.display(&db).to_string())
.collect::<Vec<_>>()),
constraints.map(|strings| strings
.iter()
.map(std::string::ToString::to_string)
.collect::<Vec<_>>())
);
assert_eq!(
typevar
.default_ty(&db)
.map(|ty| ty.display(&db).to_string()),
default.map(std::borrow::ToOwned::to_owned)
);
};
check_typevar("T", None, None, None);
check_typevar("U", Some("A"), None, None);
check_typevar("V", None, Some(&["A", "B"]), None);
check_typevar("W", None, None, Some("A"));
check_typevar("X", Some("A"), None, Some("A1"));
// a typevar with less than two constraints is treated as unconstrained
check_typevar("Y", None, None, None);
}
// Incremental inference tests
fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {

View File

@@ -5,7 +5,7 @@ use itertools::Either;
use rustc_hash::FxHashSet;
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
use crate::{types::todo_type, Db};
use crate::Db;
/// The inferred method resolution order of a given class.
///
@@ -354,7 +354,7 @@ impl<'db> ClassBase<'db> {
match ty {
Type::Any => Some(Self::Any),
Type::Unknown => Some(Self::Unknown),
Type::Todo(_) => Some(Self::Todo),
Type::Todo => Some(Self::Todo),
Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)),
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
Type::Intersection(_) => None, // TODO -- probably incorrect?
@@ -372,11 +372,7 @@ impl<'db> ClassBase<'db> {
| Type::SubclassOf(_) => None,
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::Literal
| KnownInstanceType::Union
| KnownInstanceType::NoReturn
| KnownInstanceType::Never
| KnownInstanceType::Optional => None,
},
}
@@ -409,7 +405,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
fn from(value: ClassBase<'db>) -> Self {
match value {
ClassBase::Any => Type::Any,
ClassBase::Todo => todo_type!(),
ClassBase::Todo => Type::Todo,
ClassBase::Unknown => Type::Unknown,
ClassBase::Class(class) => Type::class_literal(class),
}

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)]
use super::{definition_expression_ty, Type};
use crate::semantic_index::definition::Definition;
use crate::Db;
use crate::{semantic_index::definition::Definition, types::todo_type};
use ruff_python_ast::{self as ast, name::Name};
/// A typed callable signature.
@@ -18,7 +18,7 @@ impl<'db> Signature<'db> {
pub(crate) fn todo() -> Self {
Self {
parameters: Parameters::todo(),
return_ty: todo_type!("return type"),
return_ty: Type::Todo,
}
}
@@ -33,7 +33,8 @@ impl<'db> Signature<'db> {
.as_ref()
.map(|returns| {
if function_node.is_async {
todo_type!("generic types.CoroutineType")
// TODO: generic `types.CoroutineType`!
Type::Todo
} else {
definition_expression_ty(db, definition, returns.as_ref())
}
@@ -80,11 +81,11 @@ impl<'db> Parameters<'db> {
Self {
variadic: Some(Parameter {
name: Some(Name::new_static("args")),
annotated_ty: todo_type!(),
annotated_ty: Type::Todo,
}),
keywords: Some(Parameter {
name: Some(Name::new_static("kwargs")),
annotated_ty: todo_type!(),
annotated_ty: Type::Todo,
}),
..Default::default()
}

View File

@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
use crate::semantic_index::symbol::ScopeId;
use crate::types::{todo_type, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::types::{Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::Db;
/// Unpacks the value expression type to their respective targets.
@@ -59,7 +59,7 @@ impl<'db> Unpacker<'db> {
// 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"));
element_types.push(Type::Todo);
element_types.extend_from_slice(
// SAFETY: Safe because of the length check above.
@@ -72,7 +72,7 @@ impl<'db> Unpacker<'db> {
// index.
element_types.resize(elts.len() - 1, Type::Unknown);
// TODO: This should be `list[Unknown]`
element_types.insert(starred_index, todo_type!("starred unpacking"));
element_types.insert(starred_index, Type::Todo);
Cow::Owned(element_types)
}
} else {

View File

@@ -180,16 +180,6 @@ where
}
}
/// Discard `@Todo`-type metadata from expected types, which is not available
/// when running in release mode.
#[cfg(not(debug_assertions))]
fn discard_todo_metadata(ty: &str) -> std::borrow::Cow<'_, str> {
static TODO_METADATA_REGEX: std::sync::LazyLock<regex::Regex> =
std::sync::LazyLock::new(|| regex::Regex::new(r"@Todo\([^)]*\)").unwrap());
TODO_METADATA_REGEX.replace_all(ty, "@Todo")
}
struct Matcher {
line_index: LineIndex,
source: SourceText,
@@ -286,9 +276,6 @@ impl Matcher {
}
}
Assertion::Revealed(expected_type) => {
#[cfg(not(debug_assertions))]
let expected_type = discard_todo_metadata(&expected_type);
let mut matched_revealed_type = None;
let mut matched_undefined_reveal = None;
let expected_reveal_type_message = format!("Revealed type is `{expected_type}`");

View File

@@ -1,6 +0,0 @@
while True:
class A:
x: int
break

View File

@@ -1,6 +0,0 @@
while True:
def b():
x: int
break

View File

@@ -1,6 +0,0 @@
for _ in range(1):
class A:
x: int
break

View File

@@ -1,6 +0,0 @@
for _ in range(1):
def b():
x: int
break

View File

@@ -1 +0,0 @@
../../../../ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py

View File

@@ -1 +0,0 @@
x: f"Literal[{1 + 2}]" = 3

View File

@@ -1,3 +0,0 @@
from typing import Union
x: Union[int, str] = 1

View File

@@ -264,12 +264,26 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
}
/// Whether or not the .py/.pyi version of this file is expected to fail
#[rustfmt::skip]
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
// related to circular references in class definitions
// Probably related to missing support for type aliases / type params:
("crates/ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py", true, true),
("crates/ruff_python_parser/resources/inline/err/type_param_param_spec_invalid_default_expr.py", true, true),
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_invalid_default_expr.py", true, true),
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_missing_default.py", true, true),
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_invalid_default_expr.py", true, true),
("crates/ruff_python_parser/resources/inline/ok/type_param_param_spec.py", true, true),
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true),
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true),
("crates/ruff_python_parser/resources/valid/statement/type.py", true, true),
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false),
// Fails for unknown reasons:
("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true),
("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false),
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
// related to circular references in type aliases (salsa cycle panic):
("crates/ruff_python_parser/resources/inline/err/type_alias_invalid_value_expr.py", true, true),
];

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.8.0"
version = "0.7.4"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1966,75 +1966,3 @@ fn nested_implicit_namespace_package() -> Result<()> {
Ok(())
}
#[test]
fn flake8_import_convention_invalid_aliases_config_alias_name() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.flake8-import-conventions.aliases]
"module.name" = "invalid.alias"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
, @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to parse [TMP]/ruff.toml
Cause: TOML parse error at line 2, column 2
|
2 | [lint.flake8-import-conventions.aliases]
| ^^^^
invalid value: string "invalid.alias", expected a Python identifier
"###);});
Ok(())
}
#[test]
fn flake8_import_convention_invalid_aliases_config_module_name() -> Result<()> {
let tempdir = TempDir::new()?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
r#"
[lint.flake8-import-conventions.aliases]
"module..invalid" = "alias"
"#,
)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
}, {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.arg("--config")
.arg(&ruff_toml)
, @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: Failed to parse [TMP]/ruff.toml
Cause: TOML parse error at line 2, column 2
|
2 | [lint.flake8-import-conventions.aliases]
| ^^^^
invalid value: string "module..invalid", expected a sequence of Python identifiers delimited by periods
"###);});
Ok(())
}

View File

@@ -27,17 +27,13 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
// We don't support `*` imports yet:
"error[unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
// We don't support terminal statements in control flow yet:
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:57:71 Invalid expression in type expression",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:69:66 Invalid expression in type expression",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
"error[conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:145:27 Invalid expression in type expression",
"error[annotation-with-invalid-expression] /src/tomllib/_parser.py:196:25 Invalid expression in type expression",
"error[conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",

View File

@@ -1,11 +1,10 @@
use super::{write, Arguments, FormatElement};
use crate::format_element::Interned;
use crate::prelude::{LineMode, Tag};
use crate::prelude::LineMode;
use crate::{FormatResult, FormatState};
use rustc_hash::FxHashMap;
use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::num::NonZeroUsize;
use std::ops::{Deref, DerefMut};
/// A trait for writing or formatting into [`FormatElement`]-accepting buffers or streams.
@@ -295,11 +294,10 @@ where
}
}
/// A Buffer that removes any soft line breaks or [`if_group_breaks`](crate::builders::if_group_breaks) elements.
/// A Buffer that removes any soft line breaks.
///
/// - Removes [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::Soft).
/// - Replaces [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::SoftOrSpace) with a [`Space`](FormatElement::Space)
/// - Removes [`if_group_breaks`](crate::builders::if_group_breaks) elements.
///
/// # Examples
///
@@ -352,8 +350,6 @@ pub struct RemoveSoftLinesBuffer<'a, Context> {
/// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements
/// that are now unused. But there's little harm in that and the cache is cleaned when dropping the buffer.
interned_cache: FxHashMap<Interned, Interned>,
state: RemoveSoftLineBreaksState,
}
impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
@@ -361,7 +357,6 @@ impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
pub fn new(inner: &'a mut dyn Buffer<Context = Context>) -> Self {
Self {
inner,
state: RemoveSoftLineBreaksState::default(),
interned_cache: FxHashMap::default(),
}
}
@@ -380,8 +375,6 @@ fn clean_interned(
if let Some(cleaned) = interned_cache.get(interned) {
cleaned.clone()
} else {
let mut state = RemoveSoftLineBreaksState::default();
// Find the first soft line break element or interned element that must be changed
let result = interned
.iter()
@@ -389,9 +382,8 @@ fn clean_interned(
.find_map(|(index, element)| match element {
FormatElement::Line(LineMode::Soft | LineMode::SoftOrSpace) => {
let mut cleaned = Vec::new();
let (before, after) = interned.split_at(index);
cleaned.extend_from_slice(before);
Some((cleaned, &after[1..]))
cleaned.extend_from_slice(&interned[..index]);
Some((cleaned, &interned[index..]))
}
FormatElement::Interned(inner) => {
let cleaned_inner = clean_interned(inner, interned_cache);
@@ -406,33 +398,19 @@ fn clean_interned(
}
}
element => {
if state.should_drop(element) {
let mut cleaned = Vec::new();
let (before, after) = interned.split_at(index);
cleaned.extend_from_slice(before);
Some((cleaned, &after[1..]))
} else {
None
}
}
_ => None,
});
let result = match result {
// Copy the whole interned buffer so that becomes possible to change the necessary elements.
Some((mut cleaned, rest)) => {
for element in rest {
if state.should_drop(element) {
continue;
}
let element = match element {
FormatElement::Line(LineMode::Soft) => continue,
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
FormatElement::Interned(interned) => {
FormatElement::Interned(clean_interned(interned, interned_cache))
}
element => element.clone(),
};
cleaned.push(element);
@@ -453,17 +431,12 @@ impl<Context> Buffer for RemoveSoftLinesBuffer<'_, Context> {
type Context = Context;
fn write_element(&mut self, element: FormatElement) {
if self.state.should_drop(&element) {
return;
}
let element = match element {
FormatElement::Line(LineMode::Soft) => return,
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
FormatElement::Interned(interned) => {
FormatElement::Interned(self.clean_interned(&interned))
}
element => element,
};
@@ -483,77 +456,14 @@ impl<Context> Buffer for RemoveSoftLinesBuffer<'_, Context> {
}
fn snapshot(&self) -> BufferSnapshot {
BufferSnapshot::Any(Box::new(RemoveSoftLinebreaksSnapshot {
inner: self.inner.snapshot(),
state: self.state,
}))
self.inner.snapshot()
}
fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
let RemoveSoftLinebreaksSnapshot { inner, state } = snapshot.unwrap_any();
self.inner.restore_snapshot(inner);
self.state = state;
self.inner.restore_snapshot(snapshot);
}
}
#[derive(Copy, Clone, Debug, Default)]
enum RemoveSoftLineBreaksState {
#[default]
Default,
InIfGroupBreaks {
conditional_content_level: NonZeroUsize,
},
}
impl RemoveSoftLineBreaksState {
fn should_drop(&mut self, element: &FormatElement) -> bool {
match self {
Self::Default => {
// Entered the start of an `if_group_breaks`
if let FormatElement::Tag(Tag::StartConditionalContent(condition)) = element {
if condition.mode.is_expanded() {
*self = Self::InIfGroupBreaks {
conditional_content_level: NonZeroUsize::new(1).unwrap(),
};
return true;
}
}
false
}
Self::InIfGroupBreaks {
conditional_content_level,
} => {
match element {
// A nested `if_group_breaks` or `if_group_fits`
FormatElement::Tag(Tag::StartConditionalContent(_)) => {
*conditional_content_level = conditional_content_level.saturating_add(1);
}
// The end of an `if_group_breaks` or `if_group_fits`.
FormatElement::Tag(Tag::EndConditionalContent) => {
if let Some(level) = NonZeroUsize::new(conditional_content_level.get() - 1)
{
*conditional_content_level = level;
} else {
// Found the end tag of the initial `if_group_breaks`. Skip this element but retain
// the elements coming after
*self = RemoveSoftLineBreaksState::Default;
}
}
_ => {}
}
true
}
}
}
}
struct RemoveSoftLinebreaksSnapshot {
inner: BufferSnapshot,
state: RemoveSoftLineBreaksState,
}
pub trait BufferExtensions: Buffer + Sized {
/// Returns a new buffer that calls the passed inspector for every element that gets written to the output
#[must_use]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.8.0"
version = "0.7.4"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,15 +0,0 @@
from airflow import DAG, dag
DAG(dag_id="class_default_schedule")
DAG(dag_id="class_schedule", schedule="@hourly")
@dag()
def decorator_default_schedule():
pass
@dag(schedule="0 * * * *")
def decorator_schedule():
pass

View File

@@ -14,8 +14,6 @@ ContextVar("cv", default=frozenset())
ContextVar("cv", default=MappingProxyType({}))
ContextVar("cv", default=re.compile("foo"))
ContextVar("cv", default=float(1))
ContextVar("cv", default=frozenset[str]())
ContextVar[frozenset[str]]("cv", default=frozenset[str]())
# Bad
ContextVar("cv", default=[])
@@ -27,8 +25,6 @@ ContextVar("cv", default=[char for char in "foo"])
ContextVar("cv", default={char for char in "foo"})
ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
ContextVar("cv", default=collections.deque())
ContextVar("cv", default=set[str]())
ContextVar[set[str]]("cv", default=set[str]())
def bar() -> list[int]:
return [1, 2, 3]

View File

@@ -84,27 +84,3 @@ field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union me
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
# Should emit in cases with nested `typing.Union`
field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int`
# Should emit in cases with nested `typing.Union`
field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int`
# Should emit in cases with mixed `typing.Union` and `|`
field28: typing.Union[int | int] # Error
# Should emit twice in cases with multiple nested `typing.Union`
field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error
# Should emit once in cases with multiple nested `typing.Union`
field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error
# Should emit once, and fix to `typing.Union[float, int]`
field31: typing.Union[float, typing.Union[int | int]] # Error
# Should emit once, and fix to `typing.Union[float, int]`
field32: typing.Union[float, typing.Union[int | int | int]] # Error
# Test case for mixed union type fix
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error

View File

@@ -84,27 +84,3 @@ field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union me
# duplicates of the outer `int`), but not three times (which would indicate that
# we incorrectly re-checked the nested union).
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
# Should emit in cases with nested `typing.Union`
field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int`
# Should emit in cases with nested `typing.Union`
field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int`
# Should emit in cases with mixed `typing.Union` and `|`
field28: typing.Union[int | int] # Error
# Should emit twice in cases with multiple nested `typing.Union`
field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error
# Should emit once in cases with multiple nested `typing.Union`
field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error
# Should emit once, and fix to `typing.Union[float, int]`
field31: typing.Union[float, typing.Union[int | int]] # Error
# Should emit once, and fix to `typing.Union[float, int]`
field32: typing.Union[float, typing.Union[int | int | int]] # Error
# Test case for mixed union type fix
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error

View File

@@ -39,30 +39,14 @@ async def f4(**kwargs: int | int | float) -> None:
...
def f5(arg1: int, *args: Union[int, int, float]) -> None:
...
def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None:
...
def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None:
...
def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None:
...
def f9(
def f5(
arg: Union[ # comment
float, # another
complex, int]
) -> None:
...
def f10(
def f6(
arg: (
int | # comment
float | # another

View File

@@ -46,18 +46,6 @@ def f6(
)
) -> None: ... # PYI041
def f5(arg1: int, *args: Union[int, int, float]) -> None: ... # PYI041
def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None: ... # PYI041
def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None: ... # PYI041
def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None: ... # PYI041
class Foo:
def good(self, arg: int) -> None: ...

View File

@@ -5,9 +5,6 @@ A: str | Literal["foo"]
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...

View File

@@ -5,9 +5,6 @@ A: str | Literal["foo"]
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...

View File

@@ -1,14 +1,11 @@
import builtins
from typing import Union
s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
t: type[int] | type[str] | type[float]
u: builtins.type[int] | type[str] | builtins.type[complex]
v: Union[type[float], type[complex]]
w: Union[type[float, int], type[complex]]
x: Union[Union[type[float, int], type[complex]]]
y: Union[Union[Union[type[float, int], type[complex]]]]
z: Union[type[complex], Union[Union[type[float, int]]]]
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None:

View File

@@ -1,14 +1,11 @@
import builtins
from typing import Union
s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
t: type[int] | type[str] | type[float]
u: builtins.type[int] | type[str] | builtins.type[complex]
v: Union[type[float], type[complex]]
w: Union[type[float, int], type[complex]]
x: Union[Union[type[float, int], type[complex]]]
y: Union[Union[Union[type[float, int], type[complex]]]]
z: Union[type[complex], Union[Union[type[float, int]]]]
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
x: type[int] | type[str] | type[float]
y: builtins.type[int] | type[str] | builtins.type[complex]
z: Union[type[float], type[complex]]
z: Union[type[float, int], type[complex]]
def func(arg: type[int] | str | type[float]) -> None: ...

View File

@@ -1,4 +1,4 @@
from typing import Literal, Union
from typing import Literal
def func1(arg1: Literal[None]):
@@ -17,7 +17,7 @@ def func4(arg1: Literal[int, None, float]):
...
def func5(arg1: Literal[None, None]):
def func5(arg1: Literal[None, None]):
...
@@ -25,21 +25,13 @@ def func6(arg1: Literal[
"hello",
None # Comment 1
, "world"
]):
]):
...
def func7(arg1: Literal[
None # Comment 1
]):
...
def func8(arg1: Literal[None] | None):
...
def func9(arg1: Union[Literal[None], None]):
]):
...
@@ -66,16 +58,3 @@ Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replac
# and there are no None members in the Literal[] slice,
# only emit Y062:
Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True"
# Regression tests for https://github.com/astral-sh/ruff/issues/14567
x: Literal[None] | None
y: None | Literal[None]
z: Union[Literal[None], None]
a: int | Literal[None] | None
b: None | Literal[None] | None
c: (None | Literal[None]) | None
d: None | (Literal[None] | None)
e: None | ((None | Literal[None]) | None) | None
f: Literal[None] | Literal[None]

View File

@@ -1,4 +1,4 @@
from typing import Literal, Union
from typing import Literal
def func1(arg1: Literal[None]): ...
@@ -28,12 +28,6 @@ def func7(arg1: Literal[
]): ...
def func8(arg1: Literal[None] | None):...
def func9(arg1: Union[Literal[None], None]): ...
# OK
def good_func(arg1: Literal[int] | None): ...
@@ -41,16 +35,3 @@ def good_func(arg1: Literal[int] | None): ...
# From flake8-pyi
Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None"
Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None"
# Regression tests for https://github.com/astral-sh/ruff/issues/14567
x: Literal[None] | None
y: None | Literal[None]
z: Union[Literal[None], None]
a: int | Literal[None] | None
b: None | Literal[None] | None
c: (None | Literal[None]) | None
d: None | (Literal[None] | None)
e: None | ((None | Literal[None]) | None) | None
f: Literal[None] | Literal[None]

View File

@@ -25,9 +25,3 @@ Literal[
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
n: Literal["No", "duplicates", "here", 1, "1"]
# nested literals, all equivalent to `Literal[1]`
Literal[Literal[1]] # no duplicate
Literal[Literal[Literal[1], Literal[1]]] # once
Literal[Literal[1], Literal[Literal[Literal[1]]]] # once

View File

@@ -25,9 +25,3 @@ Literal[
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
n: Literal["No", "duplicates", "here", 1, "1"]
# nested literals, all equivalent to `Literal[1]`
Literal[Literal[1]] # no duplicate
Literal[Literal[Literal[1], Literal[1]]] # once
Literal[Literal[1], Literal[Literal[Literal[1]]]] # once

View File

@@ -69,15 +69,3 @@ def test_implicit_str_concat_with_multi_parens(param1, param2, param3):
@pytest.mark.parametrize(("param1,param2"), [(1, 2), (3, 4)])
def test_csv_with_parens(param1, param2):
...
parametrize = pytest.mark.parametrize(("param1,param2"), [(1, 2), (3, 4)])
@parametrize
def test_not_decorator(param1, param2):
...
@pytest.mark.parametrize(argnames=("param1,param2"), argvalues=[(1, 2), (3, 4)])
def test_keyword_arguments(param1, param2):
...

View File

@@ -1,62 +0,0 @@
def f():
from typing import cast
cast(int, 3.0) # TC006
def f():
from typing import cast
cast(list[tuple[bool | float | int | str]], 3.0) # TC006
def f():
from typing import Union, cast
cast(list[tuple[Union[bool, float, int, str]]], 3.0) # TC006
def f():
from typing import cast
cast("int", 3.0) # OK
def f():
from typing import cast
cast("list[tuple[bool | float | int | str]]", 3.0) # OK
def f():
from typing import Union, cast
cast("list[tuple[Union[bool, float, int, str]]]", 3.0) # OK
def f():
from typing import cast as typecast
typecast(int, 3.0) # TC006
def f():
import typing
typing.cast(int, 3.0) # TC006
def f():
import typing as t
t.cast(t.Literal["3.0", '3'], 3.0) # TC006
def f():
from typing import cast
cast(
int # TC006 (unsafe, because it will get rid of this comment)
| None,
3.0
)

View File

@@ -1,21 +0,0 @@
"""Regression test for #13824.
Don't report an error when the function being annotated has the
`@no_type_check` decorator.
However, we still want to ignore this annotation on classes. See
https://github.com/python/typing/pull/1615/files and the discussion on #14615.
"""
from typing import no_type_check
@no_type_check
def f(arg: "this isn't python") -> "this isn't python either":
x: "this also isn't python" = 0
@no_type_check
class C:
def f(arg: "this isn't python") -> "this isn't python either":
x: "this also isn't python" = 1

View File

@@ -1,21 +0,0 @@
"""Regression test for #13824.
Don't report an error when the function being annotated has the
`@no_type_check` decorator.
However, we still want to ignore this annotation on classes. See
https://github.com/python/typing/pull/1615/files and the discussion on #14615.
"""
import typing
@typing.no_type_check
def f(arg: "A") -> "R":
x: "A" = 1
@typing.no_type_check
class C:
def f(self, arg: "B") -> "S":
x: "B" = 1

View File

@@ -12,4 +12,3 @@ os.getenv("AA", "GOOD %s" % "BAR")
os.getenv("B", Z)
os.getenv("AA", "GOOD" if Z else "BAR")
os.getenv("AA", 1 if Z else "BAR") # [invalid-envvar-default]
os.environ.get("TEST", 12) # [invalid-envvar-default]

View File

@@ -1,234 +0,0 @@
if len('TEST'): # [PLC1802]
pass
if not len('TEST'): # [PLC1802]
pass
z = []
if z and len(['T', 'E', 'S', 'T']): # [PLC1802]
pass
if True or len('TEST'): # [PLC1802]
pass
if len('TEST') == 0: # Should be fine
pass
if len('TEST') < 1: # Should be fine
pass
if len('TEST') <= 0: # Should be fine
pass
if 1 > len('TEST'): # Should be fine
pass
if 0 >= len('TEST'): # Should be fine
pass
if z and len('TEST') == 0: # Should be fine
pass
if 0 == len('TEST') < 10: # Should be fine
pass
# Should be fine
if 0 < 1 <= len('TEST') < 10: # [comparison-of-constants]
pass
if 10 > len('TEST') != 0: # Should be fine
pass
if 10 > len('TEST') > 1 > 0: # Should be fine
pass
if 0 <= len('TEST') < 100: # Should be fine
pass
if z or 10 > len('TEST') != 0: # Should be fine
pass
if z:
pass
elif len('TEST'): # [PLC1802]
pass
if z:
pass
elif not len('TEST'): # [PLC1802]
pass
while len('TEST'): # [PLC1802]
pass
while not len('TEST'): # [PLC1802]
pass
while z and len('TEST'): # [PLC1802]
pass
while not len('TEST') and z: # [PLC1802]
pass
assert len('TEST') > 0 # Should be fine
x = 1 if len('TEST') != 0 else 2 # Should be fine
f_o_o = len('TEST') or 42 # Should be fine
a = x and len(x) # Should be fine
def some_func():
return len('TEST') > 0 # Should be fine
def github_issue_1325():
l = [1, 2, 3]
length = len(l) if l else 0 # Should be fine
return length
def github_issue_1331(*args):
assert False, len(args) # Should be fine
def github_issue_1331_v2(*args):
assert len(args), args # [PLC1802]
def github_issue_1331_v3(*args):
assert len(args) or z, args # [PLC1802]
def github_issue_1331_v4(*args):
assert z and len(args), args # [PLC1802]
def github_issue_1331_v5(**args):
assert z and len(args), args # [PLC1802]
b = bool(len(z)) # [PLC1802]
c = bool(len('TEST') or 42) # [PLC1802]
def github_issue_1879():
class ClassWithBool(list):
def __bool__(self):
return True
class ClassWithoutBool(list):
pass
class ChildClassWithBool(ClassWithBool):
pass
class ChildClassWithoutBool(ClassWithoutBool):
pass
assert len(ClassWithBool())
assert len(ChildClassWithBool())
assert len(ClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
assert len(ChildClassWithoutBool()) # unintuitive?, in pylint: [PLC1802]
assert len(range(0)) # [PLC1802]
assert len([t + 1 for t in []]) # [PLC1802]
# assert len(u + 1 for u in []) generator has no len
assert len({"1":(v + 1) for v in {}}) # [PLC1802]
assert len(set((w + 1) for w in set())) # [PLC1802]
import numpy
numpy_array = numpy.array([0])
if len(numpy_array) > 0:
print('numpy_array')
if len(numpy_array):
print('numpy_array')
if numpy_array:
print('b')
import pandas as pd
pandas_df = pd.DataFrame()
if len(pandas_df):
print("this works, but pylint tells me not to use len() without comparison")
if len(pandas_df) > 0:
print("this works and pylint likes it, but it's not the solution intended by PEP-8")
if pandas_df:
print("this does not work (truth value of dataframe is ambiguous)")
def function_returning_list(r):
if r==1:
return [1]
return [2]
def function_returning_int(r):
if r==1:
return 1
return 2
def function_returning_generator(r):
for i in [r, 1, 2, 3]:
yield i
def function_returning_comprehension(r):
return [x+1 for x in [r, 1, 2, 3]]
def function_returning_function(r):
return function_returning_generator(r)
assert len(function_returning_list(z)) # [PLC1802] differs from pylint
assert len(function_returning_int(z))
# This should raise a PLC1802 once astroid can infer it
# See https://github.com/pylint-dev/pylint/pull/3821#issuecomment-743771514
assert len(function_returning_generator(z))
assert len(function_returning_comprehension(z))
assert len(function_returning_function(z))
def github_issue_4215():
# Test undefined variables
# https://github.com/pylint-dev/pylint/issues/4215
if len(undefined_var): # [undefined-variable]
pass
if len(undefined_var2[0]): # [undefined-variable]
pass
def f(cond:bool):
x = [1,2,3]
if cond:
x = [4,5,6]
if len(x): # this should be addressed
print(x)
def g(cond:bool):
x = [1,2,3]
if cond:
x = [4,5,6]
if len(x): # this should be addressed
print(x)
del x
def h(cond:bool):
x = [1,2,3]
x = 123
if len(x): # ok
print(x)
def outer():
x = [1,2,3]
def inner(x:int):
return x+1
if len(x): # [PLC1802]
print(x)
def redefined():
x = 123
x = [1, 2, 3]
if len(x): # this should be addressed
print(x)
global_seq = [1, 2, 3]
def i():
global global_seq
if len(global_seq): # ok
print(global_seq)
def j():
if False:
x = [1, 2, 3]
if len(x): # [PLC1802] should be fine
print(x)

View File

@@ -102,6 +102,3 @@ blah = lambda: {"a": 1}.__delitem__("a") # OK
blah = dict[{"a": 1}.__delitem__("a")] # OK
"abc".__contains__("a")
# https://github.com/astral-sh/ruff/issues/14597
assert "abc".__str__() == "abc"

View File

@@ -1,125 +0,0 @@
import attr
from attrs import define, field, frozen, mutable
foo = int
@define # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@define() # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@define(auto_attribs=None) # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@frozen # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@frozen() # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@frozen(auto_attribs=None) # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@mutable # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@mutable() # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@mutable(auto_attribs=None) # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s() # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=None) # auto_attribs = None => True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=False) # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=True) # auto_attribs = True
class C:
a: str = 0
b = field()
c: int = foo()
d = list()
@attr.s(auto_attribs=[1, 2, 3]) # auto_attribs = False
class C:
a: str = 0
b = field()
c: int = foo()
d = list()

View File

@@ -6,12 +6,3 @@ Never | int
NoReturn | int
Union[Union[Never, int], Union[NoReturn, int]]
Union[NoReturn, int, float]
# Regression tests for https://github.com/astral-sh/ruff/issues/14567
x: None | Never | None
y: (None | Never) | None
z: None | (Never | None)
a: int | Never | None
b: Never | Never | None

View File

@@ -1,5 +0,0 @@
fruits = ["apples", "plums", "pear"]
fruits.filter(lambda fruit: fruit.startwith("p"))
assert len(fruits), 2
assert True, "always true"

View File

@@ -11,8 +11,8 @@ use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
use crate::rules::{
airflow, flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear,
flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
flake8_simplify, flake8_tidy_imports, flake8_type_checking, flake8_use_pathlib, flynt, numpy,
@@ -494,9 +494,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::SuperWithoutBrackets) {
pylint::rules::super_without_brackets(checker, func);
}
if checker.enabled(Rule::LenTest) {
pylint::rules::len_test(checker, call);
}
if checker.enabled(Rule::BitCount) {
refurb::rules::bit_count(checker, call);
}
@@ -859,15 +856,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.settings.preview.is_enabled()
&& checker.any_enabled(&[
Rule::PytestParametrizeNamesWrongType,
Rule::PytestParametrizeValuesWrongType,
Rule::PytestDuplicateParametrizeTestCases,
])
{
flake8_pytest_style::rules::parametrize(checker, call);
}
if checker.enabled(Rule::PytestUnittestAssertion) {
if let Some(diagnostic) = flake8_pytest_style::rules::unittest_assertion(
checker, expr, func, args, keywords,
@@ -1073,9 +1061,6 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::UnrawRePattern) {
ruff::rules::unraw_re_pattern(checker, call);
}
if checker.enabled(Rule::AirflowDagNoScheduleArgument) {
airflow::rules::dag_no_schedule_argument(checker, expr);
}
}
Expr::Dict(dict) => {
if checker.any_enabled(&[

View File

@@ -309,20 +309,12 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
body,
);
}
// In preview mode, calls are analyzed. To avoid duplicate diagnostics,
// skip analyzing the decorators.
if !checker.settings.preview.is_enabled()
&& checker.any_enabled(&[
Rule::PytestParametrizeNamesWrongType,
Rule::PytestParametrizeValuesWrongType,
Rule::PytestDuplicateParametrizeTestCases,
])
{
for decorator in decorator_list {
if let Some(call) = decorator.expression.as_call_expr() {
flake8_pytest_style::rules::parametrize(checker, call);
}
}
if checker.any_enabled(&[
Rule::PytestParametrizeNamesWrongType,
Rule::PytestParametrizeValuesWrongType,
Rule::PytestDuplicateParametrizeTestCases,
]) {
flake8_pytest_style::rules::parametrize(checker, decorator_list);
}
if checker.any_enabled(&[
Rule::PytestIncorrectMarkParenthesesStyle,
@@ -1276,9 +1268,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::AssertWithPrintMessage) {
ruff::rules::assert_with_print_message(checker, assert_stmt);
}
if checker.enabled(Rule::InvalidAssertMessageLiteralArgument) {
ruff::rules::invalid_assert_message_literal_argument(checker, assert_stmt);
}
}
Stmt::With(
with_stmt @ ast::StmtWith {

View File

@@ -723,12 +723,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
// Visit the decorators and arguments, but avoid the body, which will be
// deferred.
for decorator in decorator_list {
if self
.semantic
.match_typing_expr(&decorator.expression, "no_type_check")
{
self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK;
}
self.visit_decorator(decorator);
}
@@ -1286,10 +1280,6 @@ impl<'a> Visitor<'a> for Checker<'a> {
let mut args = arguments.args.iter();
if let Some(arg) = args.next() {
self.visit_type_definition(arg);
if self.enabled(Rule::RuntimeCastValue) {
flake8_type_checking::rules::runtime_cast_value(self, arg);
}
}
for arg in args {
self.visit_expr(arg);
@@ -1857,9 +1847,6 @@ impl<'a> Checker<'a> {
/// Visit an [`Expr`], and treat it as a type definition.
fn visit_type_definition(&mut self, expr: &'a Expr) {
if self.semantic.in_no_type_check() {
return;
}
let snapshot = self.semantic.flags;
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION;
self.visit_expr(expr);

View File

@@ -192,7 +192,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0208") => (RuleGroup::Stable, rules::pylint::rules::IterationOverSet),
(Pylint, "C0414") => (RuleGroup::Stable, rules::pylint::rules::UselessImportAlias),
(Pylint, "C0415") => (RuleGroup::Preview, rules::pylint::rules::ImportOutsideTopLevel),
(Pylint, "C1802") => (RuleGroup::Preview, rules::pylint::rules::LenTest),
(Pylint, "C1901") => (RuleGroup::Preview, rules::pylint::rules::CompareToEmptyString),
(Pylint, "C2401") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiName),
(Pylint, "C2403") => (RuleGroup::Stable, rules::pylint::rules::NonAsciiImportName),
@@ -857,7 +856,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8TypeChecking, "003") => (RuleGroup::Stable, rules::flake8_type_checking::rules::TypingOnlyStandardLibraryImport),
(Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
(Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
(Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeCastValue),
(Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion),
// tryceratops
@@ -979,7 +977,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral),
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
@@ -1034,7 +1031,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
// airflow
(Airflow, "001") => (RuleGroup::Stable, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
(Airflow, "301") => (RuleGroup::Preview, rules::airflow::rules::AirflowDagNoScheduleArgument),
// perflint
(Perflint, "101") => (RuleGroup::Stable, rules::perflint::rules::UnnecessaryListCast),

View File

@@ -13,7 +13,6 @@ mod tests {
use crate::{assert_messages, settings};
#[test_case(Rule::AirflowVariableNameTaskIdMismatch, Path::new("AIR001.py"))]
#[test_case(Rule::AirflowDagNoScheduleArgument, Path::new("AIR301.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,84 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Expr;
use ruff_python_ast::{self as ast};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for a `DAG()` class or `@dag()` decorator without an explicit
/// `schedule` parameter.
///
/// ## Why is this bad?
/// The default `schedule` value on Airflow 2 is `timedelta(days=1)`, which is
/// almost never what a user is looking for. Airflow 3 changes this the default
/// to *None*, and would break existing DAGs using the implicit default.
///
/// If your DAG does not have an explicit `schedule` argument, Airflow 2
/// schedules a run for it every day (at the time determined by `start_date`).
/// Such a DAG will no longer be scheduled on Airflow 3 at all, without any
/// exceptions or other messages visible to the user.
///
/// ## Example
/// ```python
/// from airflow import DAG
///
///
/// # Using the implicit default schedule.
/// dag = DAG(dag_id="my_dag")
/// ```
///
/// Use instead:
/// ```python
/// from datetime import timedelta
///
/// from airflow import DAG
///
///
/// dag = DAG(dag_id="my_dag", schedule=timedelta(days=1))
/// ```
#[violation]
pub struct AirflowDagNoScheduleArgument;
impl Violation for AirflowDagNoScheduleArgument {
#[derive_message_formats]
fn message(&self) -> String {
"DAG should have an explicit `schedule` argument".to_string()
}
}
/// AIR301
pub(crate) fn dag_no_schedule_argument(checker: &mut Checker, expr: &Expr) {
if !checker.semantic().seen_module(Modules::AIRFLOW) {
return;
}
// Don't check non-call expressions.
let Expr::Call(ast::ExprCall {
func, arguments, ..
}) = expr
else {
return;
};
// We don't do anything unless this is a `DAG` (class) or `dag` (decorator
// function) from Airflow.
if !checker
.semantic()
.resolve_qualified_name(func)
.is_some_and(|qualname| matches!(qualname.segments(), ["airflow", .., "DAG" | "dag"]))
{
return;
}
// If there's a `schedule` keyword argument, we are good.
if arguments.find_keyword("schedule").is_some() {
return;
}
// Produce a diagnostic when the `schedule` keyword argument is not found.
let diagnostic = Diagnostic::new(AirflowDagNoScheduleArgument, expr.range());
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,5 +1,3 @@
pub(crate) use dag_schedule_argument::*;
pub(crate) use task_variable_name::*;
mod dag_schedule_argument;
mod task_variable_name;

View File

@@ -1,20 +0,0 @@
---
source: crates/ruff_linter/src/rules/airflow/mod.rs
---
AIR301.py:3:1: AIR301 DAG should have an explicit `schedule` argument
|
1 | from airflow import DAG, dag
2 |
3 | DAG(dag_id="class_default_schedule")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AIR301
4 |
5 | DAG(dag_id="class_schedule", schedule="@hourly")
|
AIR301.py:8:2: AIR301 DAG should have an explicit `schedule` argument
|
8 | @dag()
| ^^^^^ AIR301
9 | def decorator_default_schedule():
10 | pass
|

View File

@@ -311,8 +311,7 @@ pub(crate) fn shell_injection(checker: &mut Checker, call: &ast::ExprCall) {
}
// S603
Some(ShellKeyword {
truthiness:
Truthiness::False | Truthiness::Falsey | Truthiness::None | Truthiness::Unknown,
truthiness: Truthiness::False | Truthiness::Falsey | Truthiness::Unknown,
}) => {
if checker.enabled(Rule::SubprocessWithoutShellEqualsTrue) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -106,10 +106,6 @@ pub(crate) fn boolean_default_value_positional_argument(
decorator_list: &[Decorator],
parameters: &Parameters,
) {
// https://github.com/astral-sh/ruff/issues/14535
if checker.source_type.is_stub() {
return;
}
// Allow Boolean defaults in explicitly-allowed functions.
if is_allowed_func_def(name) {
return;

View File

@@ -115,10 +115,6 @@ pub(crate) fn boolean_type_hint_positional_argument(
decorator_list: &[Decorator],
parameters: &Parameters,
) {
// https://github.com/astral-sh/ruff/issues/14535
if checker.source_type.is_stub() {
return;
}
// Allow Boolean type hints in explicitly-allowed functions.
if is_allowed_func_def(name) {
return;

View File

@@ -11,14 +11,12 @@ use crate::checkers::ast::Checker;
use crate::registry::Rule;
/// ## What it does
/// Checks for abstract classes without abstract methods or properties.
/// Annotated but unassigned class variables are regarded as abstract.
/// Checks for abstract classes without abstract methods.
///
/// ## Why is this bad?
/// Abstract base classes are used to define interfaces. If an abstract base
/// class has no abstract methods or properties, you may have forgotten
/// to add an abstract method or property to the class,
/// or omitted an `@abstractmethod` decorator.
/// class has no abstract methods, you may have forgotten to add an abstract
/// method to the class or omitted an `@abstractmethod` decorator.
///
/// If the class is _not_ meant to be used as an interface, consider removing
/// the `ABC` base class from the class definition.
@@ -26,12 +24,9 @@ use crate::registry::Rule;
/// ## Example
/// ```python
/// from abc import ABC
/// from typing import ClassVar
///
///
/// class Foo(ABC):
/// class_var: ClassVar[str] = "assigned"
///
/// def method(self):
/// bar()
/// ```
@@ -39,12 +34,9 @@ use crate::registry::Rule;
/// Use instead:
/// ```python
/// from abc import ABC, abstractmethod
/// from typing import ClassVar
///
///
/// class Foo(ABC):
/// class_var: ClassVar[str] # unassigned
///
/// @abstractmethod
/// def method(self):
/// bar()
@@ -52,7 +44,6 @@ use crate::registry::Rule;
///
/// ## References
/// - [Python documentation: `abc`](https://docs.python.org/3/library/abc.html)
/// - [Python documentation: `typing.ClassVar`](https://docs.python.org/3/library/typing.html#typing.ClassVar)
#[violation]
pub struct AbstractBaseClassWithoutAbstractMethod {
name: String,
@@ -62,7 +53,7 @@ impl Violation for AbstractBaseClassWithoutAbstractMethod {
#[derive_message_formats]
fn message(&self) -> String {
let AbstractBaseClassWithoutAbstractMethod { name } = self;
format!("`{name}` is an abstract base class, but it has no abstract methods or properties")
format!("`{name}` is an abstract base class, but it has no abstract methods")
}
}

View File

@@ -1,6 +1,5 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::{is_immutable_func, is_mutable_expr, is_mutable_func};
@@ -97,7 +96,7 @@ pub(crate) fn mutable_contextvar_default(checker: &mut Checker, call: &ast::Expr
&& !is_immutable_func(func, checker.semantic(), &extend_immutable_calls)))
&& checker
.semantic()
.resolve_qualified_name(map_subscript(&call.func))
.resolve_qualified_name(&call.func)
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["contextvars", "ContextVar"])
})

View File

@@ -11,11 +11,10 @@ use crate::checkers::ast::Checker;
///
/// ## Why is this bad?
/// The `warnings.warn` method uses a `stacklevel` of 1 by default, which
/// will output a stack frame of the line on which the "warn" method
/// is called. Setting it to a higher number will output a stack frame
/// from higher up the stack.
/// limits the rendered stack trace to that of the line on which the
/// `warn` method is called.
///
/// It's recommended to use a `stacklevel` of 2 or higher, to give the caller
/// It's recommended to use a `stacklevel` of 2 or higher, give the caller
/// more context about the warning.
///
/// ## Example

View File

@@ -2,7 +2,7 @@
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text
---
B024.py:18:7: B024 `Base_1` is an abstract base class, but it has no abstract methods or properties
B024.py:18:7: B024 `Base_1` is an abstract base class, but it has no abstract methods
|
18 | class Base_1(ABC): # error
| ^^^^^^ B024
@@ -10,7 +10,7 @@ B024.py:18:7: B024 `Base_1` is an abstract base class, but it has no abstract me
20 | foo()
|
B024.py:71:7: B024 `MetaBase_1` is an abstract base class, but it has no abstract methods or properties
B024.py:71:7: B024 `MetaBase_1` is an abstract base class, but it has no abstract methods
|
71 | class MetaBase_1(metaclass=ABCMeta): # error
| ^^^^^^^^^^ B024
@@ -18,7 +18,7 @@ B024.py:71:7: B024 `MetaBase_1` is an abstract base class, but it has no abstrac
73 | foo()
|
B024.py:82:7: B024 `abc_Base_1` is an abstract base class, but it has no abstract methods or properties
B024.py:82:7: B024 `abc_Base_1` is an abstract base class, but it has no abstract methods
|
82 | class abc_Base_1(abc.ABC): # error
| ^^^^^^^^^^ B024
@@ -26,7 +26,7 @@ B024.py:82:7: B024 `abc_Base_1` is an abstract base class, but it has no abstrac
84 | foo()
|
B024.py:87:7: B024 `abc_Base_2` is an abstract base class, but it has no abstract methods or properties
B024.py:87:7: B024 `abc_Base_2` is an abstract base class, but it has no abstract methods
|
87 | class abc_Base_2(metaclass=abc.ABCMeta): # error
| ^^^^^^^^^^ B024
@@ -34,7 +34,7 @@ B024.py:87:7: B024 `abc_Base_2` is an abstract base class, but it has no abstrac
89 | foo()
|
B024.py:92:7: B024 `notabc_Base_1` is an abstract base class, but it has no abstract methods or properties
B024.py:92:7: B024 `notabc_Base_1` is an abstract base class, but it has no abstract methods
|
92 | class notabc_Base_1(notabc.ABC): # error
| ^^^^^^^^^^^^^ B024
@@ -42,21 +42,21 @@ B024.py:92:7: B024 `notabc_Base_1` is an abstract base class, but it has no abst
94 | foo()
|
B024.py:132:7: B024 `abc_set_class_variable_2` is an abstract base class, but it has no abstract methods or properties
B024.py:132:7: B024 `abc_set_class_variable_2` is an abstract base class, but it has no abstract methods
|
132 | class abc_set_class_variable_2(ABC): # error (not an abstract attribute)
| ^^^^^^^^^^^^^^^^^^^^^^^^ B024
133 | foo = 2
|
B024.py:136:7: B024 `abc_set_class_variable_3` is an abstract base class, but it has no abstract methods or properties
B024.py:136:7: B024 `abc_set_class_variable_3` is an abstract base class, but it has no abstract methods
|
136 | class abc_set_class_variable_3(ABC): # error (not an abstract attribute)
| ^^^^^^^^^^^^^^^^^^^^^^^^ B024
137 | foo: int = 2
|
B024.py:141:7: B024 `abc_set_class_variable_4` is an abstract base class, but it has no abstract methods or properties
B024.py:141:7: B024 `abc_set_class_variable_4` is an abstract base class, but it has no abstract methods
|
140 | # this doesn't actually declare a class variable, it's just an expression
141 | class abc_set_class_variable_4(ABC): # error

View File

@@ -2,149 +2,127 @@
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text
---
B039.py:19:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
18 | # Bad
19 | ContextVar("cv", default=[])
| ^^ B039
20 | ContextVar("cv", default={})
21 | ContextVar("cv", default=list())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:20:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
18 | # Bad
19 | ContextVar("cv", default=[])
20 | ContextVar("cv", default={})
| ^^ B039
21 | ContextVar("cv", default=list())
22 | ContextVar("cv", default=set())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:21:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
20 | # Bad
21 | ContextVar("cv", default=[])
| ^^ B039
22 | ContextVar("cv", default={})
23 | ContextVar("cv", default=list())
19 | ContextVar("cv", default=[])
20 | ContextVar("cv", default={})
21 | ContextVar("cv", default=list())
| ^^^^^^ B039
22 | ContextVar("cv", default=set())
23 | ContextVar("cv", default=dict())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:22:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
20 | # Bad
21 | ContextVar("cv", default=[])
22 | ContextVar("cv", default={})
| ^^ B039
23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=set())
20 | ContextVar("cv", default={})
21 | ContextVar("cv", default=list())
22 | ContextVar("cv", default=set())
| ^^^^^ B039
23 | ContextVar("cv", default=dict())
24 | ContextVar("cv", default=[char for char in "foo"])
|
= help: Replace with `None`; initialize with `.set()``
B039.py:23:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
21 | ContextVar("cv", default=[])
22 | ContextVar("cv", default={})
23 | ContextVar("cv", default=list())
21 | ContextVar("cv", default=list())
22 | ContextVar("cv", default=set())
23 | ContextVar("cv", default=dict())
| ^^^^^^ B039
24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default=dict())
24 | ContextVar("cv", default=[char for char in "foo"])
25 | ContextVar("cv", default={char for char in "foo"})
|
= help: Replace with `None`; initialize with `.set()``
B039.py:24:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
22 | ContextVar("cv", default={})
23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=set())
| ^^^^^ B039
25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default=[char for char in "foo"])
22 | ContextVar("cv", default=set())
23 | ContextVar("cv", default=dict())
24 | ContextVar("cv", default=[char for char in "foo"])
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039
25 | ContextVar("cv", default={char for char in "foo"})
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
|
= help: Replace with `None`; initialize with `.set()``
B039.py:25:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default=dict())
| ^^^^^^ B039
26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default={char for char in "foo"})
23 | ContextVar("cv", default=dict())
24 | ContextVar("cv", default=[char for char in "foo"])
25 | ContextVar("cv", default={char for char in "foo"})
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
27 | ContextVar("cv", default=collections.deque())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:26:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default=[char for char in "foo"])
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039
27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
24 | ContextVar("cv", default=[char for char in "foo"])
25 | ContextVar("cv", default={char for char in "foo"})
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B039
27 | ContextVar("cv", default=collections.deque())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:27:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default={char for char in "foo"})
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
29 | ContextVar("cv", default=collections.deque())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:28:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B039
29 | ContextVar("cv", default=collections.deque())
30 | ContextVar("cv", default=set[str]())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:29:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
29 | ContextVar("cv", default=collections.deque())
25 | ContextVar("cv", default={char for char in "foo"})
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
27 | ContextVar("cv", default=collections.deque())
| ^^^^^^^^^^^^^^^^^^^ B039
30 | ContextVar("cv", default=set[str]())
31 | ContextVar[set[str]]("cv", default=set[str]())
28 |
29 | def bar() -> list[int]:
|
= help: Replace with `None`; initialize with `.set()``
B039.py:30:26: B039 Do not use mutable data structures for `ContextVar` defaults
B039.py:32:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
29 | ContextVar("cv", default=collections.deque())
30 | ContextVar("cv", default=set[str]())
| ^^^^^^^^^^ B039
31 | ContextVar[set[str]]("cv", default=set[str]())
30 | return [1, 2, 3]
31 |
32 | ContextVar("cv", default=bar())
| ^^^^^ B039
33 | ContextVar("cv", default=time.time())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:31:36: B039 Do not use mutable data structures for `ContextVar` defaults
B039.py:33:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
29 | ContextVar("cv", default=collections.deque())
30 | ContextVar("cv", default=set[str]())
31 | ContextVar[set[str]]("cv", default=set[str]())
| ^^^^^^^^^^ B039
32 |
33 | def bar() -> list[int]:
32 | ContextVar("cv", default=bar())
33 | ContextVar("cv", default=time.time())
| ^^^^^^^^^^^ B039
34 |
35 | def baz(): ...
|
= help: Replace with `None`; initialize with `.set()``
B039.py:36:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
34 | return [1, 2, 3]
35 |
36 | ContextVar("cv", default=bar())
| ^^^^^ B039
37 | ContextVar("cv", default=time.time())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:37:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
36 | ContextVar("cv", default=bar())
37 | ContextVar("cv", default=time.time())
| ^^^^^^^^^^^ B039
38 |
39 | def baz(): ...
|
= help: Replace with `None`; initialize with `.set()``
B039.py:40:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
39 | def baz(): ...
40 | ContextVar("cv", default=baz())
35 | def baz(): ...
36 | ContextVar("cv", default=baz())
| ^^^^^ B039
|
= help: Replace with `None`; initialize with `.set()``

View File

@@ -36,10 +36,6 @@ mod tests {
Rule::BuiltinModuleShadowing,
Path::new("A005/modules/package/bisect.py")
)]
#[test_case(
Rule::BuiltinModuleShadowing,
Path::new("A005/modules/_abc/__init__.py")
)]
#[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))]
#[test_case(Rule::BuiltinLambdaArgumentShadowing, Path::new("A006.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
@@ -95,10 +91,6 @@ mod tests {
Rule::BuiltinModuleShadowing,
Path::new("A005/modules/package/bisect.py")
)]
#[test_case(
Rule::BuiltinModuleShadowing,
Path::new("A005/modules/_abc/__init__.py")
)]
#[test_case(Rule::BuiltinModuleShadowing, Path::new("A005/modules/package/xml.py"))]
fn builtins_allowed_modules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(

View File

@@ -1,5 +1,7 @@
use std::path::Path;
use crate::package::PackageRoot;
use crate::settings::types::PythonVersion;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::PySourceType;
@@ -7,9 +9,6 @@ use ruff_python_stdlib::path::is_module_file;
use ruff_python_stdlib::sys::is_known_standard_library;
use ruff_text_size::TextRange;
use crate::package::PackageRoot;
use crate::settings::types::PythonVersion;
/// ## What it does
/// Checks for modules that use the same names as Python builtin modules.
///
@@ -48,35 +47,25 @@ pub(crate) fn builtin_module_shadowing(
return None;
}
let package = package?;
if let Some(package) = package {
let module_name = if is_module_file(path) {
package.path().file_name().unwrap().to_string_lossy()
} else {
path.file_stem().unwrap().to_string_lossy()
};
let module_name = if is_module_file(path) {
package.path().file_name().unwrap().to_string_lossy()
} else {
path.file_stem().unwrap().to_string_lossy()
};
if !is_known_standard_library(target_version.minor(), &module_name) {
return None;
if is_known_standard_library(target_version.minor(), &module_name)
&& allowed_modules
.iter()
.all(|allowed_module| allowed_module != &module_name)
{
return Some(Diagnostic::new(
BuiltinModuleShadowing {
name: module_name.to_string(),
},
TextRange::default(),
));
}
}
// Shadowing private stdlib modules is okay.
// https://github.com/astral-sh/ruff/issues/12949
if module_name.starts_with('_') && !module_name.starts_with("__") {
return None;
}
if allowed_modules
.iter()
.any(|allowed_module| allowed_module == &module_name)
{
return None;
}
Some(Diagnostic::new(
BuiltinModuleShadowing {
name: module_name.to_string(),
},
TextRange::default(),
))
None
}

View File

@@ -1,5 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
snapshot_kind: text
---

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