Compare commits
56 Commits
dcreager/u
...
alex/submo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c86b40ee1 | ||
|
|
0c6d652b5f | ||
|
|
03fe560164 | ||
|
|
68343e7edf | ||
|
|
a57e291311 | ||
|
|
f317a71682 | ||
|
|
35bfcff24d | ||
|
|
474b00568a | ||
|
|
3b23d3c041 | ||
|
|
3f4875313f | ||
|
|
8327f262ff | ||
|
|
8c4a9d8808 | ||
|
|
907e7f7705 | ||
|
|
7e8915d76e | ||
|
|
680297aef8 | ||
|
|
314a6e58ed | ||
|
|
918fc2c773 | ||
|
|
e642874cf1 | ||
|
|
aec225d825 | ||
|
|
d24c891a4b | ||
|
|
859f9ec21a | ||
|
|
3410041b4c | ||
|
|
f2ce5e561a | ||
|
|
f495c6d4ae | ||
|
|
768bb24cdf | ||
|
|
492d676736 | ||
|
|
ddc1417f22 | ||
|
|
040aa7463b | ||
|
|
09d457aa52 | ||
|
|
438ef334d3 | ||
|
|
6cc502781f | ||
|
|
e2a1d1a8eb | ||
|
|
040b482cf7 | ||
|
|
03dfbf21eb | ||
|
|
e3c78d8203 | ||
|
|
a9b3caf181 | ||
|
|
629258241f | ||
|
|
762c44527e | ||
|
|
59c6cb521d | ||
|
|
54dba15088 | ||
|
|
1af318534a | ||
|
|
553e568624 | ||
|
|
cdef3f5ab8 | ||
|
|
6178822427 | ||
|
|
6b7adb0537 | ||
|
|
06941c1987 | ||
|
|
eb7c098d6b | ||
|
|
1b28fc1f14 | ||
|
|
290a5720cb | ||
|
|
c4767f5aa8 | ||
|
|
6e84f4fd7a | ||
|
|
78ce17ce8f | ||
|
|
0761ea42d9 | ||
|
|
416e2267da | ||
|
|
02c102da88 | ||
|
|
29c24bc8a6 |
40
.github/workflows/ci.yaml
vendored
40
.github/workflows/ci.yaml
vendored
@@ -261,15 +261,15 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -319,11 +319,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -352,11 +352,11 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
@@ -462,7 +462,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
shared-key: ruff-linux-debug
|
||||
@@ -497,7 +497,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -532,7 +532,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
activate-environment: true
|
||||
@@ -638,7 +638,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -697,7 +697,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -748,7 +748,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
@@ -792,7 +792,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
python-version: 3.13
|
||||
activate-environment: true
|
||||
@@ -947,13 +947,13 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -987,13 +987,13 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -1027,13 +1027,13 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -250,7 +250,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
10
.github/workflows/sync_typeshed.yaml
vendored
10
.github/workflows/sync_typeshed.yaml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -207,12 +207,12 @@ jobs:
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@537c30d2b45cc3aa3fb35e2bbcfb61ef93fd6f02 # v2.62.52
|
||||
uses: taiki-e/install-action@f79fe7514db78f0a7bdba3cb6dd9c1baa7d046d9 # v2.62.56
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Update snapshots
|
||||
|
||||
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
4
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
|
||||
4
.github/workflows/ty-ecosystem-report.yaml
vendored
4
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
with:
|
||||
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e5c5f5b2d762af91b28490537fe0077334165693"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@e26ebfb78d372b8b091e1cb1d6fc522e135474c1"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--verbose \
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,5 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.6
|
||||
|
||||
Released on 2025-11-21.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bandit`\] Support new PySNMP API paths (`S508`, `S509`) ([#21374](https://github.com/astral-sh/ruff/pull/21374))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Adjust own-line comment placement between branches ([#21185](https://github.com/astral-sh/ruff/pull/21185))
|
||||
- Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value ([#20418](https://github.com/astral-sh/ruff/pull/20418))
|
||||
- Fix panic when formatting comments in unary expressions ([#21501](https://github.com/astral-sh/ruff/pull/21501))
|
||||
- Respect `fmt: skip` for compound statements on a single line ([#20633](https://github.com/astral-sh/ruff/pull/20633))
|
||||
- \[`refurb`\] Fix `FURB103` autofix ([#21454](https://github.com/astral-sh/ruff/pull/21454))
|
||||
- \[`ruff`\] Fix false positive for complex conversion specifiers in `logging-eager-conversion` (`RUF065`) ([#21464](https://github.com/astral-sh/ruff/pull/21464))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`ruff`\] Avoid false positive on `ClassVar` reassignment (`RUF012`) ([#21478](https://github.com/astral-sh/ruff/pull/21478))
|
||||
|
||||
### CLI
|
||||
|
||||
- Render hyperlinks for lint errors ([#21514](https://github.com/astral-sh/ruff/pull/21514))
|
||||
- Add a `ruff analyze` option to skip over imports in `TYPE_CHECKING` blocks ([#21472](https://github.com/astral-sh/ruff/pull/21472))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Limit `eglot-format` hook to eglot-managed Python buffers ([#21459](https://github.com/astral-sh/ruff/pull/21459))
|
||||
- Mention `force-exclude` in "Configuration > Python file discovery" ([#21500](https://github.com/astral-sh/ruff/pull/21500))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@gauthsvenkat](https://github.com/gauthsvenkat)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@thamer](https://github.com/thamer)
|
||||
- [@Ruchir28](https://github.com/Ruchir28)
|
||||
- [@thejcannon](https://github.com/thejcannon)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@chirizxc](https://github.com/chirizxc)
|
||||
|
||||
## 0.14.5
|
||||
|
||||
Released on 2025-11-13.
|
||||
|
||||
52
Cargo.lock
generated
52
Cargo.lock
generated
@@ -442,9 +442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.51"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -452,9 +452,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.51"
|
||||
version = "4.5.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -1016,7 +1016,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1108,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1255,7 +1255,7 @@ checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"smallvec",
|
||||
]
|
||||
@@ -1353,9 +1353,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
]
|
||||
@@ -1564,12 +1564,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.12.0"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
|
||||
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
@@ -1763,7 +1763,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2859,7 +2859,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3117,7 +3117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3127,7 +3127,7 @@ dependencies = [
|
||||
"fern",
|
||||
"glob",
|
||||
"globset",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"imperative",
|
||||
"insta",
|
||||
"is-macro",
|
||||
@@ -3472,7 +3472,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3570,7 +3570,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3588,7 +3588,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3612,12 +3612,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a885bb4c4c192741b8a17418fef81a71e33d111e#a885bb4c4c192741b8a17418fef81a71e33d111e"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=17bc55d699565e5a1cb1bd42363b905af2f9f3e7#17bc55d699565e5a1cb1bd42363b905af2f9f3e7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3935,9 +3935,9 @@ checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.110"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3971,7 +3971,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4462,7 +4462,7 @@ dependencies = [
|
||||
"drop_bomb",
|
||||
"get-size2",
|
||||
"glob",
|
||||
"hashbrown 0.16.0",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"indoc",
|
||||
"insta",
|
||||
@@ -5020,7 +5020,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -146,7 +146,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a885bb4c4c192741b8a17418fef81a71e33d111e", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "17bc55d699565e5a1cb1bd42363b905af2f9f3e7", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -147,8 +147,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.14.5/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.5/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.6/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.6/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -181,7 +181,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.14.5
|
||||
rev: v0.14.6
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -59,8 +59,6 @@ divan = { workspace = true, optional = true }
|
||||
anyhow = { workspace = true }
|
||||
codspeed-criterion-compat = { workspace = true, default-features = false, optional = true }
|
||||
criterion = { workspace = true, default-features = false, optional = true }
|
||||
rayon = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -88,3 +86,7 @@ mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64", target_arch = "riscv64")))'.dev-dependencies]
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-hash = { workspace = true }
|
||||
rayon = { workspace = true }
|
||||
|
||||
@@ -143,7 +143,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
525,
|
||||
600,
|
||||
);
|
||||
|
||||
static PANDAS: Benchmark = Benchmark::new(
|
||||
@@ -163,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
3000,
|
||||
4000,
|
||||
);
|
||||
|
||||
static PYDANTIC: Benchmark = Benchmark::new(
|
||||
@@ -181,7 +181,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY39,
|
||||
},
|
||||
5000,
|
||||
7000,
|
||||
);
|
||||
|
||||
static SYMPY: Benchmark = Benchmark::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -208,3 +208,17 @@ _ = t"b {f"c" f"d {t"e" t"f"} g"} h"
|
||||
_ = f"b {t"abc" \
|
||||
t"def"} g"
|
||||
|
||||
|
||||
# Explicit concatenation with either operand being
|
||||
# a string literal that wraps across multiple lines (in parentheses)
|
||||
# reports diagnostic - no autofix.
|
||||
# See https://github.com/astral-sh/ruff/issues/19757
|
||||
_ = "abc" + (
|
||||
"def"
|
||||
"ghi"
|
||||
)
|
||||
|
||||
_ = (
|
||||
"abc"
|
||||
"def"
|
||||
) + "ghi"
|
||||
|
||||
@@ -30,3 +30,23 @@ for a, b in d_tuple:
|
||||
pass
|
||||
for a, b in d_tuple_annotated:
|
||||
pass
|
||||
|
||||
# Empty dict cases
|
||||
empty_dict = {}
|
||||
empty_dict["x"] = 1
|
||||
for k, v in empty_dict:
|
||||
pass
|
||||
|
||||
empty_dict_annotated_tuple_keys: dict[tuple[int, str], bool] = {}
|
||||
for k, v in empty_dict_annotated_tuple_keys:
|
||||
pass
|
||||
|
||||
empty_dict_unannotated = {}
|
||||
empty_dict_unannotated[("x", "y")] = True
|
||||
for k, v in empty_dict_unannotated:
|
||||
pass
|
||||
|
||||
empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
empty_dict_annotated_str_keys["x"] = 1
|
||||
for k, v in empty_dict_annotated_str_keys:
|
||||
pass
|
||||
|
||||
@@ -129,3 +129,26 @@ def generator_with_lambda():
|
||||
yield 1
|
||||
func = lambda x: x # Just a regular lambda
|
||||
yield 2
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/21162
|
||||
def foo():
|
||||
def g():
|
||||
yield 1
|
||||
raise StopIteration # Should not trigger
|
||||
|
||||
|
||||
def foo():
|
||||
def g():
|
||||
raise StopIteration # Should not trigger
|
||||
yield 1
|
||||
|
||||
# https://github.com/astral-sh/ruff/pull/21177#pullrequestreview-3430209718
|
||||
def foo():
|
||||
yield 1
|
||||
class C:
|
||||
raise StopIteration # Should trigger
|
||||
yield C
|
||||
|
||||
# https://github.com/astral-sh/ruff/pull/21177#discussion_r2539702728
|
||||
def foo():
|
||||
raise StopIteration((yield 1)) # Should trigger
|
||||
109
crates/ruff_linter/resources/test/fixtures/ruff/RUF052_1.py
vendored
Normal file
109
crates/ruff_linter/resources/test/fixtures/ruff/RUF052_1.py
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
# Correct usage in loop and comprehension
|
||||
def process_data():
|
||||
return 42
|
||||
def test_correct_dummy_usage():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[process_data() for _ in my_list] # OK: `_` is ignored by rule
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[item["foo"] for item in my_list] # OK: not a dummy variable name
|
||||
|
||||
# Should NOT detect - dummy variable is not used
|
||||
[42 for _unused in my_list] # OK: `_unused` is not accessed
|
||||
|
||||
# Regular For Loops
|
||||
def test_for_loops():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
for _item in my_list:
|
||||
print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect used dummy variable
|
||||
for _index, _value in enumerate(my_list):
|
||||
result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
||||
# List Comprehensions
|
||||
def test_list_comprehensions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect used dummy variable in nested comprehension
|
||||
nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
# RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
||||
# Should detect with conditions
|
||||
filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Dict Comprehensions
|
||||
def test_dict_comprehensions():
|
||||
my_list = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with enumerate
|
||||
indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
# RUF052: Both `_index` and `_item` are accessed
|
||||
|
||||
# Should detect in nested dict comprehension
|
||||
nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
for _outer, sublist in enumerate([my_list])}
|
||||
# RUF052: `_outer`, `_inner` are accessed
|
||||
|
||||
# Set Comprehensions
|
||||
def test_set_comprehensions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}, {"foo": 1}] # Note: duplicate values
|
||||
|
||||
# Should detect used dummy variable
|
||||
unique_values = {_item["foo"] for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with conditions
|
||||
filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with complex expression
|
||||
processed = {_item["foo"] * 2 for _item in my_list}
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Generator Expressions
|
||||
def test_generator_expressions():
|
||||
my_list = [{"foo": 1}, {"foo": 2}]
|
||||
|
||||
# Should detect used dummy variable
|
||||
gen = (_item["foo"] for _item in my_list)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect when passed to function
|
||||
total = sum(_item["foo"] for _item in my_list)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
|
||||
# Should detect with multiple generators
|
||||
pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
# RUF052: Both `_x` and `_y` are accessed
|
||||
|
||||
# Should detect in nested generator
|
||||
nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
# RUF052: `_inner` and `_sublist` are accessed
|
||||
|
||||
# Complex Examples with Multiple Comprehension Types
|
||||
def test_mixed_comprehensions():
|
||||
data = [{"items": [1, 2, 3]}, {"items": [4, 5, 6]}]
|
||||
|
||||
# Should detect in mixed comprehensions
|
||||
result = [
|
||||
{_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
for _record in data
|
||||
]
|
||||
# RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
|
||||
# Should detect in generator passed to list constructor
|
||||
gen_list = list(_item["items"][0] for _item in data)
|
||||
# RUF052: Local dummy variable `_item` is accessed
|
||||
@@ -131,6 +131,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.is_rule_enabled(Rule::GeneratorReturnFromIterMethod) {
|
||||
flake8_pyi::rules::bad_generator_return_type(function_def, checker);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StopIterationReturn) {
|
||||
pylint::rules::stop_iteration_return(checker, function_def);
|
||||
}
|
||||
if checker.source_type.is_stub() {
|
||||
if checker.is_rule_enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(checker, stmt);
|
||||
@@ -950,9 +953,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.is_rule_enabled(Rule::MisplacedBareRaise) {
|
||||
pylint::rules::misplaced_bare_raise(checker, raise);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StopIterationReturn) {
|
||||
pylint::rules::stop_iteration_return(checker, raise);
|
||||
}
|
||||
}
|
||||
Stmt::AugAssign(aug_assign @ ast::StmtAugAssign { target, .. }) => {
|
||||
if checker.is_rule_enabled(Rule::GlobalStatement) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{Edit, Fix};
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for string literals that are explicitly concatenated (using the
|
||||
@@ -36,14 +36,16 @@ use crate::{Edit, Fix};
|
||||
#[violation_metadata(stable_since = "v0.0.201")]
|
||||
pub(crate) struct ExplicitStringConcatenation;
|
||||
|
||||
impl AlwaysFixableViolation for ExplicitStringConcatenation {
|
||||
impl Violation for ExplicitStringConcatenation {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Explicitly concatenated string should be implicitly concatenated".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove redundant '+' operator to implicitly concatenate".to_string()
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove redundant '+' operator to implicitly concatenate".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +84,27 @@ pub(crate) fn explicit(checker: &Checker, expr: &Expr) {
|
||||
.locator()
|
||||
.contains_line_break(TextRange::new(left.end(), right.start()))
|
||||
{
|
||||
checker
|
||||
.report_diagnostic(ExplicitStringConcatenation, expr.range())
|
||||
.set_fix(generate_fix(checker, bin_op));
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(ExplicitStringConcatenation, expr.range());
|
||||
|
||||
let is_parenthesized = |expr: &Expr| {
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
bin_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.is_some()
|
||||
};
|
||||
// If either `left` or `right` is parenthesized, generating
|
||||
// a fix would be too involved. Just report the diagnostic.
|
||||
// Currently, attempting `generate_fix` would result in
|
||||
// an invalid code. See: #19757
|
||||
if is_parenthesized(left) || is_parenthesized(right) {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.set_fix(generate_fix(checker, bin_op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,3 +357,33 @@ help: Remove redundant '+' operator to implicitly concatenate
|
||||
203 | )
|
||||
204 |
|
||||
205 | # nested examples with both t and f-strings
|
||||
|
||||
ISC003 Explicitly concatenated string should be implicitly concatenated
|
||||
--> ISC.py:216:5
|
||||
|
|
||||
214 | # reports diagnostic - no autofix.
|
||||
215 | # See https://github.com/astral-sh/ruff/issues/19757
|
||||
216 | _ = "abc" + (
|
||||
| _____^
|
||||
217 | | "def"
|
||||
218 | | "ghi"
|
||||
219 | | )
|
||||
| |_^
|
||||
220 |
|
||||
221 | _ = (
|
||||
|
|
||||
help: Remove redundant '+' operator to implicitly concatenate
|
||||
|
||||
ISC003 Explicitly concatenated string should be implicitly concatenated
|
||||
--> ISC.py:221:5
|
||||
|
|
||||
219 | )
|
||||
220 |
|
||||
221 | _ = (
|
||||
| _____^
|
||||
222 | | "abc"
|
||||
223 | | "def"
|
||||
224 | | ) + "ghi"
|
||||
| |_________^
|
||||
|
|
||||
help: Remove redundant '+' operator to implicitly concatenate
|
||||
|
||||
@@ -89,3 +89,24 @@ ISC002 Implicitly concatenated string literals over multiple lines
|
||||
209 | | t"def"} g"
|
||||
| |__________^
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:217:5
|
||||
|
|
||||
215 | # See https://github.com/astral-sh/ruff/issues/19757
|
||||
216 | _ = "abc" + (
|
||||
217 | / "def"
|
||||
218 | | "ghi"
|
||||
| |_________^
|
||||
219 | )
|
||||
|
|
||||
|
||||
ISC002 Implicitly concatenated string literals over multiple lines
|
||||
--> ISC.py:222:5
|
||||
|
|
||||
221 | _ = (
|
||||
222 | / "abc"
|
||||
223 | | "def"
|
||||
| |_________^
|
||||
224 | ) + "ghi"
|
||||
|
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{Expr, Stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
@@ -108,15 +108,77 @@ fn is_dict_key_tuple_with_two_elements(binding: &Binding, semantic: &SemanticMod
|
||||
return false;
|
||||
};
|
||||
|
||||
let Stmt::Assign(assign_stmt) = statement else {
|
||||
let (value, annotation) = match statement {
|
||||
Stmt::Assign(assign_stmt) => (assign_stmt.value.as_ref(), None),
|
||||
Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
value: Some(value),
|
||||
annotation,
|
||||
..
|
||||
}) => (value.as_ref(), Some(annotation.as_ref())),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
let Expr::Dict(dict_expr) = value else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Expr::Dict(dict_expr) = &*assign_stmt.value else {
|
||||
return false;
|
||||
};
|
||||
// Check if dict is empty
|
||||
let is_empty = dict_expr.is_empty();
|
||||
|
||||
if is_empty {
|
||||
// For empty dicts, check type annotation
|
||||
return annotation
|
||||
.is_some_and(|annotation| is_annotation_dict_with_tuple_keys(annotation, semantic));
|
||||
}
|
||||
|
||||
// For non-empty dicts, check if all keys are 2-tuples
|
||||
dict_expr
|
||||
.iter_keys()
|
||||
.all(|key| matches!(key, Some(Expr::Tuple(tuple)) if tuple.len() == 2))
|
||||
}
|
||||
|
||||
/// Returns true if the annotation is `dict[tuple[T1, T2], ...]` where tuple has exactly 2 elements.
|
||||
fn is_annotation_dict_with_tuple_keys(annotation: &Expr, semantic: &SemanticModel) -> bool {
|
||||
// Check if it's a subscript: dict[...]
|
||||
let Expr::Subscript(subscript) = annotation else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if it's dict or typing.Dict
|
||||
if !semantic.match_builtin_expr(subscript.value.as_ref(), "dict")
|
||||
&& !semantic.match_typing_expr(subscript.value.as_ref(), "Dict")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the slice (should be a tuple: (key_type, value_type))
|
||||
let Expr::Tuple(tuple) = subscript.slice.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// dict[K, V] format - check if K is tuple with 2 elements
|
||||
if let [key, _value] = tuple.elts.as_slice() {
|
||||
return is_tuple_type_with_two_elements(key, semantic);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns true if the expression represents a tuple type with exactly 2 elements.
|
||||
fn is_tuple_type_with_two_elements(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
// Handle tuple[...] subscript
|
||||
if let Expr::Subscript(subscript) = expr {
|
||||
// Check if it's tuple or typing.Tuple
|
||||
if semantic.match_builtin_expr(subscript.value.as_ref(), "tuple")
|
||||
|| semantic.match_typing_expr(subscript.value.as_ref(), "Tuple")
|
||||
{
|
||||
// Check the slice - tuple[T1, T2]
|
||||
if let Expr::Tuple(tuple_slice) = subscript.slice.as_ref() {
|
||||
return tuple_slice.elts.len() == 2;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
|
||||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
helpers::map_callable,
|
||||
visitor::{Visitor, walk_expr, walk_stmt},
|
||||
};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
@@ -50,65 +53,54 @@ impl Violation for StopIterationReturn {
|
||||
}
|
||||
|
||||
/// PLR1708
|
||||
pub(crate) fn stop_iteration_return(checker: &Checker, raise_stmt: &ast::StmtRaise) {
|
||||
// Fast-path: only continue if this is `raise StopIteration` (with or without args)
|
||||
let Some(exc) = &raise_stmt.exc else {
|
||||
return;
|
||||
pub(crate) fn stop_iteration_return(checker: &Checker, function_def: &ast::StmtFunctionDef) {
|
||||
let mut analyzer = GeneratorAnalyzer {
|
||||
checker,
|
||||
has_yield: false,
|
||||
stop_iteration_raises: Vec::new(),
|
||||
};
|
||||
|
||||
let is_stop_iteration = match exc.as_ref() {
|
||||
ast::Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
checker.semantic().match_builtin_expr(func, "StopIteration")
|
||||
analyzer.visit_body(&function_def.body);
|
||||
|
||||
if analyzer.has_yield {
|
||||
for raise_stmt in analyzer.stop_iteration_raises {
|
||||
checker.report_diagnostic(StopIterationReturn, raise_stmt.range());
|
||||
}
|
||||
expr => checker.semantic().match_builtin_expr(expr, "StopIteration"),
|
||||
};
|
||||
|
||||
if !is_stop_iteration {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the (more expensive) generator context
|
||||
if !in_generator_context(checker) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(StopIterationReturn, raise_stmt.range());
|
||||
}
|
||||
|
||||
/// Returns true if we're inside a function that contains any `yield`/`yield from`.
|
||||
fn in_generator_context(checker: &Checker) -> bool {
|
||||
for scope in checker.semantic().current_scopes() {
|
||||
if let ruff_python_semantic::ScopeKind::Function(function_def) = scope.kind {
|
||||
if contains_yield_statement(&function_def.body) {
|
||||
return true;
|
||||
struct GeneratorAnalyzer<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
has_yield: bool,
|
||||
stop_iteration_raises: Vec<&'a ast::StmtRaise>,
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for GeneratorAnalyzer<'a, '_> {
|
||||
fn visit_stmt(&mut self, stmt: &'a ast::Stmt) {
|
||||
match stmt {
|
||||
ast::Stmt::FunctionDef(_) => {}
|
||||
ast::Stmt::Raise(raise @ ast::StmtRaise { exc: Some(exc), .. }) => {
|
||||
if self
|
||||
.checker
|
||||
.semantic()
|
||||
.match_builtin_expr(map_callable(exc), "StopIteration")
|
||||
{
|
||||
self.stop_iteration_raises.push(raise);
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
_ => walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if a statement list contains any yield statements
|
||||
fn contains_yield_statement(body: &[ast::Stmt]) -> bool {
|
||||
struct YieldFinder {
|
||||
found: bool,
|
||||
}
|
||||
|
||||
impl Visitor<'_> for YieldFinder {
|
||||
fn visit_expr(&mut self, expr: &ast::Expr) {
|
||||
if matches!(expr, ast::Expr::Yield(_) | ast::Expr::YieldFrom(_)) {
|
||||
self.found = true;
|
||||
} else {
|
||||
fn visit_expr(&mut self, expr: &'a ast::Expr) {
|
||||
match expr {
|
||||
ast::Expr::Lambda(_) => {}
|
||||
ast::Expr::Yield(_) | ast::Expr::YieldFrom(_) => {
|
||||
self.has_yield = true;
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
_ => walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
|
||||
let mut finder = YieldFinder { found: false };
|
||||
for stmt in body {
|
||||
walk_stmt(&mut finder, stmt);
|
||||
if finder.found {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -39,3 +39,61 @@ help: Add a call to `.items()`
|
||||
18 |
|
||||
19 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:37:13
|
||||
|
|
||||
35 | empty_dict = {}
|
||||
36 | empty_dict["x"] = 1
|
||||
37 | for k, v in empty_dict:
|
||||
| ^^^^^^^^^^
|
||||
38 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
34 | # Empty dict cases
|
||||
35 | empty_dict = {}
|
||||
36 | empty_dict["x"] = 1
|
||||
- for k, v in empty_dict:
|
||||
37 + for k, v in empty_dict.items():
|
||||
38 | pass
|
||||
39 |
|
||||
40 | empty_dict_annotated_tuple_keys: dict[tuple[int, str], bool] = {}
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:46:13
|
||||
|
|
||||
44 | empty_dict_unannotated = {}
|
||||
45 | empty_dict_unannotated[("x", "y")] = True
|
||||
46 | for k, v in empty_dict_unannotated:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
47 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
43 |
|
||||
44 | empty_dict_unannotated = {}
|
||||
45 | empty_dict_unannotated[("x", "y")] = True
|
||||
- for k, v in empty_dict_unannotated:
|
||||
46 + for k, v in empty_dict_unannotated.items():
|
||||
47 | pass
|
||||
48 |
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLE1141 [*] Unpacking a dictionary in iteration without calling `.items()`
|
||||
--> dict_iter_missing_items.py:51:13
|
||||
|
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
50 | empty_dict_annotated_str_keys["x"] = 1
|
||||
51 | for k, v in empty_dict_annotated_str_keys:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 | pass
|
||||
|
|
||||
help: Add a call to `.items()`
|
||||
48 |
|
||||
49 | empty_dict_annotated_str_keys: dict[str, int] = {}
|
||||
50 | empty_dict_annotated_str_keys["x"] = 1
|
||||
- for k, v in empty_dict_annotated_str_keys:
|
||||
51 + for k, v in empty_dict_annotated_str_keys.items():
|
||||
52 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -107,3 +107,24 @@ PLR1708 Explicit `raise StopIteration` in generator
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
PLR1708 Explicit `raise StopIteration` in generator
|
||||
--> stop_iteration_return.py:149:9
|
||||
|
|
||||
147 | yield 1
|
||||
148 | class C:
|
||||
149 | raise StopIteration # Should trigger
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
150 | yield C
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
PLR1708 Explicit `raise StopIteration` in generator
|
||||
--> stop_iteration_return.py:154:5
|
||||
|
|
||||
152 | # https://github.com/astral-sh/ruff/pull/21177#discussion_r2539702728
|
||||
153 | def foo():
|
||||
154 | raise StopIteration((yield 1)) # Should trigger
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `return` instead
|
||||
|
||||
@@ -97,7 +97,8 @@ mod tests {
|
||||
#[test_case(Rule::MapIntVersionParsing, Path::new("RUF048_1.py"))]
|
||||
#[test_case(Rule::DataclassEnum, Path::new("RUF049.py"))]
|
||||
#[test_case(Rule::IfKeyInDictDel, Path::new("RUF051.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"))]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_1.py"))]
|
||||
#[test_case(Rule::ClassWithMixedTypeVars, Path::new("RUF053.py"))]
|
||||
#[test_case(Rule::FalsyDictGetFallback, Path::new("RUF056.py"))]
|
||||
#[test_case(Rule::UnnecessaryRound, Path::new("RUF057.py"))]
|
||||
@@ -621,8 +622,8 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"), r"^_+", 1)]
|
||||
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052_0.py"), r"", 2)]
|
||||
fn custom_regexp_preset(
|
||||
rule_code: Rule,
|
||||
path: &Path,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_dunder;
|
||||
use ruff_python_semantic::{Binding, BindingId};
|
||||
use ruff_python_semantic::{Binding, BindingId, BindingKind, ScopeKind};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -111,7 +111,7 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
return;
|
||||
}
|
||||
|
||||
// We only emit the lint on variables defined via assignments.
|
||||
// We only emit the lint on local variables.
|
||||
//
|
||||
// ## Why not also emit the lint on function parameters?
|
||||
//
|
||||
@@ -127,8 +127,30 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
// autofixing the diagnostic for assignments. See:
|
||||
// - <https://github.com/astral-sh/ruff/issues/14790>
|
||||
// - <https://github.com/astral-sh/ruff/issues/14799>
|
||||
if !binding.kind.is_assignment() {
|
||||
return;
|
||||
match binding.kind {
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::Assignment
|
||||
| BindingKind::LoopVar
|
||||
| BindingKind::WithItemVar
|
||||
| BindingKind::BoundException
|
||||
| BindingKind::UnboundException(_) => {}
|
||||
|
||||
BindingKind::TypeParam
|
||||
| BindingKind::Global(_)
|
||||
| BindingKind::Nonlocal(_, _)
|
||||
| BindingKind::Builtin
|
||||
| BindingKind::ClassDefinition(_)
|
||||
| BindingKind::FunctionDefinition(_)
|
||||
| BindingKind::Export(_)
|
||||
| BindingKind::FutureImport
|
||||
| BindingKind::Import(_)
|
||||
| BindingKind::FromImport(_)
|
||||
| BindingKind::SubmoduleImport(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::ConditionalDeletion(_)
|
||||
| BindingKind::DunderClassCell => return,
|
||||
}
|
||||
|
||||
// This excludes `global` and `nonlocal` variables.
|
||||
@@ -138,9 +160,12 @@ pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding, binding_
|
||||
|
||||
let semantic = checker.semantic();
|
||||
|
||||
// Only variables defined in function scopes
|
||||
// Only variables defined in function and generator scopes
|
||||
let scope = &semantic.scopes[binding.scope];
|
||||
if !scope.kind.is_function() {
|
||||
if !matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::Generator { .. }
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -24,7 +24,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -45,7 +45,7 @@ help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -67,7 +67,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -90,7 +90,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -112,7 +112,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -123,7 +123,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -133,7 +133,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -154,7 +154,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 [*] Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -184,7 +184,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -213,7 +213,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -242,7 +242,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -270,7 +270,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -297,7 +297,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -323,7 +323,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -347,7 +347,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -371,7 +371,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -396,7 +396,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -0,0 +1,494 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:21:9
|
||||
|
|
||||
20 | # Should detect used dummy variable
|
||||
21 | for _item in my_list:
|
||||
| ^^^^^
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
18 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
19 |
|
||||
20 | # Should detect used dummy variable
|
||||
- for _item in my_list:
|
||||
- print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
21 + for item in my_list:
|
||||
22 + print(item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_index` is accessed
|
||||
--> RUF052_1.py:25:9
|
||||
|
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
| ^^^^^^
|
||||
26 | result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
- for _index, _value in enumerate(my_list):
|
||||
- result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
25 + for index, _value in enumerate(my_list):
|
||||
26 + result = index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
27 |
|
||||
28 | # List Comprehensions
|
||||
29 | def test_list_comprehensions():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_value` is accessed
|
||||
--> RUF052_1.py:25:17
|
||||
|
|
||||
24 | # Should detect used dummy variable
|
||||
25 | for _index, _value in enumerate(my_list):
|
||||
| ^^^^^^
|
||||
26 | result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
22 | print(_item["foo"]) # RUF052: Local dummy variable `_item` is accessed
|
||||
23 |
|
||||
24 | # Should detect used dummy variable
|
||||
- for _index, _value in enumerate(my_list):
|
||||
- result = _index + _value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
25 + for _index, value in enumerate(my_list):
|
||||
26 + result = _index + value["foo"] # RUF052: Both `_index` and `_value` are accessed
|
||||
27 |
|
||||
28 | # List Comprehensions
|
||||
29 | def test_list_comprehensions():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:33:32
|
||||
|
|
||||
32 | # Should detect used dummy variable
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
| ^^^^^
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
|
|
||||
help: Remove leading underscores
|
||||
30 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
31 |
|
||||
32 | # Should detect used dummy variable
|
||||
- result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
33 + result = [item["foo"] for item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:36:33
|
||||
|
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
| ^^^^^
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
- nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
36 + nested = [[item["foo"] for item in _sublist] for _sublist in [my_list, my_list]]
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_sublist` is accessed
|
||||
--> RUF052_1.py:36:56
|
||||
|
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
36 | nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
| ^^^^^^^^
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
33 | result = [_item["foo"] for _item in my_list] # RUF052: Local dummy variable `_item` is accessed
|
||||
34 |
|
||||
35 | # Should detect used dummy variable in nested comprehension
|
||||
- nested = [[_item["foo"] for _item in _sublist] for _sublist in [my_list, my_list]]
|
||||
36 + nested = [[_item["foo"] for _item in sublist] for sublist in [my_list, my_list]]
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:40:34
|
||||
|
|
||||
39 | # Should detect with conditions
|
||||
40 | filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
| ^^^^^
|
||||
41 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
37 | # RUF052: Both `_item` and `_sublist` are accessed
|
||||
38 |
|
||||
39 | # Should detect with conditions
|
||||
- filtered = [_item["foo"] for _item in my_list if _item["foo"] > 0]
|
||||
40 + filtered = [item["foo"] for item in my_list if item["foo"] > 0]
|
||||
41 | # RUF052: Local dummy variable `_item` is accessed
|
||||
42 |
|
||||
43 | # Dict Comprehensions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:48:48
|
||||
|
|
||||
47 | # Should detect used dummy variable
|
||||
48 | result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
| ^^^^^
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
45 | my_list = [{"key": "a", "value": 1}, {"key": "b", "value": 2}]
|
||||
46 |
|
||||
47 | # Should detect used dummy variable
|
||||
- result = {_item["key"]: _item["value"] for _item in my_list}
|
||||
48 + result = {item["key"]: item["value"] for item in my_list}
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_index` is accessed
|
||||
--> RUF052_1.py:52:43
|
||||
|
|
||||
51 | # Should detect with enumerate
|
||||
52 | indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
| ^^^^^^
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
- indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
52 + indexed = {index: _item["value"] for index, _item in enumerate(my_list)}
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:52:51
|
||||
|
|
||||
51 | # Should detect with enumerate
|
||||
52 | indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
| ^^^^^
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
49 | # RUF052: Local dummy variable `_item` is accessed
|
||||
50 |
|
||||
51 | # Should detect with enumerate
|
||||
- indexed = {_index: _item["value"] for _index, _item in enumerate(my_list)}
|
||||
52 + indexed = {_index: item["value"] for _index, item in enumerate(my_list)}
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_inner` is accessed
|
||||
--> RUF052_1.py:56:59
|
||||
|
|
||||
55 | # Should detect in nested dict comprehension
|
||||
56 | nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
| ^^^^^^
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
- nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
56 + nested = {_outer: {inner["key"]: inner["value"] for inner in sublist}
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
59 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_outer` is accessed
|
||||
--> RUF052_1.py:57:19
|
||||
|
|
||||
55 | # Should detect in nested dict comprehension
|
||||
56 | nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
57 | for _outer, sublist in enumerate([my_list])}
|
||||
| ^^^^^^
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
53 | # RUF052: Both `_index` and `_item` are accessed
|
||||
54 |
|
||||
55 | # Should detect in nested dict comprehension
|
||||
- nested = {_outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
- for _outer, sublist in enumerate([my_list])}
|
||||
56 + nested = {outer: {_inner["key"]: _inner["value"] for _inner in sublist}
|
||||
57 + for outer, sublist in enumerate([my_list])}
|
||||
58 | # RUF052: `_outer`, `_inner` are accessed
|
||||
59 |
|
||||
60 | # Set Comprehensions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:65:39
|
||||
|
|
||||
64 | # Should detect used dummy variable
|
||||
65 | unique_values = {_item["foo"] for _item in my_list}
|
||||
| ^^^^^
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
62 | my_list = [{"foo": 1}, {"foo": 2}, {"foo": 1}] # Note: duplicate values
|
||||
63 |
|
||||
64 | # Should detect used dummy variable
|
||||
- unique_values = {_item["foo"] for _item in my_list}
|
||||
65 + unique_values = {item["foo"] for item in my_list}
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
67 |
|
||||
68 | # Should detect with conditions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:69:38
|
||||
|
|
||||
68 | # Should detect with conditions
|
||||
69 | filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
| ^^^^^
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
66 | # RUF052: Local dummy variable `_item` is accessed
|
||||
67 |
|
||||
68 | # Should detect with conditions
|
||||
- filtered_set = {_item["foo"] for _item in my_list if _item["foo"] > 0}
|
||||
69 + filtered_set = {item["foo"] for item in my_list if item["foo"] > 0}
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
71 |
|
||||
72 | # Should detect with complex expression
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:73:39
|
||||
|
|
||||
72 | # Should detect with complex expression
|
||||
73 | processed = {_item["foo"] * 2 for _item in my_list}
|
||||
| ^^^^^
|
||||
74 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
70 | # RUF052: Local dummy variable `_item` is accessed
|
||||
71 |
|
||||
72 | # Should detect with complex expression
|
||||
- processed = {_item["foo"] * 2 for _item in my_list}
|
||||
73 + processed = {item["foo"] * 2 for item in my_list}
|
||||
74 | # RUF052: Local dummy variable `_item` is accessed
|
||||
75 |
|
||||
76 | # Generator Expressions
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:81:29
|
||||
|
|
||||
80 | # Should detect used dummy variable
|
||||
81 | gen = (_item["foo"] for _item in my_list)
|
||||
| ^^^^^
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
78 | my_list = [{"foo": 1}, {"foo": 2}]
|
||||
79 |
|
||||
80 | # Should detect used dummy variable
|
||||
- gen = (_item["foo"] for _item in my_list)
|
||||
81 + gen = (item["foo"] for item in my_list)
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
83 |
|
||||
84 | # Should detect when passed to function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:85:34
|
||||
|
|
||||
84 | # Should detect when passed to function
|
||||
85 | total = sum(_item["foo"] for _item in my_list)
|
||||
| ^^^^^
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
82 | # RUF052: Local dummy variable `_item` is accessed
|
||||
83 |
|
||||
84 | # Should detect when passed to function
|
||||
- total = sum(_item["foo"] for _item in my_list)
|
||||
85 + total = sum(item["foo"] for item in my_list)
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052_1.py:89:27
|
||||
|
|
||||
88 | # Should detect with multiple generators
|
||||
89 | pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
| ^^
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
- pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
89 + pairs = ((x, _y) for x in range(3) for _y in range(3) if x != _y)
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_y` is accessed
|
||||
--> RUF052_1.py:89:46
|
||||
|
|
||||
88 | # Should detect with multiple generators
|
||||
89 | pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
| ^^
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
86 | # RUF052: Local dummy variable `_item` is accessed
|
||||
87 |
|
||||
88 | # Should detect with multiple generators
|
||||
- pairs = ((_x, _y) for _x in range(3) for _y in range(3) if _x != _y)
|
||||
89 + pairs = ((_x, y) for _x in range(3) for y in range(3) if _x != y)
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_inner` is accessed
|
||||
--> RUF052_1.py:93:41
|
||||
|
|
||||
92 | # Should detect in nested generator
|
||||
93 | nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
| ^^^^^^
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
- nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
93 + nested_gen = (sum(inner["foo"] for inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
95 |
|
||||
96 | # Complex Examples with Multiple Comprehension Types
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_sublist` is accessed
|
||||
--> RUF052_1.py:93:64
|
||||
|
|
||||
92 | # Should detect in nested generator
|
||||
93 | nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
| ^^^^^^^^
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
|
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
90 | # RUF052: Both `_x` and `_y` are accessed
|
||||
91 |
|
||||
92 | # Should detect in nested generator
|
||||
- nested_gen = (sum(_inner["foo"] for _inner in sublist) for _sublist in [my_list] for sublist in _sublist)
|
||||
93 + nested_gen = (sum(_inner["foo"] for _inner in sublist) for sublist_ in [my_list] for sublist in sublist_)
|
||||
94 | # RUF052: `_inner` and `_sublist` are accessed
|
||||
95 |
|
||||
96 | # Complex Examples with Multiple Comprehension Types
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_val` is accessed
|
||||
--> RUF052_1.py:102:30
|
||||
|
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
| ^^^^
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
102 + {_key: [val * 2 for val in _record["items"]] for _key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_key` is accessed
|
||||
--> RUF052_1.py:102:60
|
||||
|
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
| ^^^^
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
102 + {key: [_val * 2 for _val in _record["items"]] for key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_record` is accessed
|
||||
--> RUF052_1.py:103:13
|
||||
|
|
||||
101 | result = [
|
||||
102 | {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
103 | for _record in data
|
||||
| ^^^^^^^
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
99 |
|
||||
100 | # Should detect in mixed comprehensions
|
||||
101 | result = [
|
||||
- {_key: [_val * 2 for _val in _record["items"]] for _key in ["doubled"]}
|
||||
- for _record in data
|
||||
102 + {_key: [_val * 2 for _val in record["items"]] for _key in ["doubled"]}
|
||||
103 + for record in data
|
||||
104 | ]
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
106 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_item` is accessed
|
||||
--> RUF052_1.py:108:43
|
||||
|
|
||||
107 | # Should detect in generator passed to list constructor
|
||||
108 | gen_list = list(_item["items"][0] for _item in data)
|
||||
| ^^^^^
|
||||
109 | # RUF052: Local dummy variable `_item` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
105 | # RUF052: `_key`, `_val`, and `_record` are all accessed
|
||||
106 |
|
||||
107 | # Should detect in generator passed to list constructor
|
||||
- gen_list = list(_item["items"][0] for _item in data)
|
||||
108 + gen_list = list(item["items"][0] for item in data)
|
||||
109 | # RUF052: Local dummy variable `_item` is accessed
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 [*] Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -24,7 +24,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -45,7 +45,7 @@ help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -67,7 +67,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -90,7 +90,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -112,7 +112,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -123,7 +123,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -133,7 +133,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -154,7 +154,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 [*] Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -184,7 +184,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -213,7 +213,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -242,7 +242,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -270,7 +270,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -297,7 +297,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -323,7 +323,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -347,7 +347,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -371,7 +371,7 @@ help: Remove leading underscores
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 [*] Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -396,7 +396,7 @@ help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -2,7 +2,7 @@
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF052 Local dummy variable `_var` is accessed
|
||||
--> RUF052.py:92:9
|
||||
--> RUF052_0.py:92:9
|
||||
|
|
||||
90 | class Class_:
|
||||
91 | def fun(self):
|
||||
@@ -13,7 +13,7 @@ RUF052 Local dummy variable `_var` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_list` is accessed
|
||||
--> RUF052.py:99:5
|
||||
--> RUF052_0.py:99:5
|
||||
|
|
||||
98 | def fun():
|
||||
99 | _list = "built-in" # [RUF052]
|
||||
@@ -23,7 +23,7 @@ RUF052 Local dummy variable `_list` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a built-in
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:106:5
|
||||
--> RUF052_0.py:106:5
|
||||
|
|
||||
104 | def fun():
|
||||
105 | global x
|
||||
@@ -34,7 +34,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `x` is accessed
|
||||
--> RUF052.py:110:3
|
||||
--> RUF052_0.py:110:3
|
||||
|
|
||||
109 | def foo():
|
||||
110 | x = "outer"
|
||||
@@ -44,7 +44,7 @@ RUF052 Local dummy variable `x` is accessed
|
||||
|
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:113:5
|
||||
--> RUF052_0.py:113:5
|
||||
|
|
||||
111 | def bar():
|
||||
112 | nonlocal x
|
||||
@@ -56,7 +56,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_x` is accessed
|
||||
--> RUF052.py:120:5
|
||||
--> RUF052_0.py:120:5
|
||||
|
|
||||
118 | def fun():
|
||||
119 | x = "local"
|
||||
@@ -67,7 +67,7 @@ RUF052 Local dummy variable `_x` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:128:5
|
||||
--> RUF052_0.py:128:5
|
||||
|
|
||||
127 | def unfixables():
|
||||
128 | _GLOBAL_1 = "foo"
|
||||
@@ -78,7 +78,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:136:5
|
||||
--> RUF052_0.py:136:5
|
||||
|
|
||||
135 | # unfixable because the rename would shadow a local variable
|
||||
136 | _local = "local3" # [RUF052]
|
||||
@@ -88,7 +88,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
--> RUF052.py:140:9
|
||||
--> RUF052_0.py:140:9
|
||||
|
|
||||
139 | def nested():
|
||||
140 | _GLOBAL_1 = "foo"
|
||||
@@ -99,7 +99,7 @@ RUF052 Local dummy variable `_GLOBAL_1` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_local` is accessed
|
||||
--> RUF052.py:145:9
|
||||
--> RUF052_0.py:145:9
|
||||
|
|
||||
144 | # unfixable because the rename would shadow a variable from the outer function
|
||||
145 | _local = "local4"
|
||||
@@ -109,7 +109,7 @@ RUF052 Local dummy variable `_local` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_P` is accessed
|
||||
--> RUF052.py:153:5
|
||||
--> RUF052_0.py:153:5
|
||||
|
|
||||
151 | from collections import namedtuple
|
||||
152 |
|
||||
@@ -121,7 +121,7 @@ RUF052 Local dummy variable `_P` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_T` is accessed
|
||||
--> RUF052.py:154:5
|
||||
--> RUF052_0.py:154:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -132,7 +132,7 @@ RUF052 Local dummy variable `_T` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT` is accessed
|
||||
--> RUF052.py:155:5
|
||||
--> RUF052_0.py:155:5
|
||||
|
|
||||
153 | _P = ParamSpec("_P")
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
@@ -144,7 +144,7 @@ RUF052 Local dummy variable `_NT` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_E` is accessed
|
||||
--> RUF052.py:156:5
|
||||
--> RUF052_0.py:156:5
|
||||
|
|
||||
154 | _T = TypeVar(name="_T", covariant=True, bound=int|str)
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
@@ -156,7 +156,7 @@ RUF052 Local dummy variable `_E` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT2` is accessed
|
||||
--> RUF052.py:157:5
|
||||
--> RUF052_0.py:157:5
|
||||
|
|
||||
155 | _NT = NamedTuple("_NT", [("foo", int)])
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
@@ -168,7 +168,7 @@ RUF052 Local dummy variable `_NT2` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NT3` is accessed
|
||||
--> RUF052.py:158:5
|
||||
--> RUF052_0.py:158:5
|
||||
|
|
||||
156 | _E = Enum("_E", ["a", "b", "c"])
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
@@ -180,7 +180,7 @@ RUF052 Local dummy variable `_NT3` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_DynamicClass` is accessed
|
||||
--> RUF052.py:159:5
|
||||
--> RUF052_0.py:159:5
|
||||
|
|
||||
157 | _NT2 = namedtuple("_NT2", ['x', 'y', 'z'])
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
@@ -191,7 +191,7 @@ RUF052 Local dummy variable `_DynamicClass` is accessed
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `_NotADynamicClass` is accessed
|
||||
--> RUF052.py:160:5
|
||||
--> RUF052_0.py:160:5
|
||||
|
|
||||
158 | _NT3 = namedtuple(typename="_NT3", field_names=['x', 'y', 'z'])
|
||||
159 | _DynamicClass = type("_DynamicClass", (), {})
|
||||
@@ -202,8 +202,18 @@ RUF052 Local dummy variable `_NotADynamicClass` is accessed
|
||||
|
|
||||
help: Remove leading underscores
|
||||
|
||||
RUF052 Local dummy variable `other` is accessed
|
||||
--> RUF052_0.py:177:13
|
||||
|
|
||||
175 | return
|
||||
176 | _seen.add(self)
|
||||
177 | for other in self.connected:
|
||||
| ^^^^^
|
||||
178 | other.recurse(_seen=_seen)
|
||||
|
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:182:5
|
||||
--> RUF052_0.py:182:5
|
||||
|
|
||||
181 | def foo():
|
||||
182 | _dummy_var = 42
|
||||
@@ -214,7 +224,7 @@ RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
help: Prefer using trailing underscores to avoid shadowing a variable
|
||||
|
||||
RUF052 Local dummy variable `_dummy_var` is accessed
|
||||
--> RUF052.py:192:5
|
||||
--> RUF052_0.py:192:5
|
||||
|
|
||||
190 | # Unfixable because both possible candidates for the new name are shadowed
|
||||
191 | # in the scope of one of the references to the variable
|
||||
@@ -773,10 +773,14 @@ pub trait StringFlags: Copy {
|
||||
}
|
||||
|
||||
/// The total length of the string's closer.
|
||||
/// This is always equal to `self.quote_len()`,
|
||||
/// but is provided here for symmetry with the `opener_len()` method.
|
||||
/// This is always equal to `self.quote_len()`, except when the string is unclosed,
|
||||
/// in which case the length is zero.
|
||||
fn closer_len(self) -> TextSize {
|
||||
self.quote_len()
|
||||
if self.is_unclosed() {
|
||||
TextSize::default()
|
||||
} else {
|
||||
self.quote_len()
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any_string_flags(self) -> AnyStringFlags {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# parse_options: {"mode": "ipython"}
|
||||
with (a, ?b)
|
||||
?
|
||||
@@ -0,0 +1,3 @@
|
||||
# parse_options: {"mode": "ipython"}
|
||||
with (a, ?b
|
||||
?
|
||||
@@ -0,0 +1,4 @@
|
||||
# parse_options: {"mode": "ipython"}
|
||||
with a, ?b
|
||||
?
|
||||
x = 1
|
||||
@@ -1115,7 +1115,27 @@ impl RecoveryContextKind {
|
||||
TokenKind::Colon => Some(ListTerminatorKind::ErrorRecovery),
|
||||
_ => None,
|
||||
},
|
||||
WithItemKind::Unparenthesized | WithItemKind::ParenthesizedExpression => p
|
||||
// test_err ipython_help_escape_command_error_recovery_1
|
||||
// # parse_options: {"mode": "ipython"}
|
||||
// with (a, ?b)
|
||||
// ?
|
||||
|
||||
// test_err ipython_help_escape_command_error_recovery_2
|
||||
// # parse_options: {"mode": "ipython"}
|
||||
// with (a, ?b
|
||||
// ?
|
||||
|
||||
// test_err ipython_help_escape_command_error_recovery_3
|
||||
// # parse_options: {"mode": "ipython"}
|
||||
// with a, ?b
|
||||
// ?
|
||||
// x = 1
|
||||
WithItemKind::Unparenthesized => matches!(
|
||||
p.current_token_kind(),
|
||||
TokenKind::Colon | TokenKind::Newline
|
||||
)
|
||||
.then_some(ListTerminatorKind::Regular),
|
||||
WithItemKind::ParenthesizedExpression => p
|
||||
.at(TokenKind::Colon)
|
||||
.then_some(ListTerminatorKind::Regular),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/ipython_help_escape_command_error_recovery_1.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..52,
|
||||
body: [
|
||||
With(
|
||||
StmtWith {
|
||||
node_index: NodeIndex(None),
|
||||
range: 37..49,
|
||||
is_async: false,
|
||||
items: [
|
||||
WithItem {
|
||||
range: 43..44,
|
||||
node_index: NodeIndex(None),
|
||||
context_expr: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 43..44,
|
||||
id: Name("a"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
optional_vars: None,
|
||||
},
|
||||
WithItem {
|
||||
range: 47..48,
|
||||
node_index: NodeIndex(None),
|
||||
context_expr: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 47..48,
|
||||
id: Name("b"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
optional_vars: None,
|
||||
},
|
||||
],
|
||||
body: [],
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
node_index: NodeIndex(None),
|
||||
range: 50..51,
|
||||
kind: Help,
|
||||
value: "",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with (a, ?b)
|
||||
| ^ Syntax Error: Expected `,`, found `?`
|
||||
3 | ?
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with (a, ?b)
|
||||
| ^ Syntax Error: Expected `:`, found newline
|
||||
3 | ?
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with (a, ?b)
|
||||
3 | ?
|
||||
| ^ Syntax Error: Expected an indented block after `with` statement
|
||||
|
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/ipython_help_escape_command_error_recovery_2.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..51,
|
||||
body: [
|
||||
With(
|
||||
StmtWith {
|
||||
node_index: NodeIndex(None),
|
||||
range: 37..51,
|
||||
is_async: false,
|
||||
items: [
|
||||
WithItem {
|
||||
range: 42..51,
|
||||
node_index: NodeIndex(None),
|
||||
context_expr: Tuple(
|
||||
ExprTuple {
|
||||
node_index: NodeIndex(None),
|
||||
range: 42..51,
|
||||
elts: [
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 43..44,
|
||||
id: Name("a"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 47..48,
|
||||
id: Name("b"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
],
|
||||
ctx: Load,
|
||||
parenthesized: true,
|
||||
},
|
||||
),
|
||||
optional_vars: None,
|
||||
},
|
||||
],
|
||||
body: [],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with (a, ?b
|
||||
| ^ Syntax Error: Expected an expression or a ')'
|
||||
3 | ?
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with (a, ?b
|
||||
3 | ?
|
||||
| ^ Syntax Error: Expected `,`, found `?`
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
2 | with (a, ?b
|
||||
3 | ?
|
||||
| ^ Syntax Error: unexpected EOF while parsing
|
||||
|
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/ipython_help_escape_command_error_recovery_3.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..56,
|
||||
body: [
|
||||
With(
|
||||
StmtWith {
|
||||
node_index: NodeIndex(None),
|
||||
range: 37..47,
|
||||
is_async: false,
|
||||
items: [
|
||||
WithItem {
|
||||
range: 42..43,
|
||||
node_index: NodeIndex(None),
|
||||
context_expr: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 42..43,
|
||||
id: Name("a"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
optional_vars: None,
|
||||
},
|
||||
WithItem {
|
||||
range: 46..47,
|
||||
node_index: NodeIndex(None),
|
||||
context_expr: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 46..47,
|
||||
id: Name("b"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
optional_vars: None,
|
||||
},
|
||||
],
|
||||
body: [],
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
node_index: NodeIndex(None),
|
||||
range: 48..49,
|
||||
kind: Help,
|
||||
value: "",
|
||||
},
|
||||
),
|
||||
Assign(
|
||||
StmtAssign {
|
||||
node_index: NodeIndex(None),
|
||||
range: 50..55,
|
||||
targets: [
|
||||
Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 50..51,
|
||||
id: Name("x"),
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 54..55,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with a, ?b
|
||||
| ^ Syntax Error: Expected `,`, found `?`
|
||||
3 | ?
|
||||
4 | x = 1
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with a, ?b
|
||||
| ^ Syntax Error: Expected `:`, found newline
|
||||
3 | ?
|
||||
4 | x = 1
|
||||
|
|
||||
|
||||
|
||||
|
|
||||
1 | # parse_options: {"mode": "ipython"}
|
||||
2 | with a, ?b
|
||||
3 | ?
|
||||
| ^ Syntax Error: Expected an indented block after `with` statement
|
||||
4 | x = 1
|
||||
|
|
||||
@@ -283,24 +283,27 @@ fn to_lsp_diagnostic(
|
||||
range = diagnostic_range.to_range(source_kind.source_code(), index, encoding);
|
||||
}
|
||||
|
||||
let (severity, tags, code) = if let Some(code) = code {
|
||||
let code = code.to_string();
|
||||
(
|
||||
Some(severity(&code)),
|
||||
tags(diagnostic),
|
||||
Some(lsp_types::NumberOrString::String(code)),
|
||||
)
|
||||
let (severity, code) = if let Some(code) = code {
|
||||
(severity(code), code.to_string())
|
||||
} else {
|
||||
(None, None, None)
|
||||
(
|
||||
match diagnostic.severity() {
|
||||
ruff_db::diagnostic::Severity::Info => lsp_types::DiagnosticSeverity::INFORMATION,
|
||||
ruff_db::diagnostic::Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
|
||||
ruff_db::diagnostic::Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
|
||||
ruff_db::diagnostic::Severity::Fatal => lsp_types::DiagnosticSeverity::ERROR,
|
||||
},
|
||||
diagnostic.id().to_string(),
|
||||
)
|
||||
};
|
||||
|
||||
(
|
||||
cell,
|
||||
lsp_types::Diagnostic {
|
||||
range,
|
||||
severity,
|
||||
tags,
|
||||
code,
|
||||
severity: Some(severity),
|
||||
tags: tags(diagnostic),
|
||||
code: Some(lsp_types::NumberOrString::String(code)),
|
||||
code_description: diagnostic.documentation_url().and_then(|url| {
|
||||
Some(lsp_types::CodeDescription {
|
||||
href: lsp_types::Url::parse(url).ok()?,
|
||||
|
||||
@@ -106,6 +106,25 @@ impl TextSize {
|
||||
pub fn checked_sub(self, rhs: TextSize) -> Option<TextSize> {
|
||||
self.raw.checked_sub(rhs.raw).map(|raw| TextSize { raw })
|
||||
}
|
||||
|
||||
/// Saturating addition. Returns maximum `TextSize` if overflow occurred.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_add(self, rhs: TextSize) -> TextSize {
|
||||
TextSize {
|
||||
raw: self.raw.saturating_add(rhs.raw),
|
||||
}
|
||||
}
|
||||
|
||||
/// Saturating subtraction. Returns minimum `TextSize` if overflow
|
||||
/// occurred.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn saturating_sub(self, rhs: TextSize) -> TextSize {
|
||||
TextSize {
|
||||
raw: self.raw.saturating_sub(rhs.raw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for TextSize {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.5"
|
||||
version = "0.14.6"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
222
crates/ty/docs/rules.md
generated
222
crates/ty/docs/rules.md
generated
@@ -39,7 +39,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L121" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L126" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L165" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L170" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ f(int) # error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L191" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L196" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ a = 1
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L216" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L242" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L247" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ class B(A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L307" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ class B(A, A): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L328" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L333" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ def test(): -> "Literal[5]":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L532" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L537" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ class C(A, B): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L556" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L561" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L360" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L365" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -474,7 +474,7 @@ an atypical memory layout.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L610" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L615" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L650" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L655" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ a: int = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1809" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1814" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L672" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L677" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ asyncio.run(main())
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L753" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L758" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -650,7 +650,7 @@ with 1:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L774" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -679,7 +679,7 @@ a: str
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L797" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L802" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -723,7 +723,7 @@ except ZeroDivisionError:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L833" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L838" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -756,7 +756,7 @@ class C[U](Generic[T]): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L577" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L582" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -795,7 +795,7 @@ carol = Person(name="Carol", age=25) # typo!
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L859" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L864" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -830,7 +830,7 @@ def f(t: TypeVar("U")): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L956" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L961" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -858,13 +858,99 @@ class B(metaclass=f): ...
|
||||
|
||||
- [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
|
||||
|
||||
## `invalid-method-override`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-method-override" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1942" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
**What it does**
|
||||
|
||||
Detects method overrides that violate the [Liskov Substitution Principle] ("LSP").
|
||||
|
||||
The LSP states that an instance of a subtype should be substitutable for an instance of its supertype.
|
||||
Applied to Python, this means:
|
||||
1. All argument combinations a superclass method accepts
|
||||
must also be accepted by an overriding subclass method.
|
||||
2. The return type of an overriding subclass method must be a subtype
|
||||
of the return type of the superclass method.
|
||||
|
||||
**Why is this bad?**
|
||||
|
||||
Violating the Liskov Substitution Principle will lead to many of ty's assumptions and
|
||||
inferences being incorrect, which will mean that it will fail to catch many possible
|
||||
type errors in your code.
|
||||
|
||||
**Example**
|
||||
|
||||
```python
|
||||
class Super:
|
||||
def method(self, x) -> int:
|
||||
return 42
|
||||
|
||||
class Sub(Super):
|
||||
# Liskov violation: `str` is not a subtype of `int`,
|
||||
# but the supertype method promises to return an `int`.
|
||||
def method(self, x) -> str: # error: [invalid-override]
|
||||
return "foo"
|
||||
|
||||
def accepts_super(s: Super) -> int:
|
||||
return s.method(x=42)
|
||||
|
||||
accepts_super(Sub()) # The result of this call is a string, but ty will infer
|
||||
# it to be an `int` due to the violation of the Liskov Substitution Principle.
|
||||
|
||||
class Sub2(Super):
|
||||
# Liskov violation: the superclass method can be called with a `x=`
|
||||
# keyword argument, but the subclass method does not accept it.
|
||||
def method(self, y) -> int: # error: [invalid-override]
|
||||
return 42
|
||||
|
||||
accepts_super(Sub2()) # TypeError at runtime: method() got an unexpected keyword argument 'x'
|
||||
# ty cannot catch this error due to the violation of the Liskov Substitution Principle.
|
||||
```
|
||||
|
||||
**Common issues**
|
||||
|
||||
|
||||
**Why does ty complain about my `__eq__` method?**
|
||||
|
||||
|
||||
`__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary
|
||||
objects as their second argument, for example:
|
||||
|
||||
```python
|
||||
class A:
|
||||
x: int
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
# gracefully handle an object of an unexpected type
|
||||
# without raising an exception
|
||||
if not isinstance(other, A):
|
||||
return False
|
||||
return self.x == other.x
|
||||
```
|
||||
|
||||
If `A.__eq__` here were annotated as only accepting `A` instances for its second argument,
|
||||
it would imply that you wouldn't be able to use `==` between instances of `A` and
|
||||
instances of unrelated classes without an exception possibly being raised. While some
|
||||
classes in Python do indeed behave this way, the strongly held convention is that it should
|
||||
be avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__`
|
||||
and `__ne__` methods accept `object` as their second argument.
|
||||
|
||||
[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle
|
||||
|
||||
## `invalid-named-tuple`
|
||||
|
||||
<small>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L506" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L511" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -896,7 +982,7 @@ TypeError: can only inherit from a NamedTuple type and Generic
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L932" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L937" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -926,7 +1012,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L988" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -976,7 +1062,7 @@ def foo(x: int) -> int: ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1082" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1087" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1002,7 +1088,7 @@ def f(a: int = ''): ...
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L887" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L892" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1033,7 +1119,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L442" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L447" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1067,7 +1153,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1102" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1116,7 +1202,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L631" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L636" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1141,7 +1227,7 @@ def func() -> int:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1145" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1150" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1199,7 +1285,7 @@ TODO #14889
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L916" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1226,7 +1312,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1184" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1189" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1256,7 +1342,7 @@ TYPE_CHECKING = ''
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1208" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1213" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1286,7 +1372,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1260" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1265" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1320,7 +1406,7 @@ f(10) # Error
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1232" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1237" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1354,7 +1440,7 @@ class C:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1288" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1389,7 +1475,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1317" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1322" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1414,7 +1500,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1910" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1915" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1447,7 +1533,7 @@ alice["age"] # KeyError
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1336" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1341" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1476,7 +1562,7 @@ func("string") # error: [no-matching-overload]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1359" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1364" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1500,7 +1586,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1377" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1526,7 +1612,7 @@ for i in 34: # TypeError: 'int' object is not iterable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1428" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1553,7 +1639,7 @@ f(1, x=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1663" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1668" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1611,7 +1697,7 @@ def test(): -> "int":
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1785" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1790" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1641,7 +1727,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1519" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1524" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1670,7 +1756,7 @@ class B(A): ... # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1569" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1697,7 +1783,7 @@ f("foo") # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1542" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1547" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1725,7 +1811,7 @@ def _(x: int):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1585" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1590" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1771,7 +1857,7 @@ class A:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1642" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1647" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1798,7 +1884,7 @@ f(x=1, y=2) # Error raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1684" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1689" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1826,7 +1912,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1706" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1711" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1851,7 +1937,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1725" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1730" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1876,7 +1962,7 @@ print(x) # NameError: name 'x' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1397" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1402" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1913,7 +1999,7 @@ b1 < b2 < b1 # exception raised here
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1744" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1941,7 +2027,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1766" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1771" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1966,7 +2052,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L471" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L476" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2007,7 +2093,7 @@ class SubProto(BaseProto, Protocol):
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L286" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L291" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2095,7 +2181,7 @@ a = 20 / 0 # type: ignore
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1449" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1454" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2123,7 +2209,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L139" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2155,7 +2241,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1476" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2187,7 +2273,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1837" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1842" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2214,7 +2300,7 @@ cast(int, f()) # Redundant
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1624" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1629" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2238,7 +2324,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1858" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1863" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2296,7 +2382,7 @@ def g():
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L720" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L725" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2335,7 +2421,7 @@ class D(C): ... # error: [unsupported-base]
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1026" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1031" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2398,7 +2484,7 @@ def foo(x: int | str) -> int | str:
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L268" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L273" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2422,7 +2508,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
|
||||
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
|
||||
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
|
||||
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1497" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1502" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
5 | print(sys.last_exc)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
info: Python 3.11 was assumed when accessing `last_exc`
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.11 was assumed when resolving the `last_exc` attribute
|
||||
--> pyproject.toml:3:18
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
@@ -1179,6 +1180,8 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
import os
|
||||
|
||||
os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
|
||||
from typing import LiteralString # added in Python 3.11
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
@@ -1194,8 +1197,11 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
3 |
|
||||
4 | os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
| ^^^^^^^^^^
|
||||
5 |
|
||||
6 | from typing import LiteralString # added in Python 3.11
|
||||
|
|
||||
info: Python 3.10 was assumed when accessing `grantpt`
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving the `grantpt` attribute
|
||||
--> ty.toml:3:18
|
||||
|
|
||||
2 | [environment]
|
||||
@@ -1205,7 +1211,26 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
error[unresolved-import]: Module `typing` has no member `LiteralString`
|
||||
--> main.py:6:20
|
||||
|
|
||||
4 | os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
5 |
|
||||
6 | from typing import LiteralString # added in Python 3.11
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving imports
|
||||
--> ty.toml:3:18
|
||||
|
|
||||
2 | [environment]
|
||||
3 | python-version = "3.10"
|
||||
| ^^^^^^ Python 3.10 assumed due to this configuration setting
|
||||
4 | python-platform = "linux"
|
||||
|
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 2 diagnostics
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
@@ -1225,6 +1250,8 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
import os
|
||||
|
||||
os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
|
||||
from typing import LiteralString # added in Python 3.11
|
||||
"#,
|
||||
),
|
||||
])?;
|
||||
|
||||
@@ -18,11 +18,11 @@ numpy-array,main.py,1,1
|
||||
object-attr-instance-methods,main.py,0,1
|
||||
object-attr-instance-methods,main.py,1,1
|
||||
pass-keyword-completion,main.py,0,1
|
||||
raise-uses-base-exception,main.py,0,2
|
||||
raise-uses-base-exception,main.py,0,1
|
||||
scope-existing-over-new-import,main.py,0,1
|
||||
scope-prioritize-closer,main.py,0,2
|
||||
scope-simple-long-identifier,main.py,0,1
|
||||
tstring-completions,main.py,0,1
|
||||
ty-extensions-lower-stdlib,main.py,0,8
|
||||
type-var-typing-over-ast,main.py,0,3
|
||||
type-var-typing-over-ast,main.py,1,279
|
||||
type-var-typing-over-ast,main.py,1,278
|
||||
|
||||
|
@@ -8,13 +8,12 @@ use crate::symbols::{QueryPattern, SymbolInfo, symbols_for_file_global_only};
|
||||
///
|
||||
/// Returns symbols from all files in the workspace and dependencies, filtered
|
||||
/// by the query.
|
||||
pub fn all_symbols<'db>(db: &'db dyn Db, query: &str) -> Vec<AllSymbolInfo<'db>> {
|
||||
pub fn all_symbols<'db>(db: &'db dyn Db, query: &QueryPattern) -> Vec<AllSymbolInfo<'db>> {
|
||||
// If the query is empty, return immediately to avoid expensive file scanning
|
||||
if query.is_empty() {
|
||||
if query.will_match_everything() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let query = QueryPattern::new(query);
|
||||
let results = std::sync::Mutex::new(Vec::new());
|
||||
{
|
||||
let modules = all_modules(db);
|
||||
@@ -144,7 +143,7 @@ ABCDEFGHIJKLMNOP = 'https://api.example.com'
|
||||
|
||||
impl CursorTest {
|
||||
fn all_symbols(&self, query: &str) -> String {
|
||||
let symbols = all_symbols(&self.db, query);
|
||||
let symbols = all_symbols(&self.db, &QueryPattern::new(query));
|
||||
|
||||
if symbols.is_empty() {
|
||||
return "No symbols found".to_string();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::types::ide_support::{
|
||||
call_signature_details, definitions_for_keyword_argument,
|
||||
call_signature_details, call_type_simplified_by_overloads, definitions_for_keyword_argument,
|
||||
};
|
||||
use ty_python_semantic::{
|
||||
HasDefinition, HasType, ImportAliasResolution, SemanticModel, definitions_for_imported_symbol,
|
||||
@@ -61,6 +61,7 @@ pub(crate) enum GotoTarget<'a> {
|
||||
/// ```
|
||||
ImportModuleComponent {
|
||||
module_name: String,
|
||||
level: u32,
|
||||
component_index: usize,
|
||||
component_range: TextRange,
|
||||
},
|
||||
@@ -213,6 +214,7 @@ impl<'db> DefinitionsOrTargets<'db> {
|
||||
| ty_python_semantic::types::TypeDefinition::Function(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeVar(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::SpecialForm(definition)
|
||||
| ty_python_semantic::types::TypeDefinition::NewType(definition) => {
|
||||
ResolvedDefinition::Definition(definition)
|
||||
}
|
||||
@@ -301,12 +303,21 @@ impl GotoTarget<'_> {
|
||||
// (i.e. the type of `MyClass` in `MyClass()` is `<class MyClass>` and not `() -> MyClass`)
|
||||
GotoTarget::Call { callable, .. } => callable.inferred_type(model),
|
||||
GotoTarget::TypeParamTypeVarName(typevar) => typevar.inferred_type(model),
|
||||
GotoTarget::ImportModuleComponent {
|
||||
module_name,
|
||||
component_index,
|
||||
level,
|
||||
..
|
||||
} => {
|
||||
// We don't currently support hovering the bare `.` so there is always a name
|
||||
let module = import_name(module_name, *component_index);
|
||||
model.resolve_module_type(Some(module), *level)?
|
||||
}
|
||||
// TODO: Support identifier targets
|
||||
GotoTarget::PatternMatchRest(_)
|
||||
| GotoTarget::PatternKeywordArgument(_)
|
||||
| GotoTarget::PatternMatchStarName(_)
|
||||
| GotoTarget::PatternMatchAsName(_)
|
||||
| GotoTarget::ImportModuleComponent { .. }
|
||||
| GotoTarget::TypeParamParamSpecName(_)
|
||||
| GotoTarget::TypeParamTypeVarTupleName(_)
|
||||
| GotoTarget::NonLocal { .. }
|
||||
@@ -326,6 +337,18 @@ impl GotoTarget<'_> {
|
||||
Some(ty)
|
||||
}
|
||||
|
||||
/// Try to get a simplified display of this callable type by resolving overloads
|
||||
pub(crate) fn call_type_simplified_by_overloads(
|
||||
&self,
|
||||
model: &SemanticModel,
|
||||
) -> Option<String> {
|
||||
if let GotoTarget::Call { call, .. } = self {
|
||||
call_type_simplified_by_overloads(model.db(), model, call)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the definitions for this goto target.
|
||||
///
|
||||
/// The `alias_resolution` parameter controls whether import aliases
|
||||
@@ -340,37 +363,30 @@ impl GotoTarget<'_> {
|
||||
/// as just returning a raw `NavigationTarget`.
|
||||
pub(crate) fn get_definition_targets<'db>(
|
||||
&self,
|
||||
file: ruff_db::files::File,
|
||||
db: &'db dyn crate::Db,
|
||||
model: &SemanticModel<'db>,
|
||||
alias_resolution: ImportAliasResolution,
|
||||
) -> Option<DefinitionsOrTargets<'db>> {
|
||||
use crate::NavigationTarget;
|
||||
let db = model.db();
|
||||
let file = model.file();
|
||||
|
||||
match self {
|
||||
GotoTarget::Expression(expression) => definitions_for_expression(db, file, expression)
|
||||
.map(DefinitionsOrTargets::Definitions),
|
||||
GotoTarget::Expression(expression) => {
|
||||
definitions_for_expression(model, expression).map(DefinitionsOrTargets::Definitions)
|
||||
}
|
||||
|
||||
// For already-defined symbols, they are their own definitions
|
||||
GotoTarget::FunctionDef(function) => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(function.definition(&model)),
|
||||
]))
|
||||
}
|
||||
GotoTarget::FunctionDef(function) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(function.definition(model)),
|
||||
])),
|
||||
|
||||
GotoTarget::ClassDef(class) => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(class.definition(&model)),
|
||||
]))
|
||||
}
|
||||
GotoTarget::ClassDef(class) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(class.definition(model)),
|
||||
])),
|
||||
|
||||
GotoTarget::Parameter(parameter) => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(parameter.definition(&model)),
|
||||
]))
|
||||
}
|
||||
GotoTarget::Parameter(parameter) => Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(parameter.definition(model)),
|
||||
])),
|
||||
|
||||
// For import aliases (offset within 'y' or 'z' in "from x import y as z")
|
||||
GotoTarget::ImportSymbolAlias {
|
||||
@@ -391,24 +407,18 @@ impl GotoTarget<'_> {
|
||||
GotoTarget::ImportModuleComponent {
|
||||
module_name,
|
||||
component_index,
|
||||
level,
|
||||
..
|
||||
} => {
|
||||
// Handle both `import foo.bar` and `from foo.bar import baz` where offset is within module component
|
||||
let components: Vec<&str> = module_name.split('.').collect();
|
||||
|
||||
// Build the module name up to and including the component containing the offset
|
||||
let target_module_name = components[..=*component_index].join(".");
|
||||
|
||||
// Try to resolve the module
|
||||
definitions_for_module(db, &target_module_name)
|
||||
// We don't currently support hovering the bare `.` so there is always a name
|
||||
let module = import_name(module_name, *component_index);
|
||||
definitions_for_module(model, Some(module), *level)
|
||||
}
|
||||
|
||||
// Handle import aliases (offset within 'z' in "import x.y as z")
|
||||
GotoTarget::ImportModuleAlias { alias } => {
|
||||
if alias_resolution == ImportAliasResolution::ResolveAliases {
|
||||
let full_module_name = alias.name.as_str();
|
||||
// Try to resolve the module
|
||||
definitions_for_module(db, full_module_name)
|
||||
definitions_for_module(model, Some(alias.name.as_str()), 0)
|
||||
} else {
|
||||
let alias_range = alias.asname.as_ref().unwrap().range;
|
||||
Some(DefinitionsOrTargets::Targets(
|
||||
@@ -431,9 +441,8 @@ impl GotoTarget<'_> {
|
||||
|
||||
// For exception variables, they are their own definitions (like parameters)
|
||||
GotoTarget::ExceptVariable(except_handler) => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Definition(except_handler.definition(&model)),
|
||||
ResolvedDefinition::Definition(except_handler.definition(model)),
|
||||
]))
|
||||
}
|
||||
|
||||
@@ -465,9 +474,9 @@ impl GotoTarget<'_> {
|
||||
//
|
||||
// Prefer the function impl over the callable so that its docstrings win if defined.
|
||||
GotoTarget::Call { callable, call } => {
|
||||
let mut definitions = definitions_for_callable(db, file, call);
|
||||
let mut definitions = definitions_for_callable(model, call);
|
||||
let expr_definitions =
|
||||
definitions_for_expression(db, file, callable).unwrap_or_default();
|
||||
definitions_for_expression(model, callable).unwrap_or_default();
|
||||
definitions.extend(expr_definitions);
|
||||
|
||||
if definitions.is_empty() {
|
||||
@@ -478,18 +487,15 @@ impl GotoTarget<'_> {
|
||||
}
|
||||
|
||||
GotoTarget::BinOp { expression, .. } => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_bin_op(db, &model, expression)?;
|
||||
ty_python_semantic::definitions_for_bin_op(db, model, expression)?;
|
||||
|
||||
Some(DefinitionsOrTargets::Definitions(definitions))
|
||||
}
|
||||
|
||||
GotoTarget::UnaryOp { expression, .. } => {
|
||||
let model = SemanticModel::new(db, file);
|
||||
let (definitions, _) =
|
||||
ty_python_semantic::definitions_for_unary_op(db, &model, expression)?;
|
||||
ty_python_semantic::definitions_for_unary_op(db, model, expression)?;
|
||||
|
||||
Some(DefinitionsOrTargets::Definitions(definitions))
|
||||
}
|
||||
@@ -619,6 +625,7 @@ impl GotoTarget<'_> {
|
||||
{
|
||||
return Some(GotoTarget::ImportModuleComponent {
|
||||
module_name: full_name.to_string(),
|
||||
level: 0,
|
||||
component_index,
|
||||
component_range,
|
||||
});
|
||||
@@ -659,14 +666,12 @@ impl GotoTarget<'_> {
|
||||
// Handle offset within module name in from import statements
|
||||
if let Some(module_expr) = &from.module {
|
||||
let full_module_name = module_expr.to_string();
|
||||
|
||||
if let Some((component_index, component_range)) = find_module_component(
|
||||
&full_module_name,
|
||||
module_expr.range.start(),
|
||||
offset,
|
||||
) {
|
||||
if let Some((component_index, component_range)) =
|
||||
find_module_component(&full_module_name, module_expr.start(), offset)
|
||||
{
|
||||
return Some(GotoTarget::ImportModuleComponent {
|
||||
module_name: full_module_name,
|
||||
level: from.level,
|
||||
component_index,
|
||||
component_range,
|
||||
});
|
||||
@@ -863,27 +868,26 @@ fn convert_resolved_definitions_to_targets(
|
||||
|
||||
/// Shared helper to get definitions for an expr (that is presumably a name/attr)
|
||||
fn definitions_for_expression<'db>(
|
||||
db: &'db dyn crate::Db,
|
||||
file: ruff_db::files::File,
|
||||
model: &SemanticModel<'db>,
|
||||
expression: &ruff_python_ast::ExprRef<'_>,
|
||||
) -> Option<Vec<ResolvedDefinition<'db>>> {
|
||||
match expression {
|
||||
ast::ExprRef::Name(name) => Some(definitions_for_name(db, file, name)),
|
||||
ast::ExprRef::Name(name) => Some(definitions_for_name(model.db(), model.file(), name)),
|
||||
ast::ExprRef::Attribute(attribute) => Some(ty_python_semantic::definitions_for_attribute(
|
||||
db, file, attribute,
|
||||
model.db(),
|
||||
model.file(),
|
||||
attribute,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn definitions_for_callable<'db>(
|
||||
db: &'db dyn crate::Db,
|
||||
file: ruff_db::files::File,
|
||||
model: &SemanticModel<'db>,
|
||||
call: &ast::ExprCall,
|
||||
) -> Vec<ResolvedDefinition<'db>> {
|
||||
let model = SemanticModel::new(db, file);
|
||||
// Attempt to refine to a specific call
|
||||
let signature_info = call_signature_details(db, &model, call);
|
||||
let signature_info = call_signature_details(model.db(), model, call);
|
||||
signature_info
|
||||
.into_iter()
|
||||
.filter_map(|signature| signature.definition.map(ResolvedDefinition::Definition))
|
||||
@@ -934,7 +938,9 @@ pub(crate) fn find_goto_target(
|
||||
}
|
||||
|
||||
let covering_node = covering_node(parsed.syntax().into(), token.range())
|
||||
.find_first(|node| node.is_identifier() || node.is_expression())
|
||||
.find_first(|node| {
|
||||
node.is_identifier() || node.is_expression() || node.is_stmt_import_from()
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
GotoTarget::from_covering_node(&covering_node, offset, parsed.tokens())
|
||||
@@ -942,21 +948,15 @@ pub(crate) fn find_goto_target(
|
||||
|
||||
/// Helper function to resolve a module name and create a navigation target.
|
||||
fn definitions_for_module<'db>(
|
||||
db: &'db dyn crate::Db,
|
||||
module_name_str: &str,
|
||||
model: &SemanticModel,
|
||||
module: Option<&str>,
|
||||
level: u32,
|
||||
) -> Option<DefinitionsOrTargets<'db>> {
|
||||
use ty_python_semantic::{ModuleName, resolve_module};
|
||||
|
||||
if let Some(module_name) = ModuleName::new(module_name_str) {
|
||||
if let Some(resolved_module) = resolve_module(db, &module_name) {
|
||||
if let Some(module_file) = resolved_module.file(db) {
|
||||
return Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Module(module_file),
|
||||
]));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
let module = model.resolve_module(module, level)?;
|
||||
let file = module.file(model.db())?;
|
||||
Some(DefinitionsOrTargets::Definitions(vec![
|
||||
ResolvedDefinition::Module(file),
|
||||
]))
|
||||
}
|
||||
|
||||
/// Helper function to extract module component information from a dotted module name
|
||||
@@ -970,9 +970,7 @@ fn find_module_component(
|
||||
|
||||
// Split the module name into components and find which one contains the offset
|
||||
let mut current_pos = 0;
|
||||
let components: Vec<&str> = full_module_name.split('.').collect();
|
||||
|
||||
for (i, component) in components.iter().enumerate() {
|
||||
for (i, component) in full_module_name.split('.').enumerate() {
|
||||
let component_start = current_pos;
|
||||
let component_end = current_pos + component.len();
|
||||
|
||||
@@ -991,3 +989,16 @@ fn find_module_component(
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Helper to get the module name up to the given component index
|
||||
fn import_name(module_name: &str, component_index: usize) -> &str {
|
||||
// We want everything to the left of the nth `.`
|
||||
// If there's no nth `.` then we want the whole thing.
|
||||
let idx = module_name
|
||||
.match_indices('.')
|
||||
.nth(component_index)
|
||||
.map(|(idx, _)| idx)
|
||||
.unwrap_or(module_name.len());
|
||||
|
||||
&module_name[..idx]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the declaration of a symbol.
|
||||
///
|
||||
@@ -18,8 +18,9 @@ pub fn goto_declaration(
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let declaration_targets = goto_target
|
||||
.get_definition_targets(file, db, ImportAliasResolution::ResolveAliases)?
|
||||
.get_definition_targets(&model, ImportAliasResolution::ResolveAliases)?
|
||||
.declaration_targets(db)?;
|
||||
|
||||
Some(RangedValue {
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{Db, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Navigate to the definition of a symbol.
|
||||
///
|
||||
@@ -18,9 +18,9 @@ pub fn goto_definition(
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let definition_targets = goto_target
|
||||
.get_definition_targets(file, db, ImportAliasResolution::ResolveAliases)?
|
||||
.get_definition_targets(&model, ImportAliasResolution::ResolveAliases)?
|
||||
.definition_targets(db)?;
|
||||
|
||||
Some(RangedValue {
|
||||
|
||||
@@ -68,6 +68,134 @@ mod tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_typing_dot_literal() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Literal
|
||||
|
||||
a<CURSOR>b = Literal
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/typing.pyi:351:1
|
||||
|
|
||||
349 | Final: _SpecialForm
|
||||
350 |
|
||||
351 | Literal: _SpecialForm
|
||||
| ^^^^^^^
|
||||
352 | TypedDict: _SpecialForm
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import Literal
|
||||
3 |
|
||||
4 | ab = Literal
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
// this is a slightly different case to the one above,
|
||||
// since `Any` is a class in typeshed rather than a variable
|
||||
#[test]
|
||||
fn goto_type_of_typing_dot_any() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Any
|
||||
|
||||
a<CURSOR>b = Any
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/typing.pyi:166:7
|
||||
|
|
||||
164 | # from _typeshed import AnnotationForm
|
||||
165 |
|
||||
166 | class Any:
|
||||
| ^^^
|
||||
167 | """Special type indicating an unconstrained type.
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import Any
|
||||
3 |
|
||||
4 | ab = Any
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
// Similarly, `Generic` is a `type[]` type in typeshed
|
||||
#[test]
|
||||
fn goto_type_of_typing_dot_generic() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from typing import Generic
|
||||
|
||||
a<CURSOR>b = Generic
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/typing.pyi:770:1
|
||||
|
|
||||
768 | def __class_getitem__(cls, args: TypeVar | tuple[TypeVar, ...]) -> _Final: ...
|
||||
769 |
|
||||
770 | Generic: type[_Generic]
|
||||
| ^^^^^^^
|
||||
771 |
|
||||
772 | class _ProtocolMeta(ABCMeta):
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from typing import Generic
|
||||
3 |
|
||||
4 | ab = Generic
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_ty_extensions_special_form() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
from ty_extensions import AlwaysTruthy
|
||||
|
||||
a<CURSOR>b = AlwaysTruthy
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/ty_extensions.pyi:21:1
|
||||
|
|
||||
19 | # Types
|
||||
20 | Unknown = object()
|
||||
21 | AlwaysTruthy = object()
|
||||
| ^^^^^^^^^^^^
|
||||
22 | AlwaysFalsy = object()
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:1
|
||||
|
|
||||
2 | from ty_extensions import AlwaysTruthy
|
||||
3 |
|
||||
4 | ab = AlwaysTruthy
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_expression_with_function_type() {
|
||||
let test = cursor_test(
|
||||
@@ -157,6 +285,300 @@ mod tests {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_import_module() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
import l<CURSOR>ib
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib.py:1:1
|
||||
|
|
||||
1 | a = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:8
|
||||
|
|
||||
2 | import lib
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_import_module_multi1() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
import li<CURSOR>b.submod
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib/__init__.py", "b = 7").unwrap();
|
||||
test.write_file("lib/submod.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/__init__.py:1:1
|
||||
|
|
||||
1 | b = 7
|
||||
| ^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:8
|
||||
|
|
||||
2 | import lib.submod
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_import_module_multi2() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
import lib.subm<CURSOR>od
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib/__init__.py", "b = 7").unwrap();
|
||||
test.write_file("lib/submod.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/submod.py:1:1
|
||||
|
|
||||
1 | a = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:12
|
||||
|
|
||||
2 | import lib.submod
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_module() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
from l<CURSOR>ib import a
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib.py:1:1
|
||||
|
|
||||
1 | a = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | from lib import a
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_module_multi1() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
from li<CURSOR>b.submod import a
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib/__init__.py", "b = 7").unwrap();
|
||||
test.write_file("lib/submod.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/__init__.py:1:1
|
||||
|
|
||||
1 | b = 7
|
||||
| ^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:6
|
||||
|
|
||||
2 | from lib.submod import a
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_module_multi2() {
|
||||
let mut test = cursor_test(
|
||||
r#"
|
||||
from lib.subm<CURSOR>od import a
|
||||
"#,
|
||||
);
|
||||
|
||||
test.write_file("lib/__init__.py", "b = 7").unwrap();
|
||||
test.write_file("lib/submod.py", "a = 10").unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/submod.py:1:1
|
||||
|
|
||||
1 | a = 10
|
||||
| ^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:10
|
||||
|
|
||||
2 | from lib.submod import a
|
||||
| ^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_rel1() {
|
||||
let mut test = CursorTest::builder()
|
||||
.source(
|
||||
"lib/sub/__init__.py",
|
||||
r#"
|
||||
from .bot.bot<CURSOR>mod import *
|
||||
sub = 2
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
test.write_file("lib/__init__.py", "lib = 1").unwrap();
|
||||
// test.write_file("lib/sub/__init__.py", "sub = 2").unwrap();
|
||||
test.write_file("lib/sub/bot/__init__.py", "bot = 3")
|
||||
.unwrap();
|
||||
test.write_file("lib/sub/submod.py", "submod = 21").unwrap();
|
||||
test.write_file("lib/sub/bot/botmod.py", "botmod = 31")
|
||||
.unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/sub/bot/botmod.py:1:1
|
||||
|
|
||||
1 | botmod = 31
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> lib/sub/__init__.py:2:11
|
||||
|
|
||||
2 | from .bot.botmod import *
|
||||
| ^^^^^^
|
||||
3 | sub = 2
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_rel2() {
|
||||
let mut test = CursorTest::builder()
|
||||
.source(
|
||||
"lib/sub/__init__.py",
|
||||
r#"
|
||||
from .bo<CURSOR>t.botmod import *
|
||||
sub = 2
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
test.write_file("lib/__init__.py", "lib = 1").unwrap();
|
||||
// test.write_file("lib/sub/__init__.py", "sub = 2").unwrap();
|
||||
test.write_file("lib/sub/bot/__init__.py", "bot = 3")
|
||||
.unwrap();
|
||||
test.write_file("lib/sub/submod.py", "submod = 21").unwrap();
|
||||
test.write_file("lib/sub/bot/botmod.py", "botmod = 31")
|
||||
.unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/sub/bot/__init__.py:1:1
|
||||
|
|
||||
1 | bot = 3
|
||||
| ^^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> lib/sub/__init__.py:2:7
|
||||
|
|
||||
2 | from .bot.botmod import *
|
||||
| ^^^
|
||||
3 | sub = 2
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_rel3() {
|
||||
let mut test = CursorTest::builder()
|
||||
.source(
|
||||
"lib/sub/__init__.py",
|
||||
r#"
|
||||
from .<CURSOR>bot.botmod import *
|
||||
sub = 2
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
test.write_file("lib/__init__.py", "lib = 1").unwrap();
|
||||
// test.write_file("lib/sub/__init__.py", "sub = 2").unwrap();
|
||||
test.write_file("lib/sub/bot/__init__.py", "bot = 3")
|
||||
.unwrap();
|
||||
test.write_file("lib/sub/submod.py", "submod = 21").unwrap();
|
||||
test.write_file("lib/sub/bot/botmod.py", "botmod = 31")
|
||||
.unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> lib/sub/bot/__init__.py:1:1
|
||||
|
|
||||
1 | bot = 3
|
||||
| ^^^^^^^
|
||||
|
|
||||
info: Source
|
||||
--> lib/sub/__init__.py:2:7
|
||||
|
|
||||
2 | from .bot.botmod import *
|
||||
| ^^^
|
||||
3 | sub = 2
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_from_import_rel4() {
|
||||
let mut test = CursorTest::builder()
|
||||
.source(
|
||||
"lib/sub/__init__.py",
|
||||
r#"
|
||||
from .<CURSOR> import submod
|
||||
sub = 2
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
test.write_file("lib/__init__.py", "lib = 1").unwrap();
|
||||
// test.write_file("lib/sub/__init__.py", "sub = 2").unwrap();
|
||||
test.write_file("lib/sub/bot/__init__.py", "bot = 3")
|
||||
.unwrap();
|
||||
test.write_file("lib/sub/submod.py", "submod = 21").unwrap();
|
||||
test.write_file("lib/sub/bot/botmod.py", "botmod = 31")
|
||||
.unwrap();
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_type_of_expression_with_module() {
|
||||
let mut test = cursor_test(
|
||||
|
||||
@@ -20,19 +20,18 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho
|
||||
}
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let ty = goto_target.inferred_type(&model);
|
||||
let docs = goto_target
|
||||
.get_definition_targets(
|
||||
file,
|
||||
db,
|
||||
&model,
|
||||
ty_python_semantic::ImportAliasResolution::ResolveAliases,
|
||||
)
|
||||
.and_then(|definitions| definitions.docstring(db))
|
||||
.map(HoverContent::Docstring);
|
||||
|
||||
// TODO: Render the symbol's signature instead of just its type.
|
||||
let mut contents = Vec::new();
|
||||
if let Some(ty) = ty {
|
||||
if let Some(signature) = goto_target.call_type_simplified_by_overloads(&model) {
|
||||
contents.push(HoverContent::Signature(signature));
|
||||
} else if let Some(ty) = goto_target.inferred_type(&model) {
|
||||
tracing::debug!("Inferred type of covering node is {}", ty.display(db));
|
||||
contents.push(match ty {
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => typevar
|
||||
@@ -62,7 +61,7 @@ pub struct Hover<'db> {
|
||||
|
||||
impl<'db> Hover<'db> {
|
||||
/// Renders the hover to a string using the specified markup kind.
|
||||
pub const fn display<'a>(&'a self, db: &'a dyn Db, kind: MarkupKind) -> DisplayHover<'a> {
|
||||
pub const fn display<'a>(&'a self, db: &'db dyn Db, kind: MarkupKind) -> DisplayHover<'db, 'a> {
|
||||
DisplayHover {
|
||||
db,
|
||||
hover: self,
|
||||
@@ -93,13 +92,13 @@ impl<'a, 'db> IntoIterator for &'a Hover<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayHover<'a> {
|
||||
db: &'a dyn Db,
|
||||
hover: &'a Hover<'a>,
|
||||
pub struct DisplayHover<'db, 'a> {
|
||||
db: &'db dyn Db,
|
||||
hover: &'a Hover<'db>,
|
||||
kind: MarkupKind,
|
||||
}
|
||||
|
||||
impl fmt::Display for DisplayHover<'_> {
|
||||
impl fmt::Display for DisplayHover<'_, '_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut first = true;
|
||||
for content in &self.hover.contents {
|
||||
@@ -115,8 +114,9 @@ impl fmt::Display for DisplayHover<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HoverContent<'db> {
|
||||
Signature(String),
|
||||
Type(Type<'db>, Option<TypeVarVariance>),
|
||||
Docstring(Docstring),
|
||||
}
|
||||
@@ -140,6 +140,9 @@ pub(crate) struct DisplayHoverContent<'a, 'db> {
|
||||
impl fmt::Display for DisplayHoverContent<'_, '_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.content {
|
||||
HoverContent::Signature(signature) => {
|
||||
self.kind.fenced_code_block(&signature, "python").fmt(f)
|
||||
}
|
||||
HoverContent::Type(ty, variance) => {
|
||||
let variance = match variance {
|
||||
Some(TypeVarVariance::Covariant) => " (covariant)",
|
||||
@@ -241,14 +244,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:11:1
|
||||
@@ -299,14 +299,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:5
|
||||
@@ -365,14 +362,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:24:1
|
||||
@@ -430,14 +424,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:7
|
||||
@@ -493,10 +484,7 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:24:5
|
||||
@@ -552,10 +540,7 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
initializes MyClass (perfectly)
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:11
|
||||
@@ -614,14 +599,11 @@ mod tests {
|
||||
<class 'MyClass'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
This is such a great class!!
|
||||
|
||||
Don't you know?
|
||||
|
||||
Everyone loves my class!!
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:23:5
|
||||
@@ -688,14 +670,11 @@ mod tests {
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
|
||||
```
|
||||
This is such a great func!!
|
||||
|
||||
Args:
|
||||
a: first for a reason
|
||||
b: coming for `a`'s title
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:25:3
|
||||
@@ -961,20 +940,15 @@ def ab(a: str): ...
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(a: int) -> Unknown
|
||||
(a: str) -> Unknown
|
||||
---------------------------------------------
|
||||
the int overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(a: str) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the int overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1025,21 +999,16 @@ def ab(a: str):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r#"
|
||||
(a: int) -> Unknown
|
||||
(a: str) -> Unknown
|
||||
---------------------------------------------
|
||||
the int overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(a: str) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the int overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1094,7 +1063,6 @@ def ab(a: int):
|
||||
a: int,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(a: int) -> Unknown
|
||||
---------------------------------------------
|
||||
the two arg overload
|
||||
|
||||
@@ -1104,13 +1072,9 @@ def ab(a: int):
|
||||
a: int,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(a: int) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the two arg overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1161,27 +1125,16 @@ def ab(a: int):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(
|
||||
a: int,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(a: int) -> Unknown
|
||||
---------------------------------------------
|
||||
the two arg overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(
|
||||
a: int,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(a: int) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
the two arg overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1236,39 +1189,24 @@ def ab(a: int, *, c: int):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
) -> Unknown
|
||||
---------------------------------------------
|
||||
keywordless overload
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
c: int
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
keywordless overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1323,12 +1261,6 @@ def ab(a: int, *, c: int):
|
||||
.build();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
@@ -1339,12 +1271,6 @@ def ab(a: int, *, c: int):
|
||||
|
||||
---------------------------------------------
|
||||
```python
|
||||
(a: int) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
b: int
|
||||
) -> Unknown
|
||||
(
|
||||
a: int,
|
||||
*,
|
||||
@@ -1352,10 +1278,7 @@ def ab(a: int, *, c: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
keywordless overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1420,10 +1343,7 @@ def ab(a: int, *, c: int):
|
||||
) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
The first overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:19:1
|
||||
@@ -1475,10 +1395,7 @@ def ab(a: int, *, c: int):
|
||||
(a: str) -> Unknown
|
||||
```
|
||||
---
|
||||
```text
|
||||
The first overload
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:19:1
|
||||
@@ -1528,12 +1445,9 @@ def ab(a: int, *, c: int):
|
||||
<module 'lib'>
|
||||
```
|
||||
---
|
||||
```text
|
||||
The cool lib_py module!
|
||||
|
||||
The cool lib/_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:4:1
|
||||
@@ -1573,17 +1487,20 @@ def ab(a: int, *, c: int):
|
||||
.unwrap();
|
||||
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
<module 'lib'>
|
||||
---------------------------------------------
|
||||
The cool lib_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
|
||||
---------------------------------------------
|
||||
```text
|
||||
The cool lib_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
|
||||
```python
|
||||
<module 'lib'>
|
||||
```
|
||||
---
|
||||
The cool lib/_py module!
|
||||
|
||||
Wow this module rocks.
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:8
|
||||
@@ -2533,10 +2450,7 @@ def ab(a: int, *, c: int):
|
||||
bound method int.__add__(value: int, /) -> int
|
||||
```
|
||||
---
|
||||
```text
|
||||
Return self+value.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:12
|
||||
@@ -2652,10 +2566,7 @@ def ab(a: int, *, c: int):
|
||||
int | float
|
||||
```
|
||||
---
|
||||
```text
|
||||
Convert a string or number to a floating-point number, if possible.
|
||||
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
--> main.py:2:4
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ use ruff_python_ast::{
|
||||
};
|
||||
use ruff_python_parser::Tokens;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Mode for references search behavior
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -48,8 +48,9 @@ pub(crate) fn references(
|
||||
// Get the definitions for the symbol at the cursor position
|
||||
|
||||
// When finding references, do not resolve any local aliases.
|
||||
let model = SemanticModel::new(db, file);
|
||||
let target_definitions_nav = goto_target
|
||||
.get_definition_targets(file, db, ImportAliasResolution::PreserveAliases)?
|
||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)?
|
||||
.definition_targets(db)?;
|
||||
let target_definitions: Vec<NavigationTarget> = target_definitions_nav.into_iter().collect();
|
||||
|
||||
@@ -289,8 +290,9 @@ impl LocalReferencesFinder<'_> {
|
||||
GotoTarget::from_covering_node(covering_node, offset, self.tokens)
|
||||
{
|
||||
// Get the definitions for this goto target
|
||||
let model = SemanticModel::new(self.db, self.file);
|
||||
if let Some(current_definitions_nav) = goto_target
|
||||
.get_definition_targets(self.file, self.db, ImportAliasResolution::PreserveAliases)
|
||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)
|
||||
.and_then(|definitions| definitions.declaration_targets(self.db))
|
||||
{
|
||||
let current_definitions: Vec<NavigationTarget> =
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::references::{ReferencesMode, references};
|
||||
use crate::{Db, ReferenceTarget};
|
||||
use ruff_db::files::File;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ty_python_semantic::ImportAliasResolution;
|
||||
use ty_python_semantic::{ImportAliasResolution, SemanticModel};
|
||||
|
||||
/// Returns the range of the symbol if it can be renamed, None if not.
|
||||
pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text_size::TextRange> {
|
||||
let parsed = ruff_db::parsed::parsed_module(db, file);
|
||||
let module = parsed.load(db);
|
||||
let model = SemanticModel::new(db, file);
|
||||
|
||||
// Get the definitions for the symbol at the offset
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
@@ -24,7 +25,7 @@ pub fn can_rename(db: &dyn Db, file: File, offset: TextSize) -> Option<ruff_text
|
||||
let current_file_in_project = is_file_in_project(db, file);
|
||||
|
||||
if let Some(definition_targets) = goto_target
|
||||
.get_definition_targets(file, db, ImportAliasResolution::PreserveAliases)
|
||||
.get_definition_targets(&model, ImportAliasResolution::PreserveAliases)
|
||||
.and_then(|definitions| definitions.declaration_targets(db))
|
||||
{
|
||||
for target in &definition_targets {
|
||||
|
||||
@@ -67,6 +67,16 @@ impl QueryPattern {
|
||||
symbol_name.contains(&self.original)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true when it is known that this pattern will return `true` for
|
||||
/// all inputs given to `QueryPattern::is_match_symbol_name`.
|
||||
///
|
||||
/// This will never return `true` incorrectly, but it may return `false`
|
||||
/// incorrectly. That is, it's possible that this query will match all
|
||||
/// inputs but this still returns `false`.
|
||||
pub fn will_match_everything(&self) -> bool {
|
||||
self.re.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for QueryPattern {
|
||||
|
||||
@@ -12,11 +12,8 @@ P = ParamSpec("P")
|
||||
Ts = TypeVarTuple("Ts")
|
||||
R_co = TypeVar("R_co", covariant=True)
|
||||
|
||||
Alias: TypeAlias = int
|
||||
|
||||
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
||||
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
return args
|
||||
|
||||
def g() -> TypeGuard[int]: ...
|
||||
|
||||
@@ -482,17 +482,14 @@ class TD2(TypedDict):
|
||||
x: str
|
||||
|
||||
def f(self, dt: dict[str, Any], key: str):
|
||||
# TODO: This should not error once typed dict assignability is implemented.
|
||||
# error: [invalid-assignment]
|
||||
x1: TD = dt.get(key, {})
|
||||
reveal_type(x1) # revealed: TD
|
||||
reveal_type(x1) # revealed: Any
|
||||
|
||||
x2: TD = dt.get(key, {"x": 0})
|
||||
reveal_type(x2) # revealed: Any
|
||||
|
||||
x3: TD | None = dt.get(key, {})
|
||||
# TODO: This should reveal `Any` once typed dict assignability is implemented.
|
||||
reveal_type(x3) # revealed: Any | None
|
||||
reveal_type(x3) # revealed: Any
|
||||
|
||||
x4: TD | None = dt.get(key, {"x": 0})
|
||||
reveal_type(x4) # revealed: Any
|
||||
@@ -718,3 +715,17 @@ def _(a: int, b: str, c: int | str):
|
||||
x9: int | str | None = f(lst(c))
|
||||
reveal_type(x9) # revealed: int | str | None
|
||||
```
|
||||
|
||||
## Forward annotation with unclosed string literal
|
||||
|
||||
Regression test for [#1611](https://github.com/astral-sh/ty/issues/1611).
|
||||
|
||||
<!-- blacken-docs:off -->
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax]
|
||||
# error: [invalid-syntax-in-forward-annotation]
|
||||
a:'
|
||||
```
|
||||
|
||||
<!-- blacken-docs:on -->
|
||||
|
||||
@@ -1904,6 +1904,7 @@ we only consider the attribute assignment to be valid if the assigned attribute
|
||||
from typing import Literal
|
||||
|
||||
class Date:
|
||||
# error: [invalid-method-override]
|
||||
def __setattr__(self, name: Literal["day", "month", "year"], value: int) -> None:
|
||||
pass
|
||||
|
||||
@@ -2208,9 +2209,9 @@ reveal_type(False.real) # revealed: Literal[0]
|
||||
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
|
||||
|
||||
```py
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[Buffer], /) -> bytes
|
||||
reveal_type(b"foo".join)
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: Buffer | tuple[Buffer, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
|
||||
reveal_type(b"foo".endswith)
|
||||
```
|
||||
|
||||
@@ -2683,6 +2684,39 @@ reveal_type(datetime.UTC) # revealed: Unknown
|
||||
reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Unimported submodule incorrectly accessed as attribute
|
||||
|
||||
We give special diagnostics for this common case too:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`foo/__init__.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`foo/bar.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`baz/bar.py`:
|
||||
|
||||
```py
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import foo
|
||||
import baz
|
||||
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
Some of the tests in the *Class and instance variables* section draw inspiration from
|
||||
|
||||
@@ -313,8 +313,7 @@ reveal_type(A() + "foo") # revealed: A
|
||||
reveal_type("foo" + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + b"foo") # revealed: A
|
||||
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
|
||||
reveal_type(b"foo" + A()) # revealed: bytes
|
||||
reveal_type(b"foo" + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + ()) # revealed: A
|
||||
reveal_type(() + A()) # revealed: A
|
||||
|
||||
@@ -54,10 +54,8 @@ reveal_type(2**largest_u32) # revealed: int
|
||||
|
||||
def variable(x: int):
|
||||
reveal_type(x**2) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(2**x) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(x**x) # revealed: int
|
||||
reveal_type(2**x) # revealed: Any
|
||||
reveal_type(x**x) # revealed: Any
|
||||
```
|
||||
|
||||
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
|
||||
|
||||
@@ -598,9 +598,9 @@ from typing_extensions import Self
|
||||
|
||||
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
reveal_type(int.__new__)
|
||||
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||
reveal_type((42).__new__)
|
||||
|
||||
class X:
|
||||
|
||||
@@ -10,13 +10,13 @@ import pickle
|
||||
|
||||
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(open("", "rb")) # revealed: @Todo(`builtins.open` return type)
|
||||
reveal_type(open("", "rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||
|
||||
with open("foo.pickle", "rb") as f:
|
||||
x = pickle.load(f) # fine
|
||||
|
||||
def _(mode: str):
|
||||
reveal_type(open("", mode)) # revealed: @Todo(`builtins.open` return type)
|
||||
reveal_type(open("", mode)) # revealed: IO[Any]
|
||||
```
|
||||
|
||||
## `os.fdopen`
|
||||
@@ -29,7 +29,7 @@ import os
|
||||
|
||||
reveal_type(os.fdopen(0)) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(os.fdopen(0, "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(os.fdopen(0, "rb")) # revealed: @Todo(`os.fdopen` return type)
|
||||
reveal_type(os.fdopen(0, "rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||
|
||||
with os.fdopen(0, "rb") as f:
|
||||
x = pickle.load(f) # fine
|
||||
@@ -43,9 +43,9 @@ And similarly for `Path.open()`:
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
|
||||
reveal_type(Path("").open()) # revealed: @Todo(`Path.open` return type)
|
||||
reveal_type(Path("").open("r")) # revealed: @Todo(`Path.open` return type)
|
||||
reveal_type(Path("").open("rb")) # revealed: @Todo(`Path.open` return type)
|
||||
reveal_type(Path("").open()) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(Path("").open("r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||
reveal_type(Path("").open("rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||
|
||||
with Path("foo.pickle").open("rb") as f:
|
||||
x = pickle.load(f) # fine
|
||||
@@ -61,7 +61,7 @@ import pickle
|
||||
|
||||
reveal_type(NamedTemporaryFile()) # revealed: _TemporaryFileWrapper[bytes]
|
||||
reveal_type(NamedTemporaryFile("r")) # revealed: _TemporaryFileWrapper[str]
|
||||
reveal_type(NamedTemporaryFile("rb")) # revealed: @Todo(`tempfile.NamedTemporaryFile` return type)
|
||||
reveal_type(NamedTemporaryFile("rb")) # revealed: _TemporaryFileWrapper[bytes]
|
||||
|
||||
with NamedTemporaryFile("rb") as f:
|
||||
x = pickle.load(f) # fine
|
||||
|
||||
@@ -501,8 +501,8 @@ class A[T]:
|
||||
return a
|
||||
|
||||
class B[T](A[T]):
|
||||
def f(self, b: T) -> T:
|
||||
return super().f(b)
|
||||
def f(self, a: T) -> T:
|
||||
return super().f(a)
|
||||
```
|
||||
|
||||
## Invalid Usages
|
||||
|
||||
@@ -24,10 +24,10 @@ class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> EqReturnType:
|
||||
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: A) -> NeReturnType:
|
||||
def __ne__(self, other: A) -> NeReturnType: # error: [invalid-method-override]
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: A) -> LtReturnType:
|
||||
@@ -66,10 +66,10 @@ class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType:
|
||||
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: B) -> NeReturnType:
|
||||
def __ne__(self, other: B) -> NeReturnType: # error: [invalid-method-override]
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: B) -> LtReturnType:
|
||||
@@ -111,10 +111,10 @@ class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: B) -> EqReturnType:
|
||||
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: B) -> NeReturnType:
|
||||
def __ne__(self, other: B) -> NeReturnType: # error: [invalid-method-override]
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: B) -> LtReturnType:
|
||||
@@ -132,12 +132,10 @@ class A:
|
||||
class Unrelated: ...
|
||||
|
||||
class B:
|
||||
# To override builtins.object.__eq__ and builtins.object.__ne__
|
||||
# TODO these should emit an invalid override diagnostic
|
||||
def __eq__(self, other: Unrelated) -> B:
|
||||
def __eq__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
def __ne__(self, other: Unrelated) -> B:
|
||||
def __ne__(self, other: Unrelated) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
|
||||
@@ -180,10 +178,10 @@ class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, other: A) -> A:
|
||||
def __eq__(self, other: A) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: A) -> A:
|
||||
def __ne__(self, other: A) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
|
||||
def __lt__(self, other: A) -> A:
|
||||
@@ -199,22 +197,22 @@ class A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __eq__(self, other: A) -> EqReturnType:
|
||||
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, other: A) -> NeReturnType:
|
||||
def __ne__(self, other: A) -> NeReturnType: # error: [invalid-method-override]
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, other: A) -> LtReturnType:
|
||||
def __lt__(self, other: A) -> LtReturnType: # error: [invalid-method-override]
|
||||
return LtReturnType()
|
||||
|
||||
def __le__(self, other: A) -> LeReturnType:
|
||||
def __le__(self, other: A) -> LeReturnType: # error: [invalid-method-override]
|
||||
return LeReturnType()
|
||||
|
||||
def __gt__(self, other: A) -> GtReturnType:
|
||||
def __gt__(self, other: A) -> GtReturnType: # error: [invalid-method-override]
|
||||
return GtReturnType()
|
||||
|
||||
def __ge__(self, other: A) -> GeReturnType:
|
||||
def __ge__(self, other: A) -> GeReturnType: # error: [invalid-method-override]
|
||||
return GeReturnType()
|
||||
|
||||
reveal_type(A() == B()) # revealed: EqReturnType
|
||||
@@ -243,10 +241,10 @@ class A:
|
||||
return A()
|
||||
|
||||
class B(A):
|
||||
def __lt__(self, other: int) -> B:
|
||||
def __lt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
def __gt__(self, other: int) -> B:
|
||||
def __gt__(self, other: int) -> B: # error: [invalid-method-override]
|
||||
return B()
|
||||
|
||||
reveal_type(A() < B()) # revealed: A
|
||||
@@ -291,11 +289,10 @@ Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#ob
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
# TODO both these overrides should emit invalid-override diagnostic
|
||||
def __eq__(self, other: int) -> A:
|
||||
def __eq__(self, other: int) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
|
||||
def __ne__(self, other: int) -> A:
|
||||
def __ne__(self, other: int) -> A: # error: [invalid-method-override]
|
||||
return A()
|
||||
|
||||
reveal_type(A() == A()) # revealed: bool
|
||||
|
||||
@@ -155,10 +155,10 @@ class GtReturnType: ...
|
||||
class GeReturnType: ...
|
||||
|
||||
class A:
|
||||
def __eq__(self, o: object) -> EqReturnType:
|
||||
def __eq__(self, o: object) -> EqReturnType: # error: [invalid-method-override]
|
||||
return EqReturnType()
|
||||
|
||||
def __ne__(self, o: object) -> NeReturnType:
|
||||
def __ne__(self, o: object) -> NeReturnType: # error: [invalid-method-override]
|
||||
return NeReturnType()
|
||||
|
||||
def __lt__(self, o: A) -> LtReturnType:
|
||||
@@ -386,6 +386,7 @@ class NotBoolable:
|
||||
__bool__: None = None
|
||||
|
||||
class A:
|
||||
# error: [invalid-method-override]
|
||||
def __eq__(self, other) -> NotBoolable:
|
||||
return NotBoolable()
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ import dict_a
|
||||
import dict_b
|
||||
|
||||
def _(b_person: dict_b.Person):
|
||||
# TODO should be error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
|
||||
# error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
|
||||
person_var: dict_a.Person = b_person
|
||||
```
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ x = lambda y: y
|
||||
reveal_type(x.__code__) # revealed: CodeType
|
||||
reveal_type(x.__name__) # revealed: str
|
||||
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
|
||||
reveal_type(x.__annotations__) # revealed: dict[str, @Todo(Support for `typing.TypeAlias`)]
|
||||
reveal_type(x.__annotations__) # revealed: dict[str, Any]
|
||||
reveal_type(x.__dict__) # revealed: dict[str, Any]
|
||||
reveal_type(x.__doc__) # revealed: str | None
|
||||
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None
|
||||
|
||||
@@ -103,8 +103,7 @@ class UnknownLengthSubclassWithDunderLenOverridden(tuple[int, ...]):
|
||||
reveal_type(len(UnknownLengthSubclassWithDunderLenOverridden())) # revealed: Literal[42]
|
||||
|
||||
class FixedLengthSubclassWithDunderLenOverridden(tuple[int]):
|
||||
# TODO: we should complain about this as a Liskov violation (incompatible override)
|
||||
def __len__(self) -> Literal[42]:
|
||||
def __len__(self) -> Literal[42]: # error: [invalid-method-override]
|
||||
return 42
|
||||
|
||||
reveal_type(len(FixedLengthSubclassWithDunderLenOverridden((1,)))) # revealed: Literal[42]
|
||||
|
||||
@@ -137,84 +137,26 @@ class Sub(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def unbounded_unconstrained[T, U](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, T))
|
||||
static_assert(is_assignable_to(T, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, object))
|
||||
static_assert(is_assignable_to(T, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, U))
|
||||
static_assert(is_assignable_to(U, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, object))
|
||||
static_assert(is_assignable_to(U, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, Super))
|
||||
static_assert(not is_assignable_to(U, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, T))
|
||||
static_assert(is_subtype_of(T, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, object))
|
||||
static_assert(is_subtype_of(T, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, U))
|
||||
static_assert(is_subtype_of(U, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, object))
|
||||
static_assert(is_subtype_of(U, object))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, Super))
|
||||
static_assert(not is_subtype_of(U, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -229,137 +171,47 @@ from typing import Any
|
||||
from typing_extensions import final
|
||||
|
||||
def bounded[T: Super](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Sub, T))
|
||||
static_assert(not is_assignable_to(Sub, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Sub, T))
|
||||
static_assert(not is_subtype_of(Sub, T))
|
||||
|
||||
def bounded_by_gradual[T: Any](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Sub, T))
|
||||
static_assert(not is_assignable_to(Sub, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Sub, T))
|
||||
static_assert(not is_subtype_of(Sub, T))
|
||||
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
def bounded_final[T: FinalClass](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, FinalClass))
|
||||
static_assert(is_assignable_to(T, FinalClass))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(FinalClass, T))
|
||||
static_assert(not is_assignable_to(FinalClass, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, FinalClass))
|
||||
static_assert(is_subtype_of(T, FinalClass))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(FinalClass, T))
|
||||
static_assert(not is_subtype_of(FinalClass, T))
|
||||
```
|
||||
|
||||
@@ -370,37 +222,17 @@ typevars to `Never` in addition to that final class.
|
||||
|
||||
```py
|
||||
def two_bounded[T: Super, U: Super](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
|
||||
def two_final_bounded[T: FinalClass, U: FinalClass](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -412,237 +244,67 @@ intersection of all of its constraints is a subtype of the typevar.
|
||||
from ty_extensions import Intersection
|
||||
|
||||
def constrained[T: (Base, Unrelated)](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Base))
|
||||
static_assert(not is_assignable_to(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Unrelated))
|
||||
static_assert(not is_assignable_to(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Unrelated))
|
||||
static_assert(is_assignable_to(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Base | Unrelated))
|
||||
static_assert(is_assignable_to(T, Base | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub | Unrelated))
|
||||
static_assert(not is_assignable_to(T, Sub | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Unrelated, T))
|
||||
static_assert(not is_assignable_to(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Unrelated, T))
|
||||
static_assert(not is_assignable_to(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Base))
|
||||
static_assert(not is_subtype_of(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Unrelated))
|
||||
static_assert(not is_subtype_of(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Super | Unrelated))
|
||||
static_assert(is_subtype_of(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, Base | Unrelated))
|
||||
static_assert(is_subtype_of(T, Base | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub | Unrelated))
|
||||
static_assert(not is_subtype_of(T, Sub | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Unrelated, T))
|
||||
static_assert(not is_subtype_of(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Unrelated, T))
|
||||
static_assert(not is_subtype_of(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
|
||||
def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Base))
|
||||
static_assert(is_assignable_to(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, Unrelated))
|
||||
static_assert(not is_assignable_to(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Any))
|
||||
static_assert(is_assignable_to(T, Super | Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, Super | Unrelated))
|
||||
static_assert(is_assignable_to(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super, T))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Base, T))
|
||||
static_assert(is_assignable_to(Base, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Unrelated, T))
|
||||
static_assert(not is_assignable_to(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Any, T))
|
||||
static_assert(not is_assignable_to(Super | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Base | Any, T))
|
||||
static_assert(is_assignable_to(Base | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(Super | Unrelated, T))
|
||||
static_assert(not is_assignable_to(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[Base, Any], T))
|
||||
static_assert(is_assignable_to(Intersection[Base, Any], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super))
|
||||
static_assert(not is_subtype_of(T, Super))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Base))
|
||||
static_assert(not is_subtype_of(T, Base))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Sub))
|
||||
static_assert(not is_subtype_of(T, Sub))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Unrelated))
|
||||
static_assert(not is_subtype_of(T, Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Any))
|
||||
static_assert(not is_subtype_of(T, Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super | Any))
|
||||
static_assert(not is_subtype_of(T, Super | Any))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, Super | Unrelated))
|
||||
static_assert(not is_subtype_of(T, Super | Unrelated))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Base, T))
|
||||
static_assert(not is_subtype_of(Base, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Unrelated, T))
|
||||
static_assert(not is_subtype_of(Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Any, T))
|
||||
static_assert(not is_subtype_of(Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Any, T))
|
||||
static_assert(not is_subtype_of(Super | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Base | Any, T))
|
||||
static_assert(not is_subtype_of(Base | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Super | Unrelated, T))
|
||||
static_assert(not is_subtype_of(Super | Unrelated, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
static_assert(not is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(Intersection[Base, Any], T))
|
||||
static_assert(not is_subtype_of(Intersection[Base, Any], T))
|
||||
```
|
||||
|
||||
@@ -653,40 +315,20 @@ the same type.
|
||||
|
||||
```py
|
||||
def two_constrained[T: (int, str), U: (int, str)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
|
||||
@final
|
||||
class AnotherFinalClass: ...
|
||||
|
||||
def two_final_constrained[T: (FinalClass, AnotherFinalClass), U: (FinalClass, AnotherFinalClass)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_assignable_to(U, T))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U, T))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
```
|
||||
|
||||
@@ -694,20 +336,10 @@ A bound or constrained typevar is a subtype of itself in a union:
|
||||
|
||||
```py
|
||||
def union[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T, T | None))
|
||||
static_assert(is_assignable_to(T, T | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U, U | None))
|
||||
static_assert(is_assignable_to(U, U | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(T, T | None))
|
||||
static_assert(is_subtype_of(T, T | None))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(U, U | None))
|
||||
static_assert(is_subtype_of(U, U | None))
|
||||
```
|
||||
|
||||
@@ -715,20 +347,10 @@ A bound or constrained typevar in a union with a dynamic type is assignable to t
|
||||
|
||||
```py
|
||||
def union_with_dynamic[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(T | Any, T))
|
||||
static_assert(is_assignable_to(T | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(U | Any, U))
|
||||
static_assert(is_assignable_to(U | Any, U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(T | Any, T))
|
||||
static_assert(not is_subtype_of(T | Any, T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[never]
|
||||
reveal_type(is_subtype_of(U | Any, U))
|
||||
static_assert(not is_subtype_of(U | Any, U))
|
||||
```
|
||||
|
||||
@@ -740,20 +362,9 @@ from ty_extensions import Intersection, Not, is_disjoint_from
|
||||
class A: ...
|
||||
|
||||
def inter[T: Base, U: (Base, Unrelated)](t: T, u: U) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, Unrelated], T))
|
||||
static_assert(is_assignable_to(Intersection[T, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, Unrelated], T))
|
||||
static_assert(is_subtype_of(Intersection[T, Unrelated], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[U, A], U))
|
||||
static_assert(is_assignable_to(Intersection[U, A], U))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[U, A], U))
|
||||
static_assert(is_subtype_of(Intersection[U, A], U))
|
||||
|
||||
static_assert(is_disjoint_from(Not[T], T))
|
||||
@@ -1054,20 +665,10 @@ of) itself.
|
||||
from ty_extensions import is_assignable_to, is_subtype_of, Not, static_assert
|
||||
|
||||
def intersection_is_assignable[T](t: T) -> None:
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, None], T))
|
||||
static_assert(is_assignable_to(Intersection[T, None], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_assignable_to(Intersection[T, Not[None]], T))
|
||||
static_assert(is_assignable_to(Intersection[T, Not[None]], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, None], T))
|
||||
static_assert(is_subtype_of(Intersection[T, None], T))
|
||||
|
||||
# revealed: ty_extensions.ConstraintSet[always]
|
||||
reveal_type(is_subtype_of(Intersection[T, Not[None]], T))
|
||||
static_assert(is_subtype_of(Intersection[T, Not[None]], T))
|
||||
```
|
||||
|
||||
|
||||
@@ -191,13 +191,13 @@ def _(
|
||||
reveal_type(int_or_callable) # revealed: int | ((str, /) -> bytes)
|
||||
reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int
|
||||
# TODO should be Unknown | int
|
||||
reveal_type(type_var_or_int) # revealed: T@_ | int
|
||||
reveal_type(type_var_or_int) # revealed: typing.TypeVar | int
|
||||
# TODO should be int | Unknown
|
||||
reveal_type(int_or_type_var) # revealed: int | T@_
|
||||
reveal_type(int_or_type_var) # revealed: int | typing.TypeVar
|
||||
# TODO should be Unknown | None
|
||||
reveal_type(type_var_or_none) # revealed: T@_ | None
|
||||
reveal_type(type_var_or_none) # revealed: typing.TypeVar | None
|
||||
# TODO should be None | Unknown
|
||||
reveal_type(none_or_type_var) # revealed: None | T@_
|
||||
reveal_type(none_or_type_var) # revealed: None | typing.TypeVar
|
||||
```
|
||||
|
||||
If a type is unioned with itself in a value expression, the result is just that type. No
|
||||
@@ -1109,7 +1109,7 @@ from typing import List, Dict
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
InvalidList = List[1]
|
||||
|
||||
# error: [invalid-type-form] "`typing.typing.List` requires exactly one argument"
|
||||
# error: [invalid-type-form] "`typing.List` requires exactly one argument"
|
||||
ListTooManyArgs = List[int, str]
|
||||
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
@@ -1118,10 +1118,10 @@ InvalidDict1 = Dict[1, str]
|
||||
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
InvalidDict2 = Dict[str, 2]
|
||||
|
||||
# error: [invalid-type-form] "`typing.typing.Dict` requires exactly two arguments, got 1"
|
||||
# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 1"
|
||||
DictTooFewArgs = Dict[str]
|
||||
|
||||
# error: [invalid-type-form] "`typing.typing.Dict` requires exactly two arguments, got 3"
|
||||
# error: [invalid-type-form] "`typing.Dict` requires exactly two arguments, got 3"
|
||||
DictTooManyArgs = Dict[str, int, float]
|
||||
|
||||
def _(
|
||||
|
||||
@@ -60,8 +60,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -90,8 +90,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Absolute `from` Import of Direct Submodule in `__init__`
|
||||
@@ -125,8 +125,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -155,8 +155,8 @@ Y: int = 47
|
||||
import mypackage
|
||||
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Import of Direct Submodule in `__init__`
|
||||
@@ -184,8 +184,8 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have?
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -208,14 +208,14 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Nested Submodule in `__init__`
|
||||
|
||||
`from .submodule import nested` in an `__init__.pyi` does not re-export `mypackage.submodule`,
|
||||
`mypackage.submodule.nested`, or `nested`.
|
||||
`from .submodule import nested` in an `__init__.pyi` does re-export `mypackage.submodule`, but not
|
||||
`mypackage.submodule.nested` or `nested`.
|
||||
|
||||
### In Stub
|
||||
|
||||
@@ -241,15 +241,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -281,10 +280,10 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -318,16 +317,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# TODO: this could work and would be nice to have
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
# error: [unresolved-attribute] "has no member `nested`"
|
||||
reveal_type(mypackage.nested.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -359,10 +356,10 @@ import mypackage
|
||||
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `nested`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
reveal_type(mypackage.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
reveal_type(mypackage.nested.X) # revealed: int
|
||||
```
|
||||
@@ -396,12 +393,14 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -432,12 +431,14 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: Unknown
|
||||
# error: "has no member `submodule`"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested) # revealed: <module 'mypackage.submodule.nested'>
|
||||
# error: [possibly-missing-attribute] "Submodule `submodule` may not be available"
|
||||
# error: [possibly-missing-attribute] "Submodule `nested` may not be available"
|
||||
reveal_type(mypackage.submodule.nested.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative `from` Import of Direct Submodule in `__init__`, Mismatched Alias
|
||||
@@ -463,9 +464,9 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: "has no member `imported_m`"
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
# error: [unresolved-attribute] "has no member `imported_m`"
|
||||
reveal_type(mypackage.imported_m.X) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -489,8 +490,8 @@ X: int = 42
|
||||
import mypackage
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
reveal_type(mypackage.imported_m.X) # revealed: int
|
||||
```
|
||||
|
||||
@@ -569,7 +570,7 @@ X: int = 42
|
||||
from mypackage import *
|
||||
|
||||
# TODO: this would be nice to support
|
||||
# error: "`imported` used when not defined"
|
||||
# error: [unresolved-reference] "`imported` used when not defined"
|
||||
reveal_type(imported.X) # revealed: Unknown
|
||||
reveal_type(Z) # revealed: int
|
||||
```
|
||||
@@ -623,8 +624,7 @@ X: int = 42
|
||||
```py
|
||||
import mypackage
|
||||
|
||||
# error: "no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -673,11 +673,12 @@ X: int = 42
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
|
||||
# TODO: this would be nice to support, but it's dangerous with available_submodule_attributes
|
||||
# for details, see: https://github.com/astral-sh/ty/issues/1488
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -699,10 +700,11 @@ X: int = 42
|
||||
import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `imported`"
|
||||
reveal_type(mypackage.imported.X) # revealed: Unknown
|
||||
|
||||
# TODO: this would be nice to support, as it works at runtime
|
||||
# error: [possibly-missing-attribute] "Submodule `imported` may not be available"
|
||||
reveal_type(mypackage.imported.X) # revealed: int
|
||||
```
|
||||
|
||||
## `from` Import of Sibling Module
|
||||
@@ -737,10 +739,10 @@ import mypackage
|
||||
from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
# error: [unresolved-attribute] "has no member `fails`"
|
||||
reveal_type(imported.fails.Y) # revealed: Unknown
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails` may not be available"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
### In Non-Stub
|
||||
@@ -772,8 +774,8 @@ from mypackage import imported
|
||||
|
||||
reveal_type(imported.X) # revealed: int
|
||||
reveal_type(imported.fails.Y) # revealed: int
|
||||
# error: "has no member `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute] "Submodule `fails`"
|
||||
reveal_type(mypackage.fails.Y) # revealed: int
|
||||
```
|
||||
|
||||
## Fractal Re-export Nameclash Problems
|
||||
|
||||
@@ -247,8 +247,8 @@ X: int = 42
|
||||
from . import foo
|
||||
import package
|
||||
|
||||
# error: [unresolved-attribute] "Module `package` has no member `foo`"
|
||||
reveal_type(package.foo.X) # revealed: Unknown
|
||||
# error: [possibly-missing-attribute]
|
||||
reveal_type(package.foo.X) # revealed: int
|
||||
```
|
||||
|
||||
## Relative imports at the top of a search path
|
||||
|
||||
525
crates/ty_python_semantic/resources/mdtest/liskov.md
Normal file
525
crates/ty_python_semantic/resources/mdtest/liskov.md
Normal file
@@ -0,0 +1,525 @@
|
||||
# The Liskov Substitution Principle
|
||||
|
||||
The Liskov Substitution Principle provides the basis for many of the assumptions a type checker
|
||||
generally makes about types in Python:
|
||||
|
||||
> Subtype Requirement: Let `ϕ(x)` be a property provable about objects `x` of type `T`. Then
|
||||
> `ϕ(y)` should be true for objects `y` of type `S` where `S` is a subtype of `T`.
|
||||
|
||||
In order for a type checker's assumptions to be sound, it is crucial for the type checker to enforce
|
||||
the Liskov Substitution Principle on code that it checks. In practice, this usually manifests as
|
||||
several checks for a type checker to perform when it checks a subclass `B` of a class `A`:
|
||||
|
||||
1. Read-only attributes should only ever be overridden covariantly: if a property `A.p` resolves to
|
||||
`int` when accessed, accessing `B.p` should either resolve to `int` or a subtype of `int`.
|
||||
1. Method return types should only ever be overridden covariantly: if a method `A.f` returns `int`
|
||||
when called, calling `B.f` should also resolve to `int or a subtype of`int\`.
|
||||
1. Method parameters should only ever be overridden contravariantly: if a method `A.f` can be called
|
||||
with an argument of type `bool`, then the method `B.f` must also be callable with type `bool`
|
||||
(though it is permitted for the override to also accept other types)
|
||||
1. Mutable attributes should only ever be overridden invariantly: if a mutable attribute `A.attr`
|
||||
resolves to type `str`, it can only be overridden on a subclass with exactly the same type.
|
||||
|
||||
## Method return types
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
class Super:
|
||||
def method(self) -> int: ...
|
||||
|
||||
class Sub1(Super):
|
||||
def method(self) -> int: ... # fine
|
||||
|
||||
class Sub2(Super):
|
||||
def method(self) -> bool: ... # fine: `bool` is a subtype of `int`
|
||||
|
||||
class Sub3(Super):
|
||||
def method(self) -> object: ... # error: [invalid-method-override]
|
||||
|
||||
class Sub4(Super):
|
||||
def method(self) -> str: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## Method parameters
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
class Super:
|
||||
def method(self, x: int, /): ...
|
||||
|
||||
class Sub1(Super):
|
||||
def method(self, x: int, /): ... # fine
|
||||
|
||||
class Sub2(Super):
|
||||
def method(self, x: object, /): ... # fine: `method` still accepts any argument of type `int`
|
||||
|
||||
class Sub4(Super):
|
||||
def method(self, x: int | str, /): ... # fine
|
||||
|
||||
class Sub5(Super):
|
||||
def method(self, x: int): ... # fine: `x` can still be passed positionally
|
||||
|
||||
class Sub6(Super):
|
||||
# fine: `method()` can still be called with just a single argument
|
||||
def method(self, x: int, *args): ...
|
||||
|
||||
class Sub7(Super):
|
||||
def method(self, x: int, **kwargs): ... # fine
|
||||
|
||||
class Sub8(Super):
|
||||
def method(self, x: int, *args, **kwargs): ... # fine
|
||||
|
||||
class Sub9(Super):
|
||||
def method(self, x: int, extra_positional_arg=42, /): ... # fine
|
||||
|
||||
class Sub10(Super):
|
||||
def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine
|
||||
|
||||
class Sub11(Super):
|
||||
def method(self, x: int, *, extra_kw_only_arg=42): ... # fine
|
||||
|
||||
class Sub12(Super):
|
||||
# Some calls permitted by the superclass are now no longer allowed
|
||||
# (the method can no longer be passed any arguments!)
|
||||
def method(self, /): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub13(Super):
|
||||
# Some calls permitted by the superclass are now no longer allowed
|
||||
# (the method can no longer be passed exactly one argument!)
|
||||
def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub14(Super):
|
||||
# Some calls permitted by the superclass are now no longer allowed
|
||||
# (x can no longer be passed positionally!)
|
||||
def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub15(Super):
|
||||
# Some calls permitted by the superclass are now no longer allowed
|
||||
# (x can no longer be passed any integer -- it now requires a bool!)
|
||||
def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||
|
||||
class Super2:
|
||||
def method2(self, x): ...
|
||||
|
||||
class Sub16(Super2):
|
||||
def method2(self, x, /): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub17(Super2):
|
||||
def method2(self, *, x): ... # error: [invalid-method-override]
|
||||
|
||||
class Super3:
|
||||
def method3(self, *, x): ...
|
||||
|
||||
class Sub18(Super3):
|
||||
def method3(self, x): ... # fine: `x` can still be used as a keyword argument
|
||||
|
||||
class Sub19(Super3):
|
||||
def method3(self, x, /): ... # error: [invalid-method-override]
|
||||
|
||||
class Super4:
|
||||
def method(self, *args: int, **kwargs: str): ...
|
||||
|
||||
class Sub20(Super4):
|
||||
def method(self, *args: object, **kwargs: object): ... # fine
|
||||
|
||||
class Sub21(Super4):
|
||||
def method(self, *args): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub22(Super4):
|
||||
def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||
|
||||
class Sub23(Super4):
|
||||
def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## The entire class hierarchy is checked
|
||||
|
||||
If a child class's method definition is Liskov-compatible with the method definition on its parent
|
||||
class, Liskov compatibility must also nonetheless be checked with respect to the method definition
|
||||
on its grandparent class. This is because type checkers will treat the child class as a subtype of
|
||||
the grandparent class just as much as they treat it as a subtype of the parent class, so
|
||||
substitutability with respect to the grandparent class is just as important:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`stub.pyi`:
|
||||
|
||||
```pyi
|
||||
from typing import Any
|
||||
|
||||
class Grandparent:
|
||||
def method(self, x: int) -> None: ...
|
||||
|
||||
class Parent(Grandparent):
|
||||
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
|
||||
class Child(Parent):
|
||||
# compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
|
||||
class OtherChild(Parent):
|
||||
# compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||
def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||
|
||||
class GradualParent(Grandparent):
|
||||
def method(self, x: Any) -> None: ...
|
||||
|
||||
class ThirdChild(GradualParent):
|
||||
# `GradualParent.method` is compatible with the signature of `Grandparent.method`,
|
||||
# and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||
# but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||
def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
`other_stub.pyi`:
|
||||
|
||||
```pyi
|
||||
class A:
|
||||
def get(self, default): ...
|
||||
|
||||
class B(A):
|
||||
def get(self, default, /): ... # error: [invalid-method-override]
|
||||
|
||||
get = 56
|
||||
|
||||
class C(B):
|
||||
# `get` appears in the symbol table of `C`,
|
||||
# but that doesn't confuse our diagnostic...
|
||||
foo = get
|
||||
|
||||
class D(C):
|
||||
# compatible with `C.get` and `B.get`, but not with `A.get`
|
||||
def get(self, my_default): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## Non-generic methods on generic classes work as expected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```pyi
|
||||
class A[T]:
|
||||
def method(self, x: T) -> None: ...
|
||||
|
||||
class B[T](A[T]):
|
||||
def method(self, x: T) -> None: ... # fine
|
||||
|
||||
class C(A[int]):
|
||||
def method(self, x: int) -> None: ... # fine
|
||||
|
||||
class D[T](A[T]):
|
||||
def method(self, x: object) -> None: ... # fine
|
||||
|
||||
class E(A[int]):
|
||||
def method(self, x: object) -> None: ... # fine
|
||||
|
||||
class F[T](A[T]):
|
||||
# TODO: we should emit `invalid-method-override` on this:
|
||||
# `str` is not necessarily a supertype of `T`!
|
||||
def method(self, x: str) -> None: ...
|
||||
|
||||
class G(A[int]):
|
||||
def method(self, x: bool) -> None: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## Generic methods on non-generic classes work as expected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```pyi
|
||||
from typing import Never, Self
|
||||
|
||||
class A:
|
||||
def method[T](self, x: T) -> T: ...
|
||||
|
||||
class B(A):
|
||||
def method[T](self, x: T) -> T: ... # fine
|
||||
|
||||
class C(A):
|
||||
def method(self, x: object) -> Never: ... # fine
|
||||
|
||||
class D(A):
|
||||
# TODO: we should emit [invalid-method-override] here:
|
||||
# `A.method` accepts an argument of any type,
|
||||
# but `D.method` only accepts `int`s
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
class A2:
|
||||
def method(self, x: int) -> int: ...
|
||||
|
||||
class B2(A2):
|
||||
# fine: although `B2.method()` will not always return an `int`,
|
||||
# an instance of `B2` can be substituted wherever an instance of `A2` is expected,
|
||||
# and it *will* always return an `int` if it is passed an `int`
|
||||
# (which is all that will be allowed if an instance of `A2` is expected)
|
||||
def method[T](self, x: T) -> T: ...
|
||||
|
||||
class C2(A2):
|
||||
def method[T: int](self, x: T) -> T: ...
|
||||
|
||||
class D2(A2):
|
||||
# The type variable is bound to a type disjoint from `int`,
|
||||
# so the method will not accept integers, and therefore this is an invalid override
|
||||
def method[T: str](self, x: T) -> T: ... # error: [invalid-method-override]
|
||||
|
||||
class A3:
|
||||
def method(self) -> Self: ...
|
||||
|
||||
class B3(A3):
|
||||
def method(self) -> Self: ... # fine
|
||||
|
||||
class C3(A3):
|
||||
# TODO: should this be allowed?
|
||||
# Mypy/pyright/pyrefly all allow it,
|
||||
# but conceptually it seems similar to `B4.method` below,
|
||||
# which mypy/pyrefly agree is a Liskov violation
|
||||
# (pyright disagrees as of 20/11/2025: https://github.com/microsoft/pyright/issues/11128)
|
||||
# when called on a subclass, `C3.method()` will not return an
|
||||
# instance of that subclass
|
||||
def method(self) -> C3: ...
|
||||
|
||||
class D3(A3):
|
||||
def method(self: Self) -> Self: ... # fine
|
||||
|
||||
class E3(A3):
|
||||
def method(self: E3) -> Self: ... # fine
|
||||
|
||||
class F3(A3):
|
||||
def method(self: A3) -> Self: ... # fine
|
||||
|
||||
class G3(A3):
|
||||
def method(self: object) -> Self: ... # fine
|
||||
|
||||
class H3(A3):
|
||||
# TODO: we should emit `invalid-method-override` here
|
||||
# (`A3.method()` can be called on any instance of `A3`,
|
||||
# but `H3.method()` can only be called on objects that are
|
||||
# instances of `str`)
|
||||
def method(self: str) -> Self: ...
|
||||
|
||||
class I3(A3):
|
||||
# TODO: we should emit `invalid-method-override` here
|
||||
# (`I3.method()` cannot be called with any inhabited type!)
|
||||
def method(self: Never) -> Self: ...
|
||||
|
||||
class A4:
|
||||
def method[T: int](self, x: T) -> T: ...
|
||||
|
||||
class B4(A4):
|
||||
# TODO: we should emit `invalid-method-override` here.
|
||||
# `A4.method` promises that if it is passed a `bool`, it will return a `bool`,
|
||||
# but this is not necessarily true for `B4.method`: if passed a `bool`,
|
||||
# it could return a non-`bool` `int`!
|
||||
def method(self, x: int) -> int: ...
|
||||
```
|
||||
|
||||
## Generic methods on generic classes work as expected
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```pyi
|
||||
from typing import Never
|
||||
|
||||
class A[T]:
|
||||
def method[S](self, x: T, y: S) -> S: ...
|
||||
|
||||
class B[T](A[T]):
|
||||
def method[S](self, x: T, y: S) -> S: ... # fine
|
||||
|
||||
class C(A[int]):
|
||||
def method[S](self, x: int, y: S) -> S: ... # fine
|
||||
|
||||
class D[T](A[T]):
|
||||
def method[S](self, x: object, y: S) -> S: ... # fine
|
||||
|
||||
class E(A[int]):
|
||||
def method[S](self, x: object, y: S) -> S: ... # fine
|
||||
|
||||
class F(A[int]):
|
||||
def method(self, x: object, y: object) -> Never: ... # fine
|
||||
|
||||
class A2[T]:
|
||||
def method(self, x: T, y: int) -> int: ...
|
||||
|
||||
class B2[T](A2[T]):
|
||||
def method[S](self, x: T, y: S) -> S: ... # fine
|
||||
```
|
||||
|
||||
## Fully qualified names are used in diagnostics where appropriate
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`a.pyi`:
|
||||
|
||||
```pyi
|
||||
class A:
|
||||
def foo(self, x): ...
|
||||
```
|
||||
|
||||
`b.pyi`:
|
||||
|
||||
```pyi
|
||||
import a
|
||||
|
||||
class A(a.A):
|
||||
def foo(self, y): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## Excluded methods
|
||||
|
||||
Certain special constructor methods are excluded from Liskov checks. None of the following classes
|
||||
cause us to emit any errors, therefore:
|
||||
|
||||
```toml
|
||||
# This is so that the dataclasses machinery will generate `__replace__` methods for us
|
||||
# (the synthesized `__replace__` methods should not be reported as invalid overrides!)
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```pyi
|
||||
from dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
class Grandparent: ...
|
||||
class Parent(Grandparent):
|
||||
def __new__(cls, x: int) -> Self: ...
|
||||
def __init__(self, x: int) -> None: ...
|
||||
|
||||
class Child(Parent):
|
||||
def __new__(cls, x: str, y: str) -> Self: ...
|
||||
def __init__(self, x: str, y: str) -> Self: ...
|
||||
|
||||
@dataclass(init=False)
|
||||
class DataSuper:
|
||||
x: int
|
||||
|
||||
def __post_init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
@dataclass(init=False)
|
||||
class DataSub(DataSuper):
|
||||
y: str
|
||||
|
||||
def __post_init__(self, x: int, y: str) -> None:
|
||||
self.y = y
|
||||
super().__post_init__(x)
|
||||
```
|
||||
|
||||
## Edge case: function defined in another module and then assigned in a class body
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
`foo.pyi`:
|
||||
|
||||
```pyi
|
||||
def x(self, y: str): ...
|
||||
```
|
||||
|
||||
`bar.pyi`:
|
||||
|
||||
```pyi
|
||||
import foo
|
||||
|
||||
class A:
|
||||
def x(self, y: int): ...
|
||||
|
||||
class B(A):
|
||||
x = foo.x # error: [invalid-method-override]
|
||||
|
||||
class C:
|
||||
x = foo.x
|
||||
|
||||
class D(C):
|
||||
def x(self, y: int): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## Bad override of `__eq__`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```py
|
||||
class Bad:
|
||||
x: int
|
||||
def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||
return self.x == other.x
|
||||
```
|
||||
|
||||
## Synthesized methods
|
||||
|
||||
`NamedTuple` classes and dataclasses both have methods generated at runtime that do not have
|
||||
source-code definitions. There are several scenarios to consider here:
|
||||
|
||||
1. A synthesized method on a superclass is overridden by a "normal" (not synthesized) method on a
|
||||
subclass
|
||||
1. A "normal" method on a superclass is overridden by a synthesized method on a subclass
|
||||
1. A synthesized method on a superclass is overridden by a synthesized method on a subclass
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```pyi
|
||||
from dataclasses import dataclass
|
||||
from typing import NamedTuple
|
||||
|
||||
@dataclass(order=True)
|
||||
class Foo:
|
||||
x: int
|
||||
|
||||
class Bar(Foo):
|
||||
def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||
|
||||
# TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||
# generated that is incompatible with the generated `__lt__` method on the superclass.
|
||||
# We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
|
||||
# be `invalid-method-override` since we'd emit it on the class definition rather than
|
||||
# on any method definition. Note also that no other type checker complains about this
|
||||
# as of 2025-11-21.
|
||||
@dataclass(order=True)
|
||||
class Bar2(Foo):
|
||||
y: str
|
||||
|
||||
# TODO: Although this class does not override any methods of `Foo`, the design of the
|
||||
# `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
|
||||
# Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
|
||||
# expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
|
||||
# and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
|
||||
# be compared with instances of subclasses of `Foo`).
|
||||
#
|
||||
# Many users would probably like their type checkers to alert them to cases where instances
|
||||
# of subclasses cannot be substituted for instances of superclasses, as this violates many
|
||||
# assumptions a type checker will make and makes it likely that a type checker will fail to
|
||||
# catch type errors elsewhere in the user's code. We could therefore consider treating all
|
||||
# `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
|
||||
# this probably shouldn't be reported with the same error code as Liskov violations, since
|
||||
# the error does not stem from any method signatures written by the user. The example is
|
||||
# only included here for completeness.
|
||||
#
|
||||
# Note that no other type checker catches this error as of 2025-11-21.
|
||||
class Bar3(Foo): ...
|
||||
|
||||
class Eggs:
|
||||
def __lt__(self, other: Eggs) -> bool: ...
|
||||
|
||||
# TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
|
||||
# We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
|
||||
# diagnostic here but pyright and pyrefly do not.
|
||||
@dataclass(order=True)
|
||||
class Ham(Eggs):
|
||||
x: int
|
||||
|
||||
class Baz(NamedTuple):
|
||||
x: int
|
||||
|
||||
class Spam(Baz):
|
||||
def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||
```
|
||||
@@ -335,6 +335,9 @@ reveal_type(x19) # revealed: list[Literal[1]]
|
||||
x20: list[Literal[1]] | None = [1]
|
||||
reveal_type(x20) # revealed: list[Literal[1]]
|
||||
|
||||
x21: X[Literal[1]] | None = x(1)
|
||||
x21: X[Literal[1]] | None = X(1)
|
||||
reveal_type(x21) # revealed: X[Literal[1]]
|
||||
|
||||
x22: X[Literal[1]] | None = x(1)
|
||||
reveal_type(x22) # revealed: X[Literal[1]]
|
||||
```
|
||||
|
||||
@@ -147,6 +147,57 @@ def _(x: int | str | bytes):
|
||||
reveal_type(x) # revealed: (int & Unknown) | (str & Unknown) | (bytes & Unknown)
|
||||
```
|
||||
|
||||
## `classinfo` is a `types.UnionType`
|
||||
|
||||
Python 3.10 added the ability to use `Union[int, str]` as the second argument to `isinstance()`:
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
IntOrStr = Union[int, str]
|
||||
|
||||
reveal_type(IntOrStr) # revealed: types.UnionType
|
||||
|
||||
def _(x: int | str | bytes | memoryview | range):
|
||||
if isinstance(x, IntOrStr):
|
||||
reveal_type(x) # revealed: int | str
|
||||
elif isinstance(x, Union[bytes, memoryview]):
|
||||
reveal_type(x) # revealed: bytes | memoryview[int]
|
||||
else:
|
||||
reveal_type(x) # revealed: range
|
||||
|
||||
def _(x: int | str | None):
|
||||
if isinstance(x, Union[int, None]):
|
||||
reveal_type(x) # revealed: int | None
|
||||
else:
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
ListStrOrInt = Union[list[str], int]
|
||||
|
||||
def _(x: dict[int, str] | ListStrOrInt):
|
||||
# TODO: this should ideally be an error
|
||||
if isinstance(x, ListStrOrInt):
|
||||
# TODO: this should not be narrowed
|
||||
reveal_type(x) # revealed: list[str] | int
|
||||
|
||||
# TODO: this should ideally be an error
|
||||
if isinstance(x, Union[list[str], int]):
|
||||
# TODO: this should not be narrowed
|
||||
reveal_type(x) # revealed: list[str] | int
|
||||
```
|
||||
|
||||
## `Optional` as `classinfo`
|
||||
|
||||
```py
|
||||
from typing import Optional
|
||||
|
||||
def _(x: int | str | None):
|
||||
if isinstance(x, Optional[int]):
|
||||
reveal_type(x) # revealed: int | None
|
||||
else:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
## `classinfo` is a `typing.py` special form
|
||||
|
||||
Certain special forms in `typing.py` are aliases to classes elsewhere in the standard library; these
|
||||
@@ -262,6 +313,23 @@ def _(flag: bool):
|
||||
reveal_type(x) # revealed: Literal[1, "a"]
|
||||
```
|
||||
|
||||
## Generic aliases are not supported as second argument
|
||||
|
||||
The `classinfo` argument cannot be a generic alias:
|
||||
|
||||
```py
|
||||
def _(x: list[str] | list[int] | list[bytes]):
|
||||
# TODO: Ideally, this would be an error (requires https://github.com/astral-sh/ty/issues/116)
|
||||
if isinstance(x, list[int]):
|
||||
# No narrowing here:
|
||||
reveal_type(x) # revealed: list[str] | list[int] | list[bytes]
|
||||
|
||||
# error: [invalid-argument-type] "Invalid second argument to `isinstance`"
|
||||
if isinstance(x, list[int] | list[str]):
|
||||
# No narrowing here:
|
||||
reveal_type(x) # revealed: list[str] | list[int] | list[bytes]
|
||||
```
|
||||
|
||||
## `type[]` types are narrowed as well as class-literal types
|
||||
|
||||
```py
|
||||
|
||||
@@ -200,6 +200,26 @@ def _(x: type[int | str | bytes]):
|
||||
reveal_type(x) # revealed: (type[int] & Unknown) | (type[str] & Unknown) | (type[bytes] & Unknown)
|
||||
```
|
||||
|
||||
## `classinfo` is a `types.UnionType`
|
||||
|
||||
Python 3.10 added the ability to use `Union[int, str]` as the second argument to `issubclass()`:
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
IntOrStr = Union[int, str]
|
||||
|
||||
reveal_type(IntOrStr) # revealed: types.UnionType
|
||||
|
||||
def f(x: type[int | str | bytes | range]):
|
||||
if issubclass(x, IntOrStr):
|
||||
reveal_type(x) # revealed: type[int] | type[str]
|
||||
elif issubclass(x, Union[bytes, memoryview]):
|
||||
reveal_type(x) # revealed: type[bytes]
|
||||
else:
|
||||
reveal_type(x) # revealed: <class 'range'>
|
||||
```
|
||||
|
||||
## Special cases
|
||||
|
||||
### Emit a diagnostic if the first argument is of wrong type
|
||||
|
||||
@@ -1,8 +1,147 @@
|
||||
# PEP 613 type aliases
|
||||
|
||||
## No panics
|
||||
PEP 613 type aliases are simple assignment statements, annotated with `typing.TypeAlias` to mark
|
||||
them as a type alias. At runtime, they behave the same as implicit type aliases. Our support for
|
||||
them is currently the same as for implicit type aliases, but we don't reproduce the full
|
||||
implicit-type-alias test suite here, just some particularly interesting cases.
|
||||
|
||||
We do not fully support PEP 613 type aliases yet. For now, just make sure that we don't panic:
|
||||
## Basic
|
||||
|
||||
### as `TypeAlias`
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
IntOrStr: TypeAlias = int | str
|
||||
|
||||
def _(x: IntOrStr):
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
### as `typing.TypeAlias`
|
||||
|
||||
```py
|
||||
import typing
|
||||
|
||||
IntOrStr: typing.TypeAlias = int | str
|
||||
|
||||
def _(x: IntOrStr):
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
## Can be used as value
|
||||
|
||||
Because PEP 613 type aliases are just annotated assignments, they can be used as values, like a
|
||||
legacy type expression (and unlike a PEP 695 type alias). We might prefer this wasn't allowed, but
|
||||
people do use it.
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
MyExc: TypeAlias = Exception
|
||||
|
||||
try:
|
||||
raise MyExc("error")
|
||||
except MyExc as e:
|
||||
reveal_type(e) # revealed: Exception
|
||||
```
|
||||
|
||||
## Can inherit from an alias
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
from ty_extensions import is_subtype_of, static_assert
|
||||
|
||||
MyList: TypeAlias = list["int"]
|
||||
|
||||
class Foo(MyList): ...
|
||||
|
||||
static_assert(is_subtype_of(Foo, list[int]))
|
||||
```
|
||||
|
||||
## Cannot inherit from a stringified alias
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
MyList: TypeAlias = "list[int]"
|
||||
|
||||
# error: [invalid-base] "Invalid class base with type `str`"
|
||||
class Foo(MyList): ...
|
||||
```
|
||||
|
||||
## Unknown type in PEP 604 union
|
||||
|
||||
If we run into an unknown type in a PEP 604 union in the right-hand side of a PEP 613 type alias, we
|
||||
still understand it as a union type, just with an unknown element.
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
from nonexistent import unknown_type # error: [unresolved-import]
|
||||
|
||||
MyAlias: TypeAlias = int | unknown_type | str
|
||||
|
||||
def _(x: MyAlias):
|
||||
reveal_type(x) # revealed: int | Unknown | str
|
||||
```
|
||||
|
||||
## Callable type in union
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, Callable
|
||||
|
||||
MyAlias: TypeAlias = int | Callable[[str], int]
|
||||
|
||||
def _(x: MyAlias):
|
||||
reveal_type(x) # revealed: int | ((str, /) -> int)
|
||||
```
|
||||
|
||||
## Subscripted generic alias in union
|
||||
|
||||
```py
|
||||
from typing import TypeAlias, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
Alias1: TypeAlias = list[T] | set[T]
|
||||
MyAlias: TypeAlias = int | Alias1[str]
|
||||
|
||||
def _(x: MyAlias):
|
||||
# TODO: int | list[str] | set[str]
|
||||
reveal_type(x) # revealed: int | @Todo(Specialization of union type alias)
|
||||
```
|
||||
|
||||
## Imported
|
||||
|
||||
`alias.py`:
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
MyAlias: TypeAlias = int | str
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from alias import MyAlias
|
||||
|
||||
def _(x: MyAlias):
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
## String literal in right-hand side
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
IntOrStr: TypeAlias = "int | str"
|
||||
|
||||
def _(x: IntOrStr):
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
## Cyclic
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
@@ -18,6 +157,26 @@ def _(rec: RecursiveHomogeneousTuple):
|
||||
reveal_type(rec) # revealed: tuple[Divergent, ...]
|
||||
```
|
||||
|
||||
## Conditionally imported on Python < 3.10
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
try:
|
||||
# error: [unresolved-import]
|
||||
from typing import TypeAlias
|
||||
except ImportError:
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
MyAlias: TypeAlias = int
|
||||
|
||||
def _(x: MyAlias):
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## PEP-613 aliases in stubs are deferred
|
||||
|
||||
Although the right-hand side of a PEP-613 alias is a value expression, inference of this value is
|
||||
@@ -46,7 +205,31 @@ f(stub.B())
|
||||
|
||||
class Unrelated: ...
|
||||
|
||||
# TODO: we should emit `[invalid-argument-type]` here
|
||||
# (the alias is a `@Todo` because it's imported from another file)
|
||||
# error: [invalid-argument-type]
|
||||
f(Unrelated())
|
||||
```
|
||||
|
||||
## Invalid position
|
||||
|
||||
`typing.TypeAlias` must be used as the sole annotation in an annotated assignment. Use in any other
|
||||
context is an error.
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
# error: [invalid-type-form]
|
||||
def _(x: TypeAlias):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
|
||||
# error: [invalid-type-form]
|
||||
y: list[TypeAlias] = []
|
||||
```
|
||||
|
||||
## Right-hand side is required
|
||||
|
||||
```py
|
||||
from typing import TypeAlias
|
||||
|
||||
# error: [invalid-type-form]
|
||||
Empty: TypeAlias
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ def f() -> None:
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`)
|
||||
reveal_type(IntOrStr.__value__) # revealed: Any
|
||||
```
|
||||
|
||||
## Invalid assignment
|
||||
|
||||
@@ -2003,6 +2003,7 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
from typing_extensions import TypeVar, Self, Protocol
|
||||
from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of
|
||||
|
||||
@@ -2094,6 +2095,13 @@ class NominalReturningSelfNotGeneric:
|
||||
def g(self) -> "NominalReturningSelfNotGeneric":
|
||||
return self
|
||||
|
||||
@final
|
||||
class Other: ...
|
||||
|
||||
class NominalReturningOtherClass:
|
||||
def g(self) -> Other:
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
|
||||
@@ -2112,8 +2120,7 @@ static_assert(not is_assignable_to(NominalLegacy, UsesSelf))
|
||||
static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped))
|
||||
static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped))
|
||||
static_assert(is_assignable_to(NominalWithSelf, UsesSelf))
|
||||
# TODO: should pass
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalWithSelf, UsesSelf))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error]
|
||||
@@ -2126,6 +2133,8 @@ static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctio
|
||||
# TODO: should pass
|
||||
static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error]
|
||||
|
||||
static_assert(not is_assignable_to(NominalReturningOtherClass, UsesSelf))
|
||||
|
||||
# These test cases are taken from the typing conformance suite:
|
||||
class ShapeProtocolImplicitSelf(Protocol):
|
||||
def set_scale(self, scale: float) -> Self: ...
|
||||
@@ -3069,18 +3078,15 @@ from typing import Protocol
|
||||
from ty_extensions import static_assert, is_subtype_of, is_equivalent_to, is_disjoint_from
|
||||
|
||||
class HasRepr(Protocol):
|
||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasRepr`)
|
||||
# error: [invalid-method-override]
|
||||
def __repr__(self) -> object: ...
|
||||
|
||||
class HasReprRecursive(Protocol):
|
||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasReprRecursive`)
|
||||
# error: [invalid-method-override]
|
||||
def __repr__(self) -> "HasReprRecursive": ...
|
||||
|
||||
class HasReprRecursiveAndFoo(Protocol):
|
||||
# TODO: we should emit a diagnostic here complaining about a Liskov violation
|
||||
# (it incompatibly overrides `__repr__` from `object`, a supertype of `HasReprRecursiveAndFoo`)
|
||||
# error: [invalid-method-override]
|
||||
def __repr__(self) -> "HasReprRecursiveAndFoo": ...
|
||||
foo: int
|
||||
|
||||
@@ -3180,6 +3186,33 @@ from ty_extensions import reveal_protocol_interface
|
||||
reveal_protocol_interface(Foo)
|
||||
```
|
||||
|
||||
## Known panics
|
||||
|
||||
### Protocols generic over TypeVars bound to forward references
|
||||
|
||||
This test currently panics because the `ClassLiteral::explicit_bases` query fails to converge. See
|
||||
issue <https://github.com/astral-sh/ty/issues/1587>.
|
||||
|
||||
<!-- expect-panic: execute: too many cycle iterations -->
|
||||
|
||||
```py
|
||||
from typing import Any, Protocol, TypeVar
|
||||
|
||||
T1 = TypeVar("T1", bound="A2[Any]")
|
||||
T2 = TypeVar("T2", bound="A1[Any]")
|
||||
T3 = TypeVar("T3", bound="B2[Any]")
|
||||
T4 = TypeVar("T4", bound="B1[Any]")
|
||||
|
||||
class A1(Protocol[T1]):
|
||||
def get_x(self): ...
|
||||
|
||||
class A2(Protocol[T2]):
|
||||
def get_y(self): ...
|
||||
|
||||
class B1(A1[T3], Protocol[T3]): ...
|
||||
class B2(A2[T4], Protocol[T4]): ...
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
Add tests for:
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Unknown key for all elemens of a union
|
||||
mdtest name: assignment_diagnostics.md - Subscript assignment diagnostics - Unknown key for all elements of a union
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_diagnostics.md
|
||||
---
|
||||
|
||||
@@ -16,26 +16,27 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
||||
2 |
|
||||
3 | class Person(TypedDict):
|
||||
4 | name: str
|
||||
5 |
|
||||
6 | class Animal(TypedDict):
|
||||
7 | name: str
|
||||
8 | legs: int
|
||||
9 |
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | # error: [invalid-key]
|
||||
5 | phone_number: str
|
||||
6 |
|
||||
7 | class Animal(TypedDict):
|
||||
8 | name: str
|
||||
9 | legs: int
|
||||
10 |
|
||||
11 | def _(being: Person | Animal) -> None:
|
||||
12 | # error: [invalid-key]
|
||||
13 | being["surname"] = "unknown"
|
||||
13 | # error: [invalid-key]
|
||||
14 | being["surname"] = "unknown"
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Unknown key "surname" for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:13:5
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
|
|
||||
11 | # error: [invalid-key]
|
||||
12 | # error: [invalid-key]
|
||||
13 | being["surname"] = "unknown"
|
||||
13 | # error: [invalid-key]
|
||||
14 | being["surname"] = "unknown"
|
||||
| ----- ^^^^^^^^^ Did you mean "name"?
|
||||
| |
|
||||
| TypedDict `Person` in union type `Person | Animal`
|
||||
@@ -46,11 +47,11 @@ info: rule `invalid-key` is enabled by default
|
||||
|
||||
```
|
||||
error[invalid-key]: Unknown key "surname" for TypedDict `Animal`
|
||||
--> src/mdtest_snippet.py:13:5
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
|
|
||||
11 | # error: [invalid-key]
|
||||
12 | # error: [invalid-key]
|
||||
13 | being["surname"] = "unknown"
|
||||
13 | # error: [invalid-key]
|
||||
14 | being["surname"] = "unknown"
|
||||
| ----- ^^^^^^^^^ Did you mean "name"?
|
||||
| |
|
||||
| TypedDict `Animal` in union type `Person | Animal`
|
||||
@@ -16,23 +16,24 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/subscript/assignment_dia
|
||||
2 |
|
||||
3 | class Person(TypedDict):
|
||||
4 | name: str
|
||||
5 |
|
||||
6 | class Animal(TypedDict):
|
||||
7 | name: str
|
||||
8 | legs: int
|
||||
9 |
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["legs"] = 4 # error: [invalid-key]
|
||||
5 | phone_number: str
|
||||
6 |
|
||||
7 | class Animal(TypedDict):
|
||||
8 | name: str
|
||||
9 | legs: int
|
||||
10 |
|
||||
11 | def _(being: Person | Animal) -> None:
|
||||
12 | being["legs"] = 4 # error: [invalid-key]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-key]: Unknown key "legs" for TypedDict `Person`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
--> src/mdtest_snippet.py:12:5
|
||||
|
|
||||
10 | def _(being: Person | Animal) -> None:
|
||||
11 | being["legs"] = 4 # error: [invalid-key]
|
||||
11 | def _(being: Person | Animal) -> None:
|
||||
12 | being["legs"] = 4 # error: [invalid-key]
|
||||
| ----- ^^^^^^ Unknown key "legs"
|
||||
| |
|
||||
| TypedDict `Person` in union type `Person | Animal`
|
||||
|
||||
@@ -32,7 +32,8 @@ error[unresolved-attribute]: Module `datetime` has no member `UTC`
|
||||
5 | # error: [unresolved-attribute]
|
||||
6 | reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
|
|
||||
info: Python 3.10 was assumed when accessing `UTC` because it was specified on the command line
|
||||
info: The member may be available on other Python versions or platforms
|
||||
info: Python 3.10 was assumed when resolving the `UTC` attribute because it was specified on the command line
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Unimported submodule incorrectly accessed as attribute
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## foo/__init__.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## foo/bar.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## baz/bar.py
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | import foo
|
||||
2 | import baz
|
||||
3 |
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `foo`
|
||||
--> src/main.py:5:13
|
||||
|
|
||||
4 | # error: [possibly-missing-attribute]
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
| ^^^^^^^
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
|
|
||||
help: Consider explicitly importing `foo.bar`
|
||||
info: rule `possibly-missing-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `baz`
|
||||
--> src/main.py:7:13
|
||||
|
|
||||
5 | reveal_type(foo.bar) # revealed: <module 'foo.bar'>
|
||||
6 | # error: [possibly-missing-attribute]
|
||||
7 | reveal_type(baz.bar) # revealed: <module 'baz.bar'>
|
||||
| ^^^^^^^
|
||||
|
|
||||
help: Consider explicitly importing `baz.bar`
|
||||
info: rule `possibly-missing-attribute` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Bad override of `__eq__`
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class Bad:
|
||||
2 | x: int
|
||||
3 | def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||
4 | return self.x == other.x
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `__eq__`
|
||||
--> src/mdtest_snippet.py:3:9
|
||||
|
|
||||
1 | class Bad:
|
||||
2 | x: int
|
||||
3 | def __eq__(self, other: "Bad") -> bool: # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `object.__eq__`
|
||||
4 | return self.x == other.x
|
||||
|
|
||||
::: stdlib/builtins.pyi:142:9
|
||||
|
|
||||
140 | def __setattr__(self, name: str, value: Any, /) -> None: ...
|
||||
141 | def __delattr__(self, name: str, /) -> None: ...
|
||||
142 | def __eq__(self, value: object, /) -> bool: ...
|
||||
| -------------------------------------- `object.__eq__` defined here
|
||||
143 | def __ne__(self, value: object, /) -> bool: ...
|
||||
144 | def __str__(self) -> str: ... # noqa: Y029
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
help: It is recommended for `__eq__` to work with arbitrary objects, for example:
|
||||
help
|
||||
help: def __eq__(self, other: object) -> bool:
|
||||
help: if not isinstance(other, Bad):
|
||||
help: return False
|
||||
help: return <logic to compare two `Bad` instances>
|
||||
help
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Edge case: function defined in another module and then assigned in a class body
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## foo.pyi
|
||||
|
||||
```
|
||||
1 | def x(self, y: str): ...
|
||||
```
|
||||
|
||||
## bar.pyi
|
||||
|
||||
```
|
||||
1 | import foo
|
||||
2 |
|
||||
3 | class A:
|
||||
4 | def x(self, y: int): ...
|
||||
5 |
|
||||
6 | class B(A):
|
||||
7 | x = foo.x # error: [invalid-method-override]
|
||||
8 |
|
||||
9 | class C:
|
||||
10 | x = foo.x
|
||||
11 |
|
||||
12 | class D(C):
|
||||
13 | def x(self, y: int): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `x`
|
||||
--> src/bar.pyi:4:9
|
||||
|
|
||||
3 | class A:
|
||||
4 | def x(self, y: int): ...
|
||||
| --------------- `A.x` defined here
|
||||
5 |
|
||||
6 | class B(A):
|
||||
7 | x = foo.x # error: [invalid-method-override]
|
||||
| ^^^^^^^^^ Definition is incompatible with `A.x`
|
||||
8 |
|
||||
9 | class C:
|
||||
|
|
||||
::: src/foo.pyi:1:5
|
||||
|
|
||||
1 | def x(self, y: str): ...
|
||||
| --------------- Signature of `B.x`
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `x`
|
||||
--> src/bar.pyi:10:5
|
||||
|
|
||||
9 | class C:
|
||||
10 | x = foo.x
|
||||
| --------- `C.x` defined here
|
||||
11 |
|
||||
12 | class D(C):
|
||||
13 | def x(self, y: int): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^ Definition is incompatible with `C.x`
|
||||
|
|
||||
::: src/foo.pyi:1:5
|
||||
|
|
||||
1 | def x(self, y: str): ...
|
||||
| --------------- Signature of `C.x`
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Fully qualified names are used in diagnostics where appropriate
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## a.pyi
|
||||
|
||||
```
|
||||
1 | class A:
|
||||
2 | def foo(self, x): ...
|
||||
```
|
||||
|
||||
## b.pyi
|
||||
|
||||
```
|
||||
1 | import a
|
||||
2 |
|
||||
3 | class A(a.A):
|
||||
4 | def foo(self, y): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `foo`
|
||||
--> src/b.pyi:4:9
|
||||
|
|
||||
3 | class A(a.A):
|
||||
4 | def foo(self, y): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^ Definition is incompatible with `a.A.foo`
|
||||
|
|
||||
::: src/a.pyi:2:9
|
||||
|
|
||||
1 | class A:
|
||||
2 | def foo(self, x): ...
|
||||
| ------------ `a.A.foo` defined here
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,331 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Method parameters
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.pyi
|
||||
|
||||
```
|
||||
1 | class Super:
|
||||
2 | def method(self, x: int, /): ...
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
5 | def method(self, x: int, /): ... # fine
|
||||
6 |
|
||||
7 | class Sub2(Super):
|
||||
8 | def method(self, x: object, /): ... # fine: `method` still accepts any argument of type `int`
|
||||
9 |
|
||||
10 | class Sub4(Super):
|
||||
11 | def method(self, x: int | str, /): ... # fine
|
||||
12 |
|
||||
13 | class Sub5(Super):
|
||||
14 | def method(self, x: int): ... # fine: `x` can still be passed positionally
|
||||
15 |
|
||||
16 | class Sub6(Super):
|
||||
17 | # fine: `method()` can still be called with just a single argument
|
||||
18 | def method(self, x: int, *args): ...
|
||||
19 |
|
||||
20 | class Sub7(Super):
|
||||
21 | def method(self, x: int, **kwargs): ... # fine
|
||||
22 |
|
||||
23 | class Sub8(Super):
|
||||
24 | def method(self, x: int, *args, **kwargs): ... # fine
|
||||
25 |
|
||||
26 | class Sub9(Super):
|
||||
27 | def method(self, x: int, extra_positional_arg=42, /): ... # fine
|
||||
28 |
|
||||
29 | class Sub10(Super):
|
||||
30 | def method(self, x: int, extra_pos_or_kw_arg=42): ... # fine
|
||||
31 |
|
||||
32 | class Sub11(Super):
|
||||
33 | def method(self, x: int, *, extra_kw_only_arg=42): ... # fine
|
||||
34 |
|
||||
35 | class Sub12(Super):
|
||||
36 | # Some calls permitted by the superclass are now no longer allowed
|
||||
37 | # (the method can no longer be passed any arguments!)
|
||||
38 | def method(self, /): ... # error: [invalid-method-override]
|
||||
39 |
|
||||
40 | class Sub13(Super):
|
||||
41 | # Some calls permitted by the superclass are now no longer allowed
|
||||
42 | # (the method can no longer be passed exactly one argument!)
|
||||
43 | def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||
44 |
|
||||
45 | class Sub14(Super):
|
||||
46 | # Some calls permitted by the superclass are now no longer allowed
|
||||
47 | # (x can no longer be passed positionally!)
|
||||
48 | def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||
49 |
|
||||
50 | class Sub15(Super):
|
||||
51 | # Some calls permitted by the superclass are now no longer allowed
|
||||
52 | # (x can no longer be passed any integer -- it now requires a bool!)
|
||||
53 | def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||
54 |
|
||||
55 | class Super2:
|
||||
56 | def method2(self, x): ...
|
||||
57 |
|
||||
58 | class Sub16(Super2):
|
||||
59 | def method2(self, x, /): ... # error: [invalid-method-override]
|
||||
60 |
|
||||
61 | class Sub17(Super2):
|
||||
62 | def method2(self, *, x): ... # error: [invalid-method-override]
|
||||
63 |
|
||||
64 | class Super3:
|
||||
65 | def method3(self, *, x): ...
|
||||
66 |
|
||||
67 | class Sub18(Super3):
|
||||
68 | def method3(self, x): ... # fine: `x` can still be used as a keyword argument
|
||||
69 |
|
||||
70 | class Sub19(Super3):
|
||||
71 | def method3(self, x, /): ... # error: [invalid-method-override]
|
||||
72 |
|
||||
73 | class Super4:
|
||||
74 | def method(self, *args: int, **kwargs: str): ...
|
||||
75 |
|
||||
76 | class Sub20(Super4):
|
||||
77 | def method(self, *args: object, **kwargs: object): ... # fine
|
||||
78 |
|
||||
79 | class Sub21(Super4):
|
||||
80 | def method(self, *args): ... # error: [invalid-method-override]
|
||||
81 |
|
||||
82 | class Sub22(Super4):
|
||||
83 | def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||
84 |
|
||||
85 | class Sub23(Super4):
|
||||
86 | def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:38:9
|
||||
|
|
||||
36 | # Some calls permitted by the superclass are now no longer allowed
|
||||
37 | # (the method can no longer be passed any arguments!)
|
||||
38 | def method(self, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
39 |
|
||||
40 | class Sub13(Super):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self, x: int, /): ...
|
||||
| ----------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:43:9
|
||||
|
|
||||
41 | # Some calls permitted by the superclass are now no longer allowed
|
||||
42 | # (the method can no longer be passed exactly one argument!)
|
||||
43 | def method(self, x, y, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
44 |
|
||||
45 | class Sub14(Super):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self, x: int, /): ...
|
||||
| ----------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:48:9
|
||||
|
|
||||
46 | # Some calls permitted by the superclass are now no longer allowed
|
||||
47 | # (x can no longer be passed positionally!)
|
||||
48 | def method(self, /, *, x): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
49 |
|
||||
50 | class Sub15(Super):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self, x: int, /): ...
|
||||
| ----------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:53:9
|
||||
|
|
||||
51 | # Some calls permitted by the superclass are now no longer allowed
|
||||
52 | # (x can no longer be passed any integer -- it now requires a bool!)
|
||||
53 | def method(self, x: bool, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
54 |
|
||||
55 | class Super2:
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self, x: int, /): ...
|
||||
| ----------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method2`
|
||||
--> src/mdtest_snippet.pyi:56:9
|
||||
|
|
||||
55 | class Super2:
|
||||
56 | def method2(self, x): ...
|
||||
| ---------------- `Super2.method2` defined here
|
||||
57 |
|
||||
58 | class Sub16(Super2):
|
||||
59 | def method2(self, x, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super2.method2`
|
||||
60 |
|
||||
61 | class Sub17(Super2):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method2`
|
||||
--> src/mdtest_snippet.pyi:62:9
|
||||
|
|
||||
61 | class Sub17(Super2):
|
||||
62 | def method2(self, *, x): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super2.method2`
|
||||
63 |
|
||||
64 | class Super3:
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:56:9
|
||||
|
|
||||
55 | class Super2:
|
||||
56 | def method2(self, x): ...
|
||||
| ---------------- `Super2.method2` defined here
|
||||
57 |
|
||||
58 | class Sub16(Super2):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method3`
|
||||
--> src/mdtest_snippet.pyi:71:9
|
||||
|
|
||||
70 | class Sub19(Super3):
|
||||
71 | def method3(self, x, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super3.method3`
|
||||
72 |
|
||||
73 | class Super4:
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:65:9
|
||||
|
|
||||
64 | class Super3:
|
||||
65 | def method3(self, *, x): ...
|
||||
| ------------------- `Super3.method3` defined here
|
||||
66 |
|
||||
67 | class Sub18(Super3):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:80:9
|
||||
|
|
||||
79 | class Sub21(Super4):
|
||||
80 | def method(self, *args): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||
81 |
|
||||
82 | class Sub22(Super4):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:74:9
|
||||
|
|
||||
73 | class Super4:
|
||||
74 | def method(self, *args: int, **kwargs: str): ...
|
||||
| --------------------------------------- `Super4.method` defined here
|
||||
75 |
|
||||
76 | class Sub20(Super4):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:83:9
|
||||
|
|
||||
82 | class Sub22(Super4):
|
||||
83 | def method(self, **kwargs): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||
84 |
|
||||
85 | class Sub23(Super4):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:74:9
|
||||
|
|
||||
73 | class Super4:
|
||||
74 | def method(self, *args: int, **kwargs: str): ...
|
||||
| --------------------------------------- `Super4.method` defined here
|
||||
75 |
|
||||
76 | class Sub20(Super4):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:86:9
|
||||
|
|
||||
85 | class Sub23(Super4):
|
||||
86 | def method(self, x, *args, y, **kwargs): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super4.method`
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:74:9
|
||||
|
|
||||
73 | class Super4:
|
||||
74 | def method(self, *args: int, **kwargs: str): ...
|
||||
| --------------------------------------- `Super4.method` defined here
|
||||
75 |
|
||||
76 | class Sub20(Super4):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Method return types
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.pyi
|
||||
|
||||
```
|
||||
1 | class Super:
|
||||
2 | def method(self) -> int: ...
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
5 | def method(self) -> int: ... # fine
|
||||
6 |
|
||||
7 | class Sub2(Super):
|
||||
8 | def method(self) -> bool: ... # fine: `bool` is a subtype of `int`
|
||||
9 |
|
||||
10 | class Sub3(Super):
|
||||
11 | def method(self) -> object: ... # error: [invalid-method-override]
|
||||
12 |
|
||||
13 | class Sub4(Super):
|
||||
14 | def method(self) -> str: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:11:9
|
||||
|
|
||||
10 | class Sub3(Super):
|
||||
11 | def method(self) -> object: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
12 |
|
||||
13 | class Sub4(Super):
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self) -> int: ...
|
||||
| ------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/mdtest_snippet.pyi:14:9
|
||||
|
|
||||
13 | class Sub4(Super):
|
||||
14 | def method(self) -> str: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Super.method`
|
||||
|
|
||||
::: src/mdtest_snippet.pyi:2:9
|
||||
|
|
||||
1 | class Super:
|
||||
2 | def method(self) -> int: ...
|
||||
| ------------------- `Super.method` defined here
|
||||
3 |
|
||||
4 | class Sub1(Super):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - Synthesized methods
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.pyi
|
||||
|
||||
```
|
||||
1 | from dataclasses import dataclass
|
||||
2 | from typing import NamedTuple
|
||||
3 |
|
||||
4 | @dataclass(order=True)
|
||||
5 | class Foo:
|
||||
6 | x: int
|
||||
7 |
|
||||
8 | class Bar(Foo):
|
||||
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||
10 |
|
||||
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||
12 | # generated that is incompatible with the generated `__lt__` method on the superclass.
|
||||
13 | # We could consider detecting this and emitting a diagnostic, though maybe it shouldn't
|
||||
14 | # be `invalid-method-override` since we'd emit it on the class definition rather than
|
||||
15 | # on any method definition. Note also that no other type checker complains about this
|
||||
16 | # as of 2025-11-21.
|
||||
17 | @dataclass(order=True)
|
||||
18 | class Bar2(Foo):
|
||||
19 | y: str
|
||||
20 |
|
||||
21 | # TODO: Although this class does not override any methods of `Foo`, the design of the
|
||||
22 | # `order=True` stdlib dataclasses feature itself arguably violates the Liskov Substitution
|
||||
23 | # Principle! Instances of `Bar3` cannot be substituted wherever an instance of `Foo` is
|
||||
24 | # expected, because the generated `__lt__` method on `Foo` raises an error unless the r.h.s.
|
||||
25 | # and `l.h.s.` have exactly the same `__class__` (it does not permit instances of `Foo` to
|
||||
26 | # be compared with instances of subclasses of `Foo`).
|
||||
27 | #
|
||||
28 | # Many users would probably like their type checkers to alert them to cases where instances
|
||||
29 | # of subclasses cannot be substituted for instances of superclasses, as this violates many
|
||||
30 | # assumptions a type checker will make and makes it likely that a type checker will fail to
|
||||
31 | # catch type errors elsewhere in the user's code. We could therefore consider treating all
|
||||
32 | # `order=True` dataclasses as implicitly `@final` in order to enforce soundness. However,
|
||||
33 | # this probably shouldn't be reported with the same error code as Liskov violations, since
|
||||
34 | # the error does not stem from any method signatures written by the user. The example is
|
||||
35 | # only included here for completeness.
|
||||
36 | #
|
||||
37 | # Note that no other type checker catches this error as of 2025-11-21.
|
||||
38 | class Bar3(Foo): ...
|
||||
39 |
|
||||
40 | class Eggs:
|
||||
41 | def __lt__(self, other: Eggs) -> bool: ...
|
||||
42 |
|
||||
43 | # TODO: the generated `Ham.__lt__` method here incompatibly overrides `Eggs.__lt__`.
|
||||
44 | # We could consider emitting a diagnostic here. As of 2025-11-21, mypy reports a
|
||||
45 | # diagnostic here but pyright and pyrefly do not.
|
||||
46 | @dataclass(order=True)
|
||||
47 | class Ham(Eggs):
|
||||
48 | x: int
|
||||
49 |
|
||||
50 | class Baz(NamedTuple):
|
||||
51 | x: int
|
||||
52 |
|
||||
53 | class Spam(Baz):
|
||||
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `__lt__`
|
||||
--> src/mdtest_snippet.pyi:9:9
|
||||
|
|
||||
8 | class Bar(Foo):
|
||||
9 | def __lt__(self, other: Bar) -> bool: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Foo.__lt__`
|
||||
10 |
|
||||
11 | # TODO: specifying `order=True` on the subclass means that a `__lt__` method is
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: `Foo.__lt__` is a generated method created because `Foo` is a dataclass
|
||||
--> src/mdtest_snippet.pyi:5:7
|
||||
|
|
||||
4 | @dataclass(order=True)
|
||||
5 | class Foo:
|
||||
| ^^^ Definition of `Foo`
|
||||
6 | x: int
|
||||
|
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `_asdict`
|
||||
--> src/mdtest_snippet.pyi:54:9
|
||||
|
|
||||
53 | class Spam(Baz):
|
||||
54 | def _asdict(self) -> tuple[int, ...]: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Baz._asdict`
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: `Baz._asdict` is a generated method created because `Baz` inherits from `typing.NamedTuple`
|
||||
--> src/mdtest_snippet.pyi:50:7
|
||||
|
|
||||
48 | x: int
|
||||
49 |
|
||||
50 | class Baz(NamedTuple):
|
||||
| ^^^^^^^^^^^^^^^ Definition of `Baz`
|
||||
51 | x: int
|
||||
|
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: liskov.md - The Liskov Substitution Principle - The entire class hierarchy is checked
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## stub.pyi
|
||||
|
||||
```
|
||||
1 | from typing import Any
|
||||
2 |
|
||||
3 | class Grandparent:
|
||||
4 | def method(self, x: int) -> None: ...
|
||||
5 |
|
||||
6 | class Parent(Grandparent):
|
||||
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
8 |
|
||||
9 | class Child(Parent):
|
||||
10 | # compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||
11 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
12 |
|
||||
13 | class OtherChild(Parent):
|
||||
14 | # compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||
15 | def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||
16 |
|
||||
17 | class GradualParent(Grandparent):
|
||||
18 | def method(self, x: Any) -> None: ...
|
||||
19 |
|
||||
20 | class ThirdChild(GradualParent):
|
||||
21 | # `GradualParent.method` is compatible with the signature of `Grandparent.method`,
|
||||
22 | # and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||
23 | # but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||
24 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
## other_stub.pyi
|
||||
|
||||
```
|
||||
1 | class A:
|
||||
2 | def get(self, default): ...
|
||||
3 |
|
||||
4 | class B(A):
|
||||
5 | def get(self, default, /): ... # error: [invalid-method-override]
|
||||
6 |
|
||||
7 | get = 56
|
||||
8 |
|
||||
9 | class C(B):
|
||||
10 | # `get` appears in the symbol table of `C`,
|
||||
11 | # but that doesn't confuse our diagnostic...
|
||||
12 | foo = get
|
||||
13 |
|
||||
14 | class D(C):
|
||||
15 | # compatible with `C.get` and `B.get`, but not with `A.get`
|
||||
16 | def get(self, my_default): ... # error: [invalid-method-override]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/stub.pyi:4:9
|
||||
|
|
||||
3 | class Grandparent:
|
||||
4 | def method(self, x: int) -> None: ...
|
||||
| ---------------------------- `Grandparent.method` defined here
|
||||
5 |
|
||||
6 | class Parent(Grandparent):
|
||||
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||
8 |
|
||||
9 | class Child(Parent):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/stub.pyi:11:9
|
||||
|
|
||||
9 | class Child(Parent):
|
||||
10 | # compatible with the signature of `Parent.method`, but not with `Grandparent.method`:
|
||||
11 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||
12 |
|
||||
13 | class OtherChild(Parent):
|
||||
|
|
||||
::: src/stub.pyi:4:9
|
||||
|
|
||||
3 | class Grandparent:
|
||||
4 | def method(self, x: int) -> None: ...
|
||||
| ---------------------------- `Grandparent.method` defined here
|
||||
5 |
|
||||
6 | class Parent(Grandparent):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/stub.pyi:15:9
|
||||
|
|
||||
13 | class OtherChild(Parent):
|
||||
14 | # compatible with the signature of `Grandparent.method`, but not with `Parent.method`:
|
||||
15 | def method(self, x: int) -> None: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Parent.method`
|
||||
16 |
|
||||
17 | class GradualParent(Grandparent):
|
||||
|
|
||||
::: src/stub.pyi:7:9
|
||||
|
|
||||
6 | class Parent(Grandparent):
|
||||
7 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
| ---------------------------- `Parent.method` defined here
|
||||
8 |
|
||||
9 | class Child(Parent):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `method`
|
||||
--> src/stub.pyi:24:9
|
||||
|
|
||||
22 | # and `ThirdChild.method` is compatible with the signature of `GradualParent.method`,
|
||||
23 | # but `ThirdChild.method` is not compatible with the signature of `Grandparent.method`
|
||||
24 | def method(self, x: str) -> None: ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `Grandparent.method`
|
||||
|
|
||||
::: src/stub.pyi:4:9
|
||||
|
|
||||
3 | class Grandparent:
|
||||
4 | def method(self, x: int) -> None: ...
|
||||
| ---------------------------- `Grandparent.method` defined here
|
||||
5 |
|
||||
6 | class Parent(Grandparent):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `get`
|
||||
--> src/other_stub.pyi:2:9
|
||||
|
|
||||
1 | class A:
|
||||
2 | def get(self, default): ...
|
||||
| ------------------ `A.get` defined here
|
||||
3 |
|
||||
4 | class B(A):
|
||||
5 | def get(self, default, /): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `A.get`
|
||||
6 |
|
||||
7 | get = 56
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `get`
|
||||
--> src/other_stub.pyi:16:9
|
||||
|
|
||||
14 | class D(C):
|
||||
15 | # compatible with `C.get` and `B.get`, but not with `A.get`
|
||||
16 | def get(self, my_default): ... # error: [invalid-method-override]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `A.get`
|
||||
|
|
||||
::: src/other_stub.pyi:2:9
|
||||
|
|
||||
1 | class A:
|
||||
2 | def get(self, default): ...
|
||||
| ------------------ `A.get` defined here
|
||||
3 |
|
||||
4 | class B(A):
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
@@ -303,13 +303,13 @@ info: rule `duplicate-base` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
info[unused-ignore-comment]: Unused blanket `type: ignore` directive
|
||||
--> src/mdtest_snippet.py:72:9
|
||||
|
|
||||
70 | A,
|
||||
71 | # error: [unused-ignore-comment]
|
||||
72 | A, # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
73 | ): ...
|
||||
|
|
||||
|
||||
@@ -346,13 +346,13 @@ info: rule `duplicate-base` is enabled by default
|
||||
```
|
||||
|
||||
```
|
||||
info[unused-ignore-comment]
|
||||
info[unused-ignore-comment]: Unused blanket `type: ignore` directive
|
||||
--> src/mdtest_snippet.py:81:13
|
||||
|
|
||||
79 | ):
|
||||
80 | # error: [unused-ignore-comment]
|
||||
81 | x: int # type: ignore[duplicate-base]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
82 |
|
||||
83 | # fmt: on
|
||||
|
|
||||
|
||||
@@ -12,27 +12,59 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class NotBoolable:
|
||||
2 | __bool__: None = None
|
||||
3 |
|
||||
4 | class A:
|
||||
5 | def __eq__(self, other) -> NotBoolable:
|
||||
6 | return NotBoolable()
|
||||
7 |
|
||||
8 | # error: [unsupported-bool-conversion]
|
||||
9 | (A(),) == (A(),)
|
||||
1 | class NotBoolable:
|
||||
2 | __bool__: None = None
|
||||
3 |
|
||||
4 | class A:
|
||||
5 | # error: [invalid-method-override]
|
||||
6 | def __eq__(self, other) -> NotBoolable:
|
||||
7 | return NotBoolable()
|
||||
8 |
|
||||
9 | # error: [unsupported-bool-conversion]
|
||||
10 | (A(),) == (A(),)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-method-override]: Invalid override of method `__eq__`
|
||||
--> src/mdtest_snippet.py:6:9
|
||||
|
|
||||
4 | class A:
|
||||
5 | # error: [invalid-method-override]
|
||||
6 | def __eq__(self, other) -> NotBoolable:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Definition is incompatible with `object.__eq__`
|
||||
7 | return NotBoolable()
|
||||
|
|
||||
::: stdlib/builtins.pyi:142:9
|
||||
|
|
||||
140 | def __setattr__(self, name: str, value: Any, /) -> None: ...
|
||||
141 | def __delattr__(self, name: str, /) -> None: ...
|
||||
142 | def __eq__(self, value: object, /) -> bool: ...
|
||||
| -------------------------------------- `object.__eq__` defined here
|
||||
143 | def __ne__(self, value: object, /) -> bool: ...
|
||||
144 | def __str__(self) -> str: ... # noqa: Y029
|
||||
|
|
||||
info: This violates the Liskov Substitution Principle
|
||||
help: It is recommended for `__eq__` to work with arbitrary objects, for example:
|
||||
help
|
||||
help: def __eq__(self, other: object) -> bool:
|
||||
help: if not isinstance(other, A):
|
||||
help: return False
|
||||
help: return <logic to compare two `A` instances>
|
||||
help
|
||||
info: rule `invalid-method-override` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unsupported-bool-conversion]: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> src/mdtest_snippet.py:9:1
|
||||
|
|
||||
8 | # error: [unsupported-bool-conversion]
|
||||
9 | (A(),) == (A(),)
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
--> src/mdtest_snippet.py:10:1
|
||||
|
|
||||
9 | # error: [unsupported-bool-conversion]
|
||||
10 | (A(),) == (A(),)
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
info: rule `unsupported-bool-conversion` is enabled by default
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist.foo.ba
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
@@ -28,6 +28,10 @@ error[unresolved-import]: Cannot resolve imported module `.does_not_exist`
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. /src (first-party code)
|
||||
info: 2. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user