Compare commits
3 Commits
dcreager/i
...
david/fiel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea56e2415f | ||
|
|
23543194fc | ||
|
|
4dc88a0a5f |
@@ -1,12 +1,3 @@
|
||||
# Define serial test group for running tests sequentially.
|
||||
[test-groups]
|
||||
serial = { max-threads = 1 }
|
||||
|
||||
# Run ty file watching tests sequentially to avoid race conditions.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'binary(file_watching)'
|
||||
test-group = 'serial'
|
||||
|
||||
[profile.ci]
|
||||
# Print out output for failing tests as soon as they fail, and also at the end
|
||||
# of the run (for easy scrollability).
|
||||
|
||||
@@ -10,7 +10,7 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
|
||||
[*.{rs,py,pyi,toml}]
|
||||
[*.{rs,py,pyi}]
|
||||
indent_size = 4
|
||||
|
||||
[*.snap]
|
||||
@@ -18,3 +18,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = 100
|
||||
|
||||
[*.toml]
|
||||
indent_size = 4
|
||||
54
.github/workflows/ci.yaml
vendored
54
.github/workflows/ci.yaml
vendored
@@ -12,10 +12,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
@@ -23,7 +19,6 @@ env:
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.13"
|
||||
NEXTEST_PROFILE: ci
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
@@ -242,7 +237,7 @@ jobs:
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
@@ -276,6 +271,9 @@ jobs:
|
||||
# This step is just to get nice GitHub annotations on the PR diff in the files-changed tab.
|
||||
run: cargo test -p ty_python_semantic --test mdtest || true
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
# Check for broken links in the documentation.
|
||||
@@ -301,13 +299,9 @@ jobs:
|
||||
|
||||
cargo-test-linux-release:
|
||||
name: "cargo test (linux, release)"
|
||||
# release builds timeout on GitHub runners, so this job is just skipped on forks in the `if` check
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'no-test') &&
|
||||
(needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main')
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -331,11 +325,14 @@ jobs:
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
cargo-test-windows:
|
||||
name: "cargo test (windows)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
|
||||
runs-on: depot-windows-2022-16
|
||||
needs: determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
@@ -355,7 +352,9 @@ jobs:
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
|
||||
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
|
||||
run: |
|
||||
@@ -386,6 +385,9 @@ jobs:
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: |
|
||||
cargo nextest run --all-features --profile ci
|
||||
cargo test --all-features --doc
|
||||
@@ -422,7 +424,7 @@ jobs:
|
||||
|
||||
cargo-build-msrv:
|
||||
name: "cargo build (msrv)"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
needs: determine_changes
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
|
||||
timeout-minutes: 20
|
||||
@@ -443,6 +445,7 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Build tests"
|
||||
shell: bash
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: cargo "+${MSRV}" test --no-run --all-features
|
||||
@@ -535,7 +538,7 @@ jobs:
|
||||
|
||||
ecosystem:
|
||||
name: "ecosystem"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
@@ -660,13 +663,13 @@ jobs:
|
||||
|
||||
fuzz-ty:
|
||||
name: "Fuzz for new ty panics"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
@@ -720,7 +723,7 @@ jobs:
|
||||
|
||||
ty-completion-evaluation:
|
||||
name: "ty completion evaluation"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main' }}
|
||||
steps:
|
||||
@@ -766,7 +769,7 @@ jobs:
|
||||
|
||||
pre-commit:
|
||||
name: "pre-commit"
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -940,12 +943,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
(
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.formatter == 'true' ||
|
||||
needs.determine_changes.outputs.linter == 'true'
|
||||
)
|
||||
github.ref == 'refs/heads/main' ||
|
||||
(needs.determine_changes.outputs.formatter == 'true' || needs.determine_changes.outputs.linter == 'true')
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
@@ -979,11 +978,8 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: determine_changes
|
||||
if: |
|
||||
github.repository == 'astral-sh/ruff' &&
|
||||
(
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
)
|
||||
github.ref == 'refs/heads/main' ||
|
||||
needs.determine_changes.outputs.ty == 'true'
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: "Checkout Branch"
|
||||
|
||||
10
.github/workflows/mypy_primer.yaml
vendored
10
.github/workflows/mypy_primer.yaml
vendored
@@ -19,10 +19,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
@@ -33,7 +29,7 @@ env:
|
||||
jobs:
|
||||
mypy_primer:
|
||||
name: Run mypy_primer
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -53,6 +49,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
@@ -75,7 +72,7 @@ jobs:
|
||||
|
||||
memory_usage:
|
||||
name: Run memory statistics
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -95,6 +92,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
TY_MAX_PARALLELISM: 1 # for deterministic memory numbers
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
|
||||
76
.github/workflows/sync_typeshed.yaml
vendored
76
.github/workflows/sync_typeshed.yaml
vendored
@@ -16,10 +16,8 @@ name: Sync typeshed
|
||||
# 3. Once the Windows worker is done, a MacOS worker:
|
||||
# a. Checks out the branch created by the Linux worker
|
||||
# b. Syncs all docstrings available on MacOS that are not available on Linux or Windows
|
||||
# c. Attempts to update any snapshots that might have changed
|
||||
# (this sub-step is allowed to fail)
|
||||
# d. Commits the changes and pushes them to the same upstream branch
|
||||
# e. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# c. Commits the changes and pushes them to the same upstream branch
|
||||
# d. Creates a PR against the `main` branch using the branch all three workers have pushed to
|
||||
# 4. If any of steps 1-3 failed, an issue is created in the `astral-sh/ruff` repository
|
||||
|
||||
on:
|
||||
@@ -28,18 +26,8 @@ on:
|
||||
# Run on the 1st and the 15th of every month:
|
||||
- cron: "0 0 1,15 * *"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
# Don't set this flag globally for the workflow: it does strange things
|
||||
# to the snapshots in the `cargo insta test --accept` step in the MacOS job.
|
||||
#
|
||||
# FORCE_COLOR: 1
|
||||
|
||||
CARGO_TERM_COLOR: always
|
||||
NEXTEST_PROFILE: "ci"
|
||||
FORCE_COLOR: 1
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
# The name of the upstream branch that the first worker creates,
|
||||
@@ -98,8 +86,6 @@ jobs:
|
||||
git commit -m "Sync typeshed. Source commit: https://github.com/python/typeshed/commit/$(git -C ../typeshed rev-parse HEAD)" --allow-empty
|
||||
- name: Sync Linux docstrings
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
cd ruff
|
||||
./scripts/codemod_docstrings.sh
|
||||
@@ -138,8 +124,7 @@ jobs:
|
||||
git config --global user.email '<>'
|
||||
- name: Sync Windows docstrings
|
||||
id: docstrings
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
shell: bash
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit the changes
|
||||
if: ${{ steps.docstrings.outcome == 'success' }}
|
||||
@@ -176,63 +161,26 @@ jobs:
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- name: Sync macOS docstrings
|
||||
run: ./scripts/codemod_docstrings.sh
|
||||
- name: Commit and push the changes
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
./scripts/codemod_docstrings.sh
|
||||
git commit -am "Sync macOS docstrings" --allow-empty
|
||||
- name: Format the changes
|
||||
if: ${{ success() }}
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
run: |
|
||||
|
||||
# Here we just reformat the codemodded stubs so that they are
|
||||
# consistent with the other typeshed stubs around them.
|
||||
# Typeshed formats code using black in their CI, so we just invoke
|
||||
# black on the stubs the same way that typeshed does.
|
||||
uvx black "${VENDORED_TYPESHED}/stdlib" --config "${VENDORED_TYPESHED}/pyproject.toml" || true
|
||||
git commit -am "Format codemodded docstrings" --allow-empty
|
||||
- name: Remove typeshed pyproject.toml file
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
|
||||
rm "${VENDORED_TYPESHED}/pyproject.toml"
|
||||
git commit -am "Remove pyproject.toml file"
|
||||
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||
- name: "Install Rust toolchain"
|
||||
if: ${{ success() }}
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
if: ${{ success() }}
|
||||
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- name: "Install cargo nextest"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
if: ${{ success() }}
|
||||
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Update snapshots
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
# The `cargo insta` docs indicate that `--unreferenced=delete` might be a good option,
|
||||
# but from local testing it appears to just revert all changes made by `cargo insta test --accept`.
|
||||
#
|
||||
# If there were only snapshot-related failures, `cargo insta test --accept` will have exit code 0,
|
||||
# but if there were also other mdtest failures (for example), it will return a nonzero exit code.
|
||||
# We don't care about other tests failing here, we just want snapshots updated where possible,
|
||||
# so we use `|| true` here to ignore the exit code.
|
||||
cargo insta test --accept --color=always --all-features --test-runner=nextest || true
|
||||
- name: Commit snapshot changes
|
||||
if: ${{ success() }}
|
||||
run: git commit -am "Update snapshots" || echo "No snapshot changes to commit"
|
||||
- name: Push changes upstream and create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
|
||||
git push
|
||||
- name: Create a PR
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
gh pr list --repo "${GITHUB_REPOSITORY}" --head "${UPSTREAM_BRANCH}" --json id --jq length | grep 1 && exit 0 # exit if there is existing pr
|
||||
gh pr create --title "[ty] Sync vendored typeshed stubs" --body "Close and reopen this PR to trigger CI" --label "ty"
|
||||
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -22,7 +22,7 @@ env:
|
||||
jobs:
|
||||
ty-ecosystem-analyzer:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
if: contains(github.event.label.name, 'ecosystem-analyzer')
|
||||
steps:
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -19,7 +19,7 @@ env:
|
||||
jobs:
|
||||
ty-ecosystem-report:
|
||||
name: Create ecosystem report
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
2
.github/workflows/typing_conformance.yaml
vendored
2
.github/workflows/typing_conformance.yaml
vendored
@@ -29,7 +29,7 @@ env:
|
||||
jobs:
|
||||
typing_conformance:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,63 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.1
|
||||
|
||||
Released on 2025-10-16.
|
||||
|
||||
### Preview features
|
||||
|
||||
- [formatter] Remove parentheses around multiple exception types on Python 3.14+ ([#20768](https://github.com/astral-sh/ruff/pull/20768))
|
||||
- \[`flake8-bugbear`\] Omit annotation in preview fix for `B006` ([#20877](https://github.com/astral-sh/ruff/pull/20877))
|
||||
- \[`flake8-logging-format`\] Avoid dropping implicitly concatenated pieces in the `G004` fix ([#20793](https://github.com/astral-sh/ruff/pull/20793))
|
||||
- \[`pydoclint`\] Implement `docstring-extraneous-parameter` (`DOC102`) ([#20376](https://github.com/astral-sh/ruff/pull/20376))
|
||||
- \[`pyupgrade`\] Extend `UP019` to detect `typing_extensions.Text` (`UP019`) ([#20825](https://github.com/astral-sh/ruff/pull/20825))
|
||||
- \[`pyupgrade`\] Fix false negative for `TypeVar` with default argument in `non-pep695-generic-class` (`UP046`) ([#20660](https://github.com/astral-sh/ruff/pull/20660))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix false negatives in `Truthiness::from_expr` for lambdas, generators, and f-strings ([#20704](https://github.com/astral-sh/ruff/pull/20704))
|
||||
- Fix syntax error false positives for escapes and quotes in f-strings ([#20867](https://github.com/astral-sh/ruff/pull/20867))
|
||||
- Fix syntax error false positives on parenthesized context managers ([#20846](https://github.com/astral-sh/ruff/pull/20846))
|
||||
- \[`fastapi`\] Fix false positives for path parameters that FastAPI doesn't recognize (`FAST003`) ([#20687](https://github.com/astral-sh/ruff/pull/20687))
|
||||
- \[`flake8-pyi`\] Fix operator precedence by adding parentheses when needed (`PYI061`) ([#20508](https://github.com/astral-sh/ruff/pull/20508))
|
||||
- \[`ruff`\] Suppress diagnostic for f-string interpolations with debug text (`RUF010`) ([#20525](https://github.com/astral-sh/ruff/pull/20525))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`airflow`\] Add warning to `airflow.datasets.DatasetEvent` usage (`AIR301`) ([#20551](https://github.com/astral-sh/ruff/pull/20551))
|
||||
- \[`flake8-bugbear`\] Mark `B905` and `B912` fixes as unsafe ([#20695](https://github.com/astral-sh/ruff/pull/20695))
|
||||
- Use `DiagnosticTag` for more rules - changes display in editors ([#20758](https://github.com/astral-sh/ruff/pull/20758),[#20734](https://github.com/astral-sh/ruff/pull/20734))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update Python compatibility from 3.13 to 3.14 in README.md ([#20852](https://github.com/astral-sh/ruff/pull/20852))
|
||||
- Update `lint.flake8-type-checking.quoted-annotations` docs ([#20765](https://github.com/astral-sh/ruff/pull/20765))
|
||||
- Update setup instructions for Zed 0.208.0+ ([#20902](https://github.com/astral-sh/ruff/pull/20902))
|
||||
- \[`flake8-datetimez`\] Clarify docs for several rules ([#20778](https://github.com/astral-sh/ruff/pull/20778))
|
||||
- Fix typo in `RUF015` description ([#20873](https://github.com/astral-sh/ruff/pull/20873))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Reduce binary size ([#20863](https://github.com/astral-sh/ruff/pull/20863))
|
||||
- Improved error recovery for unclosed strings (including f- and t-strings) ([#20848](https://github.com/astral-sh/ruff/pull/20848))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@Paillat-dev](https://github.com/Paillat-dev)
|
||||
- [@terror](https://github.com/terror)
|
||||
- [@pieterh-oai](https://github.com/pieterh-oai)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@TaKO8Ki](https://github.com/TaKO8Ki)
|
||||
- [@ageorgou](https://github.com/ageorgou)
|
||||
- [@danparizher](https://github.com/danparizher)
|
||||
- [@mgaitan](https://github.com/mgaitan)
|
||||
- [@augustelalande](https://github.com/augustelalande)
|
||||
- [@dylwil3](https://github.com/dylwil3)
|
||||
- [@Lee-W](https://github.com/Lee-W)
|
||||
- [@injust](https://github.com/injust)
|
||||
- [@CarrotManMatt](https://github.com/CarrotManMatt)
|
||||
|
||||
## 0.14.0
|
||||
|
||||
Released on 2025-10-07.
|
||||
|
||||
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -2815,7 +2815,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3072,7 +3072,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3426,7 +3426,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3540,8 +3540,8 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3564,13 +3564,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=ef9f9329be6923acd050c8dddd172e3bc93e8051#ef9f9329be6923acd050c8dddd172e3bc93e8051"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=29ab321b45d00daa4315fa2a06f7207759a8c87e#29ab321b45d00daa4315fa2a06f7207759a8c87e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -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 = "ef9f9329be6923acd050c8dddd172e3bc93e8051", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "29ab321b45d00daa4315fa2a06f7207759a8c87e", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -148,8 +148,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.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.1/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.0/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,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.1
|
||||
rev: v0.14.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -13,6 +13,7 @@ use log::{debug, warn};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_linter::codes::Rule;
|
||||
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
|
||||
use ruff_linter::message::create_syntax_error_diagnostic;
|
||||
use ruff_linter::package::PackageRoot;
|
||||
use ruff_linter::pyproject_toml::lint_pyproject_toml;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
@@ -102,7 +103,11 @@ impl Diagnostics {
|
||||
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
|
||||
let dummy = SourceFileBuilder::new(name, "").finish();
|
||||
Self::new(
|
||||
vec![Diagnostic::invalid_syntax(dummy, err, TextRange::default())],
|
||||
vec![create_syntax_error_diagnostic(
|
||||
dummy,
|
||||
err,
|
||||
TextRange::default(),
|
||||
)],
|
||||
FxHashMap::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,14 +84,17 @@ impl Diagnostic {
|
||||
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
|
||||
/// the other way around. And since we want to do this conversion in a couple
|
||||
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
|
||||
///
|
||||
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
|
||||
/// message.
|
||||
pub fn invalid_syntax(
|
||||
span: impl Into<Span>,
|
||||
message: impl IntoDiagnosticMessage,
|
||||
range: impl Ranged,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
|
||||
let span = span.into().with_range(range.range());
|
||||
diag.annotate(Annotation::primary(span));
|
||||
diag.annotate(Annotation::primary(span).message(message));
|
||||
diag
|
||||
}
|
||||
|
||||
|
||||
@@ -193,17 +193,6 @@ impl Files {
|
||||
roots.at(&absolute)
|
||||
}
|
||||
|
||||
/// The same as [`Self::root`] but panics if no root is found.
|
||||
#[track_caller]
|
||||
pub fn expect_root(&self, db: &dyn Db, path: &SystemPath) -> FileRoot {
|
||||
if let Some(root) = self.root(db, path) {
|
||||
return root;
|
||||
}
|
||||
|
||||
let roots = self.inner.roots.read().unwrap();
|
||||
panic!("No root found for path '{path}'. Known roots: {roots:#?}");
|
||||
}
|
||||
|
||||
/// Adds a new root for `path` and returns the root.
|
||||
///
|
||||
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
|
||||
|
||||
@@ -81,8 +81,6 @@ impl FileRoots {
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("Adding new file root '{path}' of kind {kind:?}");
|
||||
|
||||
// normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which matchit uses for routing parameters
|
||||
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
|
||||
|
||||
@@ -98,10 +98,6 @@ impl Db for ModuleDb {
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -11,7 +11,7 @@ from airflow import (
|
||||
)
|
||||
from airflow.api_connexion.security import requires_access
|
||||
from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent, DatasetEvent
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.operators.postgres_operator import Mapping
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.secrets.cache import SecretCache
|
||||
@@ -48,7 +48,6 @@ AWSAthenaHook()
|
||||
|
||||
# airflow.datasets
|
||||
DatasetAliasEvent()
|
||||
DatasetEvent()
|
||||
|
||||
|
||||
# airflow.operators.subdag.*
|
||||
|
||||
@@ -78,11 +78,3 @@ b: None | Literal[None] | None
|
||||
c: (None | Literal[None]) | None
|
||||
d: None | (Literal[None] | None)
|
||||
e: None | ((None | Literal[None]) | None) | None
|
||||
|
||||
# Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
a (int): The first number to add.
|
||||
b (int): The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
# DOC102
|
||||
def multiply_list_elements(lst):
|
||||
"""
|
||||
Multiplies each element in a list by a given multiplier.
|
||||
|
||||
Args:
|
||||
lst (list of int): A list of integers.
|
||||
multiplier (int): The multiplier for each element in the list.
|
||||
|
||||
Returns:
|
||||
list of int: A new list with each element multiplied.
|
||||
"""
|
||||
return [x * multiplier for x in lst]
|
||||
|
||||
|
||||
# DOC102
|
||||
def find_max_value():
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
Args:
|
||||
numbers (list of int): A list of integers to search through.
|
||||
|
||||
Returns:
|
||||
int: The maximum value found in the list.
|
||||
"""
|
||||
return max(numbers)
|
||||
|
||||
|
||||
# DOC102
|
||||
def create_user_profile(location="here"):
|
||||
"""
|
||||
Creates a user profile with basic information.
|
||||
|
||||
Args:
|
||||
name (str): The name of the user.
|
||||
age (int): The age of the user.
|
||||
email (str): The user's email address.
|
||||
location (str): The location of the user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the user's profile.
|
||||
"""
|
||||
return {
|
||||
'name': name,
|
||||
'age': age,
|
||||
'email': email,
|
||||
'location': location
|
||||
}
|
||||
|
||||
|
||||
# DOC102
|
||||
def calculate_total_price(item_prices, discount):
|
||||
"""
|
||||
Calculates the total price after applying tax and a discount.
|
||||
|
||||
Args:
|
||||
item_prices (list of float): A list of prices for each item.
|
||||
tax_rate (float): The tax rate to apply.
|
||||
discount (float): The discount to subtract from the total.
|
||||
|
||||
Returns:
|
||||
float: The final total price after tax and discount.
|
||||
"""
|
||||
total = sum(item_prices)
|
||||
total_with_tax = total + (total * tax_rate)
|
||||
final_total = total_with_tax - discount
|
||||
return final_total
|
||||
|
||||
|
||||
# DOC102
|
||||
def send_email(subject, body, bcc_address=None):
|
||||
"""
|
||||
Sends an email to the specified recipients.
|
||||
|
||||
Args:
|
||||
subject (str): The subject of the email.
|
||||
body (str): The content of the email.
|
||||
to_address (str): The recipient's email address.
|
||||
cc_address (str, optional): The email address for CC. Defaults to None.
|
||||
bcc_address (str, optional): The email address for BCC. Defaults to None.
|
||||
|
||||
Returns:
|
||||
bool: True if the email was sent successfully, False otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def concatenate_strings(*args):
|
||||
"""
|
||||
Concatenates multiple strings with a specified separator.
|
||||
|
||||
Args:
|
||||
separator (str): The separator to use between strings.
|
||||
*args (str): Variable length argument list of strings to concatenate.
|
||||
|
||||
Returns:
|
||||
str: A single concatenated string.
|
||||
"""
|
||||
return separator.join(args)
|
||||
|
||||
|
||||
# DOC102
|
||||
def process_order(order_id):
|
||||
"""
|
||||
Processes an order with a list of items and optional order details.
|
||||
|
||||
Args:
|
||||
order_id (int): The unique identifier for the order.
|
||||
*items (str): Variable length argument list of items in the order.
|
||||
**details (dict): Additional details such as shipping method and address.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the order summary.
|
||||
"""
|
||||
return {
|
||||
'order_id': order_id,
|
||||
'items': items,
|
||||
'details': details
|
||||
}
|
||||
|
||||
|
||||
class Calculator:
|
||||
"""
|
||||
A simple calculator class that can perform basic arithmetic operations.
|
||||
"""
|
||||
|
||||
# DOC102
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the calculator with an initial value.
|
||||
|
||||
Args:
|
||||
value (int, optional): The initial value of the calculator. Defaults to 0.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
# DOC102
|
||||
def add(self, number2):
|
||||
"""
|
||||
Adds a number to the current value.
|
||||
|
||||
Args:
|
||||
number (int or float): The number to add to the current value.
|
||||
|
||||
Returns:
|
||||
int or float: The updated value after addition.
|
||||
"""
|
||||
self.value += number + number2
|
||||
return self.value
|
||||
|
||||
# DOC102
|
||||
@classmethod
|
||||
def from_string(cls):
|
||||
"""
|
||||
Creates a Calculator instance from a string representation of a number.
|
||||
|
||||
Args:
|
||||
value_str (str): The string representing the initial value.
|
||||
|
||||
Returns:
|
||||
Calculator: A new instance of Calculator initialized with the value from the string.
|
||||
"""
|
||||
value = float(value_str)
|
||||
return cls(value)
|
||||
|
||||
# DOC102
|
||||
@staticmethod
|
||||
def is_valid_number():
|
||||
"""
|
||||
Checks if a given number is valid (int or float).
|
||||
|
||||
Args:
|
||||
number (any): The value to check.
|
||||
|
||||
Returns:
|
||||
bool: True if the number is valid, False otherwise.
|
||||
"""
|
||||
return isinstance(number, (int, float))
|
||||
|
||||
# OK
|
||||
def foo(param1, param2, *args, **kwargs):
|
||||
"""Foo.
|
||||
|
||||
Args:
|
||||
param1 (int): The first parameter.
|
||||
param2 (:obj:`str`, optional): The second parameter. Defaults to None.
|
||||
Second line of description: should be indented.
|
||||
*args: Variable length argument list.
|
||||
**kwargs: Arbitrary keyword arguments.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
def on_server_unloaded(self, server_context: ServerContext) -> None:
|
||||
''' Execute ``on_server_unloaded`` from ``server_lifecycle.py`` (if
|
||||
it is defined) when the server cleanly exits. (Before stopping the
|
||||
server's ``IOLoop``.)
|
||||
|
||||
Args:
|
||||
server_context (ServerContext) :
|
||||
|
||||
.. warning::
|
||||
In practice this code may not run, since servers are often killed
|
||||
by a signal.
|
||||
|
||||
|
||||
'''
|
||||
return self._lifecycle_handler.on_server_unloaded(server_context)
|
||||
|
||||
# OK
|
||||
def function_with_kwargs(param1, param2, **kwargs):
|
||||
"""Function with **kwargs parameter.
|
||||
|
||||
Args:
|
||||
param1 (int): The first parameter.
|
||||
param2 (str): The second parameter.
|
||||
extra_param (str): An extra parameter that may be passed via **kwargs.
|
||||
another_extra (int): Another extra parameter.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
b: The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return
|
||||
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Args:
|
||||
a: The first number to add.
|
||||
b: The second number to add.
|
||||
|
||||
Returns:
|
||||
int: The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
@@ -1,372 +0,0 @@
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : int
|
||||
The first number to add.
|
||||
b : int
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
|
||||
# DOC102
|
||||
def multiply_list_elements(lst):
|
||||
"""
|
||||
Multiplies each element in a list by a given multiplier.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lst : list of int
|
||||
A list of integers.
|
||||
multiplier : int
|
||||
The multiplier for each element in the list.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
A new list with each element multiplied.
|
||||
"""
|
||||
return [x * multiplier for x in lst]
|
||||
|
||||
|
||||
# DOC102
|
||||
def find_max_value():
|
||||
"""
|
||||
Finds the maximum value in a list of numbers.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
numbers : list of int
|
||||
A list of integers to search through.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The maximum value found in the list.
|
||||
"""
|
||||
return max(numbers)
|
||||
|
||||
|
||||
# DOC102
|
||||
def create_user_profile(location="here"):
|
||||
"""
|
||||
Creates a user profile with basic information.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the user.
|
||||
age : int
|
||||
The age of the user.
|
||||
email : str
|
||||
The user's email address.
|
||||
location : str, optional
|
||||
The location of the user, by default "here".
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary containing the user's profile.
|
||||
"""
|
||||
return {
|
||||
'name': name,
|
||||
'age': age,
|
||||
'email': email,
|
||||
'location': location
|
||||
}
|
||||
|
||||
|
||||
# DOC102
|
||||
def calculate_total_price(item_prices, discount):
|
||||
"""
|
||||
Calculates the total price after applying tax and a discount.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item_prices : list of float
|
||||
A list of prices for each item.
|
||||
tax_rate : float
|
||||
The tax rate to apply.
|
||||
discount : float
|
||||
The discount to subtract from the total.
|
||||
|
||||
Returns
|
||||
-------
|
||||
float
|
||||
The final total price after tax and discount.
|
||||
"""
|
||||
total = sum(item_prices)
|
||||
total_with_tax = total + (total * tax_rate)
|
||||
final_total = total_with_tax - discount
|
||||
return final_total
|
||||
|
||||
|
||||
# DOC102
|
||||
def send_email(subject, body, bcc_address=None):
|
||||
"""
|
||||
Sends an email to the specified recipients.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
The subject of the email.
|
||||
body : str
|
||||
The content of the email.
|
||||
to_address : str
|
||||
The recipient's email address.
|
||||
cc_address : str, optional
|
||||
The email address for CC, by default None.
|
||||
bcc_address : str, optional
|
||||
The email address for BCC, by default None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the email was sent successfully, False otherwise.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def concatenate_strings(*args):
|
||||
"""
|
||||
Concatenates multiple strings with a specified separator.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
separator : str
|
||||
The separator to use between strings.
|
||||
*args : str
|
||||
Variable length argument list of strings to concatenate.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
A single concatenated string.
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
# DOC102
|
||||
def process_order(order_id):
|
||||
"""
|
||||
Processes an order with a list of items and optional order details.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
order_id : int
|
||||
The unique identifier for the order.
|
||||
*items : str
|
||||
Variable length argument list of items in the order.
|
||||
**details : dict
|
||||
Additional details such as shipping method and address.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dictionary containing the order summary.
|
||||
"""
|
||||
return {
|
||||
'order_id': order_id,
|
||||
'items': items,
|
||||
'details': details
|
||||
}
|
||||
|
||||
|
||||
class Calculator:
|
||||
"""
|
||||
A simple calculator class that can perform basic arithmetic operations.
|
||||
"""
|
||||
|
||||
# DOC102
|
||||
def __init__(self):
|
||||
"""
|
||||
Initializes the calculator with an initial value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : int, optional
|
||||
The initial value of the calculator, by default 0.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
# DOC102
|
||||
def add(self, number2):
|
||||
"""
|
||||
Adds two numbers to the current value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
number : int or float
|
||||
The first number to add.
|
||||
number2 : int or float
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int or float
|
||||
The updated value after addition.
|
||||
"""
|
||||
self.value += number + number2
|
||||
return self.value
|
||||
|
||||
# DOC102
|
||||
@classmethod
|
||||
def from_string(cls):
|
||||
"""
|
||||
Creates a Calculator instance from a string representation of a number.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value_str : str
|
||||
The string representing the initial value.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Calculator
|
||||
A new instance of Calculator initialized with the value from the string.
|
||||
"""
|
||||
value = float(value_str)
|
||||
return cls(value)
|
||||
|
||||
# DOC102
|
||||
@staticmethod
|
||||
def is_valid_number():
|
||||
"""
|
||||
Checks if a given number is valid (int or float).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
number : any
|
||||
The value to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the number is valid, False otherwise.
|
||||
"""
|
||||
return isinstance(number, (int, float))
|
||||
|
||||
# OK
|
||||
def function_with_kwargs(param1, param2, **kwargs):
|
||||
"""Function with **kwargs parameter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
param1 : int
|
||||
The first parameter.
|
||||
param2 : str
|
||||
The second parameter.
|
||||
extra_param : str
|
||||
An extra parameter that may be passed via **kwargs.
|
||||
another_extra : int
|
||||
Another extra parameter.
|
||||
"""
|
||||
return True
|
||||
|
||||
# OK
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
b
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
# DOC102
|
||||
def add_numbers(b):
|
||||
"""
|
||||
Adds two numbers and returns the result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a
|
||||
The first number to add.
|
||||
b
|
||||
The second number to add.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The sum of the two numbers.
|
||||
"""
|
||||
return a + b
|
||||
|
||||
class Foo:
|
||||
# OK
|
||||
def send_help(self, *args: Any) -> Any:
|
||||
"""|coro|
|
||||
|
||||
Shows the help command for the specified entity if given.
|
||||
The entity can be a command or a cog.
|
||||
|
||||
If no entity is given, then it'll show help for the
|
||||
entire bot.
|
||||
|
||||
If the entity is a string, then it looks up whether it's a
|
||||
:class:`Cog` or a :class:`Command`.
|
||||
|
||||
.. note::
|
||||
|
||||
Due to the way this function works, instead of returning
|
||||
something similar to :meth:`~.commands.HelpCommand.command_not_found`
|
||||
this returns :class:`None` on bad input or no help command.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
entity: Optional[Union[:class:`Command`, :class:`Cog`, :class:`str`]]
|
||||
The entity to show help for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
The result of the help command, if any.
|
||||
"""
|
||||
return
|
||||
|
||||
# OK
|
||||
@classmethod
|
||||
async def convert(cls, ctx: Context, argument: str) -> Self:
|
||||
"""|coro|
|
||||
|
||||
The method that actually converters an argument to the flag mapping.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cls: Type[:class:`FlagConverter`]
|
||||
The flag converter class.
|
||||
ctx: :class:`Context`
|
||||
The invocation context.
|
||||
argument: :class:`str`
|
||||
The argument to convert from.
|
||||
|
||||
Raises
|
||||
------
|
||||
FlagError
|
||||
A flag related parsing error.
|
||||
CommandError
|
||||
A command related error.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:class:`FlagConverter`
|
||||
The flag converter instance with all flags parsed.
|
||||
"""
|
||||
return
|
||||
@@ -43,7 +43,7 @@ class Foo:
|
||||
T = typing.TypeVar(*args)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# `default` was added in Python 3.13
|
||||
# `default` should be skipped for now, added in Python 3.13
|
||||
T = typing.TypeVar("T", default=Any)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
@@ -90,9 +90,9 @@ PositiveList = TypeAliasType(
|
||||
"PositiveList2", list[Annotated[T, Gt(0)]], type_params=(T,)
|
||||
)
|
||||
|
||||
# `default` was added in Python 3.13
|
||||
# `default` should be skipped for now, added in Python 3.13
|
||||
T = typing.TypeVar("T", default=Any)
|
||||
AnyList = TypeAliasType("AnyList", list[T], type_params=(T,))
|
||||
AnyList = TypeAliasType("AnyList", list[T], typep_params=(T,))
|
||||
|
||||
# unsafe fix if comments within the fix
|
||||
T = TypeVar("T")
|
||||
@@ -128,7 +128,3 @@ T: TypeAlias = ( # comment0
|
||||
str # comment6
|
||||
# comment7
|
||||
) # comment8
|
||||
|
||||
# Test case for TypeVar with default - should be converted when preview mode is enabled
|
||||
T_default = TypeVar("T_default", default=int)
|
||||
DefaultList: TypeAlias = list[T_default]
|
||||
|
||||
@@ -122,7 +122,7 @@ class MixedGenerics[U]:
|
||||
return (u, t)
|
||||
|
||||
|
||||
# default requires 3.13
|
||||
# TODO(brent) default requires 3.13
|
||||
V = TypeVar("V", default=Any, bound=str)
|
||||
|
||||
|
||||
@@ -130,14 +130,6 @@ class DefaultTypeVar(Generic[V]): # -> [V: str = Any]
|
||||
var: V
|
||||
|
||||
|
||||
# Test case for TypeVar with default but no bound
|
||||
W = TypeVar("W", default=int)
|
||||
|
||||
|
||||
class DefaultOnlyTypeVar(Generic[W]): # -> [W = int]
|
||||
var: W
|
||||
|
||||
|
||||
# nested classes and functions are skipped
|
||||
class Outer:
|
||||
class Inner(Generic[T]):
|
||||
|
||||
@@ -44,7 +44,9 @@ def any_str_param(s: AnyStr) -> AnyStr:
|
||||
return s
|
||||
|
||||
|
||||
# default requires 3.13
|
||||
# these cases are not handled
|
||||
|
||||
# TODO(brent) default requires 3.13
|
||||
V = TypeVar("V", default=Any, bound=str)
|
||||
|
||||
|
||||
@@ -52,8 +54,6 @@ def default_var(v: V) -> V:
|
||||
return v
|
||||
|
||||
|
||||
# these cases are not handled
|
||||
|
||||
def outer():
|
||||
def inner(t: T) -> T:
|
||||
return t
|
||||
|
||||
@@ -81,7 +81,6 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
Rule::UndocumentedPublicPackage,
|
||||
]);
|
||||
let enforce_pydoclint = checker.any_rule_enabled(&[
|
||||
Rule::DocstringExtraneousParameter,
|
||||
Rule::DocstringMissingReturns,
|
||||
Rule::DocstringExtraneousReturns,
|
||||
Rule::DocstringMissingYields,
|
||||
|
||||
@@ -50,6 +50,15 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::nonlocal_and_global(checker, nonlocal);
|
||||
}
|
||||
}
|
||||
Stmt::Continue(_) => {
|
||||
if checker.is_rule_enabled(Rule::ContinueOutsideLoop) {
|
||||
pyflakes::rules::continue_outside_loop(
|
||||
checker,
|
||||
stmt,
|
||||
&mut checker.semantic.current_statements().skip(1),
|
||||
);
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(
|
||||
function_def @ ast::StmtFunctionDef {
|
||||
is_async,
|
||||
|
||||
@@ -711,12 +711,6 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.report_diagnostic(pyflakes::rules::BreakOutsideLoop, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ContinueOutsideLoop => {
|
||||
// F702
|
||||
if self.is_rule_enabled(Rule::ContinueOutsideLoop) {
|
||||
self.report_diagnostic(pyflakes::rules::ContinueOutsideLoop, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||
|
||||
@@ -988,7 +988,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(FastApi, "003") => (RuleGroup::Stable, rules::fastapi::rules::FastApiUnusedPathParameter),
|
||||
|
||||
// pydoclint
|
||||
(Pydoclint, "102") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringExtraneousParameter),
|
||||
(Pydoclint, "201") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingReturns),
|
||||
(Pydoclint, "202") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringExtraneousReturns),
|
||||
(Pydoclint, "402") => (RuleGroup::Preview, rules::pydoclint::rules::DocstringMissingYields),
|
||||
|
||||
@@ -24,6 +24,7 @@ use crate::checkers::tokens::check_tokens;
|
||||
use crate::directives::Directives;
|
||||
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
|
||||
use crate::fix::{FixResult, fix_file};
|
||||
use crate::message::create_syntax_error_diagnostic;
|
||||
use crate::noqa::add_noqa;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::registry::Rule;
|
||||
@@ -495,15 +496,15 @@ fn diagnostics_to_messages(
|
||||
parse_errors
|
||||
.iter()
|
||||
.map(|parse_error| {
|
||||
Diagnostic::invalid_syntax(source_file.clone(), &parse_error.error, parse_error)
|
||||
create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error)
|
||||
})
|
||||
.chain(unsupported_syntax_errors.iter().map(|syntax_error| {
|
||||
Diagnostic::invalid_syntax(source_file.clone(), syntax_error, syntax_error)
|
||||
create_syntax_error_diagnostic(source_file.clone(), syntax_error, syntax_error)
|
||||
}))
|
||||
.chain(
|
||||
semantic_syntax_errors
|
||||
.iter()
|
||||
.map(|error| Diagnostic::invalid_syntax(source_file.clone(), error, error)),
|
||||
.map(|error| create_syntax_error_diagnostic(source_file.clone(), error, error)),
|
||||
)
|
||||
.chain(diagnostics.into_iter().map(|mut diagnostic| {
|
||||
if let Some(range) = diagnostic.range() {
|
||||
|
||||
@@ -16,7 +16,7 @@ use ruff_db::files::File;
|
||||
pub use grouped::GroupedEmitter;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::{SourceFile, SourceFileBuilder};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
pub use sarif::SarifEmitter;
|
||||
|
||||
use crate::Fix;
|
||||
@@ -26,6 +26,24 @@ use crate::settings::types::{OutputFormat, RuffOutputFormat};
|
||||
mod grouped;
|
||||
mod sarif;
|
||||
|
||||
/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff.
|
||||
///
|
||||
/// This is almost identical to `ruff_db::diagnostic::create_syntax_error_diagnostic`, except the
|
||||
/// `message` is stored as the primary diagnostic message instead of on the primary annotation.
|
||||
///
|
||||
/// TODO(brent) These should be unified at some point, but we keep them separate for now to avoid a
|
||||
/// ton of snapshot changes while combining ruff's diagnostic type with `Diagnostic`.
|
||||
pub fn create_syntax_error_diagnostic(
|
||||
span: impl Into<Span>,
|
||||
message: impl std::fmt::Display,
|
||||
range: impl Ranged,
|
||||
) -> Diagnostic {
|
||||
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
|
||||
let span = span.into().with_range(range.range());
|
||||
diag.annotate(Annotation::primary(span));
|
||||
diag
|
||||
}
|
||||
|
||||
/// Create a `Diagnostic` from a panic.
|
||||
pub fn create_panic_diagnostic(error: &PanicError, path: Option<&Path>) -> Diagnostic {
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
@@ -242,6 +260,8 @@ mod tests {
|
||||
use crate::message::{Emitter, EmitterContext, create_lint_diagnostic};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
use super::create_syntax_error_diagnostic;
|
||||
|
||||
pub(super) fn create_syntax_error_diagnostics() -> Vec<Diagnostic> {
|
||||
let source = r"from os import
|
||||
|
||||
@@ -254,7 +274,7 @@ if call(foo
|
||||
.errors()
|
||||
.iter()
|
||||
.map(|parse_error| {
|
||||
Diagnostic::invalid_syntax(source_file.clone(), &parse_error.error, parse_error)
|
||||
create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -242,11 +242,6 @@ pub(crate) const fn is_refined_submodule_import_match_enabled(settings: &LinterS
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20660
|
||||
pub(crate) const fn is_type_var_default_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// github.com/astral-sh/ruff/issues/20004
|
||||
pub(crate) const fn is_b006_check_guaranteed_mutable_expr_enabled(
|
||||
settings: &LinterSettings,
|
||||
|
||||
@@ -655,11 +655,6 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
},
|
||||
// airflow.datasets
|
||||
["airflow", "datasets", "DatasetAliasEvent"] => Replacement::None,
|
||||
["airflow", "datasets", "DatasetEvent"] => Replacement::Message(
|
||||
"`DatasetEvent` has been made private in Airflow 3. \
|
||||
Use `dict[str, Any]` for the time being. \
|
||||
An `AssetEvent` type will be added to the apache-airflow-task-sdk in a future version.",
|
||||
),
|
||||
|
||||
// airflow.hooks
|
||||
["airflow", "hooks", "base_hook", "BaseHook"] => Replacement::Rename {
|
||||
|
||||
@@ -104,49 +104,38 @@ AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
49 | # airflow.datasets
|
||||
50 | DatasetAliasEvent()
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
51 | DatasetEvent()
|
||||
|
|
||||
|
||||
AIR301 `airflow.datasets.DatasetEvent` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:51:1
|
||||
|
|
||||
49 | # airflow.datasets
|
||||
50 | DatasetAliasEvent()
|
||||
51 | DatasetEvent()
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: `DatasetEvent` has been made private in Airflow 3. Use `dict[str, Any]` for the time being. An `AssetEvent` type will be added to the apache-airflow-task-sdk in a future version.
|
||||
|
||||
AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:55:1
|
||||
--> AIR301_names.py:54:1
|
||||
|
|
||||
54 | # airflow.operators.subdag.*
|
||||
55 | SubDagOperator()
|
||||
53 | # airflow.operators.subdag.*
|
||||
54 | SubDagOperator()
|
||||
| ^^^^^^^^^^^^^^
|
||||
56 |
|
||||
57 | # airflow.operators.postgres_operator
|
||||
55 |
|
||||
56 | # airflow.operators.postgres_operator
|
||||
|
|
||||
help: The whole `airflow.subdag` module has been removed.
|
||||
|
||||
AIR301 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:58:1
|
||||
--> AIR301_names.py:57:1
|
||||
|
|
||||
57 | # airflow.operators.postgres_operator
|
||||
58 | Mapping()
|
||||
56 | # airflow.operators.postgres_operator
|
||||
57 | Mapping()
|
||||
| ^^^^^^^
|
||||
59 |
|
||||
60 | # airflow.secrets
|
||||
58 |
|
||||
59 | # airflow.secrets
|
||||
|
|
||||
|
||||
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:65:1
|
||||
--> AIR301_names.py:64:1
|
||||
|
|
||||
64 | # airflow.secrets.cache
|
||||
65 | SecretCache()
|
||||
63 | # airflow.secrets.cache
|
||||
64 | SecretCache()
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
help: Use `SecretCache` from `airflow.sdk` instead.
|
||||
14 | from airflow.datasets import DatasetAliasEvent, DatasetEvent
|
||||
14 | from airflow.datasets import DatasetAliasEvent
|
||||
15 | from airflow.operators.postgres_operator import Mapping
|
||||
16 | from airflow.operators.subdag import SubDagOperator
|
||||
- from airflow.secrets.cache import SecretCache
|
||||
@@ -164,211 +153,211 @@ help: Use `SecretCache` from `airflow.sdk` instead.
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:69:1
|
||||
--> AIR301_names.py:68:1
|
||||
|
|
||||
68 | # airflow.triggers.external_task
|
||||
69 | TaskStateTrigger()
|
||||
67 | # airflow.triggers.external_task
|
||||
68 | TaskStateTrigger()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
70 |
|
||||
71 | # airflow.utils.date
|
||||
69 |
|
||||
70 | # airflow.utils.date
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:72:1
|
||||
--> AIR301_names.py:71:1
|
||||
|
|
||||
71 | # airflow.utils.date
|
||||
72 | dates.date_range
|
||||
70 | # airflow.utils.date
|
||||
71 | dates.date_range
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
73 | dates.days_ago
|
||||
72 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:73:1
|
||||
--> AIR301_names.py:72:1
|
||||
|
|
||||
71 | # airflow.utils.date
|
||||
72 | dates.date_range
|
||||
73 | dates.days_ago
|
||||
70 | # airflow.utils.date
|
||||
71 | dates.date_range
|
||||
72 | dates.days_ago
|
||||
| ^^^^^^^^^^^^^^
|
||||
74 |
|
||||
75 | date_range
|
||||
73 |
|
||||
74 | date_range
|
||||
|
|
||||
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:75:1
|
||||
--> AIR301_names.py:74:1
|
||||
|
|
||||
73 | dates.days_ago
|
||||
74 |
|
||||
75 | date_range
|
||||
72 | dates.days_ago
|
||||
73 |
|
||||
74 | date_range
|
||||
| ^^^^^^^^^^
|
||||
76 | days_ago
|
||||
77 | infer_time_unit
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:76:1
|
||||
--> AIR301_names.py:75:1
|
||||
|
|
||||
75 | date_range
|
||||
76 | days_ago
|
||||
74 | date_range
|
||||
75 | days_ago
|
||||
| ^^^^^^^^
|
||||
77 | infer_time_unit
|
||||
78 | parse_execution_date
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
|
|
||||
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:77:1
|
||||
--> AIR301_names.py:76:1
|
||||
|
|
||||
75 | date_range
|
||||
76 | days_ago
|
||||
77 | infer_time_unit
|
||||
74 | date_range
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^
|
||||
78 | parse_execution_date
|
||||
79 | round_time
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:78:1
|
||||
--> AIR301_names.py:77:1
|
||||
|
|
||||
76 | days_ago
|
||||
77 | infer_time_unit
|
||||
78 | parse_execution_date
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
79 | round_time
|
||||
80 | scale_time_units
|
||||
78 | round_time
|
||||
79 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:79:1
|
||||
--> AIR301_names.py:78:1
|
||||
|
|
||||
77 | infer_time_unit
|
||||
78 | parse_execution_date
|
||||
79 | round_time
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
| ^^^^^^^^^^
|
||||
80 | scale_time_units
|
||||
79 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:80:1
|
||||
--> AIR301_names.py:79:1
|
||||
|
|
||||
78 | parse_execution_date
|
||||
79 | round_time
|
||||
80 | scale_time_units
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
79 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
81 |
|
||||
82 | # This one was not deprecated.
|
||||
80 |
|
||||
81 | # This one was not deprecated.
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:87:1
|
||||
--> AIR301_names.py:86:1
|
||||
|
|
||||
86 | # airflow.utils.dag_cycle_tester
|
||||
87 | test_cycle
|
||||
85 | # airflow.utils.dag_cycle_tester
|
||||
86 | test_cycle
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:91:1
|
||||
--> AIR301_names.py:90:1
|
||||
|
|
||||
90 | # airflow.utils.db
|
||||
91 | create_session
|
||||
89 | # airflow.utils.db
|
||||
90 | create_session
|
||||
| ^^^^^^^^^^^^^^
|
||||
92 |
|
||||
93 | # airflow.utils.decorators
|
||||
91 |
|
||||
92 | # airflow.utils.decorators
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:94:1
|
||||
--> AIR301_names.py:93:1
|
||||
|
|
||||
93 | # airflow.utils.decorators
|
||||
94 | apply_defaults
|
||||
92 | # airflow.utils.decorators
|
||||
93 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^
|
||||
95 |
|
||||
96 | # airflow.utils.file
|
||||
94 |
|
||||
95 | # airflow.utils.file
|
||||
|
|
||||
help: `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
|
||||
AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:97:1
|
||||
--> AIR301_names.py:96:1
|
||||
|
|
||||
96 | # airflow.utils.file
|
||||
97 | mkdirs
|
||||
95 | # airflow.utils.file
|
||||
96 | mkdirs
|
||||
| ^^^^^^
|
||||
|
|
||||
help: Use `pathlib.Path({path}).mkdir` instead
|
||||
|
||||
AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:101:1
|
||||
--> AIR301_names.py:100:1
|
||||
|
|
||||
100 | # airflow.utils.state
|
||||
101 | SHUTDOWN
|
||||
99 | # airflow.utils.state
|
||||
100 | SHUTDOWN
|
||||
| ^^^^^^^^
|
||||
102 | terminating_states
|
||||
101 | terminating_states
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:102:1
|
||||
--> AIR301_names.py:101:1
|
||||
|
|
||||
100 | # airflow.utils.state
|
||||
101 | SHUTDOWN
|
||||
102 | terminating_states
|
||||
99 | # airflow.utils.state
|
||||
100 | SHUTDOWN
|
||||
101 | terminating_states
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
103 |
|
||||
104 | # airflow.utils.trigger_rule
|
||||
102 |
|
||||
103 | # airflow.utils.trigger_rule
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:105:1
|
||||
--> AIR301_names.py:104:1
|
||||
|
|
||||
104 | # airflow.utils.trigger_rule
|
||||
105 | TriggerRule.DUMMY
|
||||
103 | # airflow.utils.trigger_rule
|
||||
104 | TriggerRule.DUMMY
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
106 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:106:1
|
||||
--> AIR301_names.py:105:1
|
||||
|
|
||||
104 | # airflow.utils.trigger_rule
|
||||
105 | TriggerRule.DUMMY
|
||||
106 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
103 | # airflow.utils.trigger_rule
|
||||
104 | TriggerRule.DUMMY
|
||||
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:110:1
|
||||
--> AIR301_names.py:109:1
|
||||
|
|
||||
109 | # airflow.www.auth
|
||||
110 | has_access
|
||||
108 | # airflow.www.auth
|
||||
109 | has_access
|
||||
| ^^^^^^^^^^
|
||||
111 | has_access_dataset
|
||||
110 | has_access_dataset
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:111:1
|
||||
--> AIR301_names.py:110:1
|
||||
|
|
||||
109 | # airflow.www.auth
|
||||
110 | has_access
|
||||
111 | has_access_dataset
|
||||
108 | # airflow.www.auth
|
||||
109 | has_access
|
||||
110 | has_access_dataset
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
112 |
|
||||
113 | # airflow.www.utils
|
||||
111 |
|
||||
112 | # airflow.www.utils
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:114:1
|
||||
--> AIR301_names.py:113:1
|
||||
|
|
||||
113 | # airflow.www.utils
|
||||
114 | get_sensitive_variables_fields
|
||||
112 | # airflow.www.utils
|
||||
113 | get_sensitive_variables_fields
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
115 | should_hide_value_for_key
|
||||
114 | should_hide_value_for_key
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:115:1
|
||||
--> AIR301_names.py:114:1
|
||||
|
|
||||
113 | # airflow.www.utils
|
||||
114 | get_sensitive_variables_fields
|
||||
115 | should_hide_value_for_key
|
||||
112 | # airflow.www.utils
|
||||
113 | get_sensitive_variables_fields
|
||||
114 | should_hide_value_for_key
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -4,8 +4,6 @@ use ruff_python_ast::{
|
||||
self as ast, Expr, ExprBinOp, ExprContext, ExprNoneLiteral, Operator, PythonVersion,
|
||||
helpers::{pep_604_union, typing_optional},
|
||||
name::Name,
|
||||
operator_precedence::OperatorPrecedence,
|
||||
parenthesize::parenthesized_range,
|
||||
};
|
||||
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -240,19 +238,7 @@ fn create_fix(
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
});
|
||||
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
|
||||
|
||||
// Check if we need parentheses to preserve operator precedence
|
||||
let content = if needs_parentheses_for_precedence(
|
||||
semantic,
|
||||
literal_expr,
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
format!("({})", checker.generator().expr(&union_expr))
|
||||
} else {
|
||||
checker.generator().expr(&union_expr)
|
||||
};
|
||||
|
||||
let content = checker.generator().expr(&union_expr);
|
||||
let union_edit = Edit::range_replacement(content, literal_expr.range());
|
||||
Fix::applicable_edit(union_edit, applicability)
|
||||
}
|
||||
@@ -270,37 +256,3 @@ enum UnionKind {
|
||||
TypingOptional,
|
||||
BitOr,
|
||||
}
|
||||
|
||||
/// Check if the union expression needs parentheses to preserve operator precedence.
|
||||
/// This is needed when the union is part of a larger expression where the `|` operator
|
||||
/// has lower precedence than the surrounding operations (like attribute access).
|
||||
fn needs_parentheses_for_precedence(
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
literal_expr: &Expr,
|
||||
comment_ranges: &ruff_python_trivia::CommentRanges,
|
||||
source: &str,
|
||||
) -> bool {
|
||||
// Get the parent expression to check if we're in a context that needs parentheses
|
||||
let Some(parent_expr) = semantic.current_expression_parent() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if the literal expression is already parenthesized
|
||||
if parenthesized_range(
|
||||
literal_expr.into(),
|
||||
parent_expr.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
return false; // Already parenthesized, don't add more
|
||||
}
|
||||
|
||||
// Check if the parent expression has higher precedence than the `|` operator
|
||||
let union_precedence = OperatorPrecedence::BitOr;
|
||||
let parent_precedence = OperatorPrecedence::from(parent_expr);
|
||||
|
||||
// If the parent operation has higher precedence than `|`, we need parentheses
|
||||
parent_precedence > union_precedence
|
||||
}
|
||||
|
||||
@@ -423,117 +423,5 @@ PYI061 Use `None` rather than `Literal[None]`
|
||||
79 | d: None | (Literal[None] | None)
|
||||
80 | e: None | ((None | Literal[None]) | None) | None
|
||||
| ^^^^
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
|
|
||||
help: Replace with `None`
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:83:18
|
||||
|
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
| ^^^^
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
80 | e: None | ((None | Literal[None]) | None) | None
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
- print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
83 + print((Literal[1] | None).__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:84:18
|
||||
|
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
| ^^^^
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
- print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
84 + print((Literal[1] | None).method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:85:18
|
||||
|
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
| ^^^^
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
- print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
85 + print((Literal[1] | None)[0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:86:18
|
||||
|
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
| ^^^^
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
- print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
86 + print((Literal[1] | None) + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:87:18
|
||||
|
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
| ^^^^
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
- print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
87 + print((Literal[1] | None) * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Literal[...] | None` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:88:19
|
||||
|
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Literal[...] | None`
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
- print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
88 + print((Literal[1] | None).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
@@ -465,153 +465,5 @@ PYI061 Use `None` rather than `Literal[None]`
|
||||
79 | d: None | (Literal[None] | None)
|
||||
80 | e: None | ((None | Literal[None]) | None) | None
|
||||
| ^^^^
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
|
|
||||
help: Replace with `None`
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:83:18
|
||||
|
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
| ^^^^
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
80 | e: None | ((None | Literal[None]) | None) | None
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
- print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
83 + print(Optional[Literal[1]].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:84:18
|
||||
|
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
| ^^^^
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
81 |
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
- print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
84 + print(Optional[Literal[1]].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:85:18
|
||||
|
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
| ^^^^
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
82 | # Test cases for operator precedence issue (https://github.com/astral-sh/ruff/issues/20265)
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
- print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
85 + print(Optional[Literal[1]][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:86:18
|
||||
|
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
| ^^^^
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
83 | print(Literal[1, None].__dict__) # Should become (Literal[1] | None).__dict__
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
- print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
86 + print(Optional[Literal[1]] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:87:18
|
||||
|
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
| ^^^^
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
84 | print(Literal[1, None].method()) # Should become (Literal[1] | None).method()
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
- print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
87 + print(Optional[Literal[1]] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
PYI061 [*] Use `Optional[Literal[...]]` rather than `Literal[None, ...]`
|
||||
--> PYI061.py:88:19
|
||||
|
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
88 | print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
| ^^^^
|
||||
|
|
||||
help: Replace with `Optional[Literal[...]]`
|
||||
- from typing import Literal, Union
|
||||
1 + from typing import Literal, Union, Optional
|
||||
2 |
|
||||
3 |
|
||||
4 | def func1(arg1: Literal[None]):
|
||||
--------------------------------------------------------------------------------
|
||||
85 | print(Literal[1, None][0]) # Should become (Literal[1] | None)[0]
|
||||
86 | print(Literal[1, None] + 1) # Should become (Literal[1] | None) + 1
|
||||
87 | print(Literal[1, None] * 2) # Should become (Literal[1] | None) * 2
|
||||
- print((Literal[1, None]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
88 + print((Optional[Literal[1]]).__dict__) # Should become ((Literal[1] | None)).__dict__
|
||||
|
||||
@@ -28,7 +28,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::DocstringExtraneousParameter, Path::new("DOC102_google.py"))]
|
||||
#[test_case(Rule::DocstringMissingReturns, Path::new("DOC201_google.py"))]
|
||||
#[test_case(Rule::DocstringExtraneousReturns, Path::new("DOC202_google.py"))]
|
||||
#[test_case(Rule::DocstringMissingYields, Path::new("DOC402_google.py"))]
|
||||
@@ -51,7 +50,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::DocstringExtraneousParameter, Path::new("DOC102_numpy.py"))]
|
||||
#[test_case(Rule::DocstringMissingReturns, Path::new("DOC201_numpy.py"))]
|
||||
#[test_case(Rule::DocstringExtraneousReturns, Path::new("DOC202_numpy.py"))]
|
||||
#[test_case(Rule::DocstringMissingYields, Path::new("DOC402_numpy.py"))]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use itertools::Itertools;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::{map_callable, map_subscript};
|
||||
use ruff_python_ast::helpers::map_callable;
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt, visitor};
|
||||
use ruff_python_semantic::analyze::{function_type, visibility};
|
||||
use ruff_python_semantic::{Definition, SemanticModel};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ruff_source_file::NewlineWithTrailingNewline;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -18,62 +18,6 @@ use crate::docstrings::styles::SectionStyle;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::pydocstyle::settings::Convention;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for function docstrings that include parameters which are not
|
||||
/// in the function signature.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If a docstring documents a parameter which is not in the function signature,
|
||||
/// it can be misleading to users and/or a sign of incomplete documentation or
|
||||
/// refactors.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def calculate_speed(distance: float, time: float) -> float:
|
||||
/// """Calculate speed as distance divided by time.
|
||||
///
|
||||
/// Args:
|
||||
/// distance: Distance traveled.
|
||||
/// time: Time spent traveling.
|
||||
/// acceleration: Rate of change of speed.
|
||||
///
|
||||
/// Returns:
|
||||
/// Speed as distance divided by time.
|
||||
/// """
|
||||
/// return distance / time
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def calculate_speed(distance: float, time: float) -> float:
|
||||
/// """Calculate speed as distance divided by time.
|
||||
///
|
||||
/// Args:
|
||||
/// distance: Distance traveled.
|
||||
/// time: Time spent traveling.
|
||||
///
|
||||
/// Returns:
|
||||
/// Speed as distance divided by time.
|
||||
/// """
|
||||
/// return distance / time
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct DocstringExtraneousParameter {
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl Violation for DocstringExtraneousParameter {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let DocstringExtraneousParameter { id } = self;
|
||||
format!("Documented parameter `{id}` is not in the function's signature")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Remove the extraneous parameter from the docstring".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for functions with `return` statements that do not have "Returns"
|
||||
/// sections in their docstrings.
|
||||
@@ -452,19 +396,6 @@ impl GenericSection {
|
||||
}
|
||||
}
|
||||
|
||||
/// A parameter in a docstring with its text range.
|
||||
#[derive(Debug, Clone)]
|
||||
struct ParameterEntry<'a> {
|
||||
name: &'a str,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl Ranged for ParameterEntry<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
/// A "Raises" section in a docstring.
|
||||
#[derive(Debug)]
|
||||
struct RaisesSection<'a> {
|
||||
@@ -483,46 +414,17 @@ impl<'a> RaisesSection<'a> {
|
||||
/// a "Raises" section.
|
||||
fn from_section(section: &SectionContext<'a>, style: Option<SectionStyle>) -> Self {
|
||||
Self {
|
||||
raised_exceptions: parse_raises(section.following_lines_str(), style),
|
||||
raised_exceptions: parse_entries(section.following_lines_str(), style),
|
||||
range: section.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An "Args" or "Parameters" section in a docstring.
|
||||
#[derive(Debug)]
|
||||
struct ParametersSection<'a> {
|
||||
parameters: Vec<ParameterEntry<'a>>,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl Ranged for ParametersSection<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ParametersSection<'a> {
|
||||
/// Return the parameters for the docstring, or `None` if the docstring does not contain
|
||||
/// an "Args" or "Parameters" section.
|
||||
fn from_section(section: &SectionContext<'a>, style: Option<SectionStyle>) -> Self {
|
||||
Self {
|
||||
parameters: parse_parameters(
|
||||
section.following_lines_str(),
|
||||
section.following_range().start(),
|
||||
style,
|
||||
),
|
||||
range: section.section_name_range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct DocstringSections<'a> {
|
||||
returns: Option<GenericSection>,
|
||||
yields: Option<GenericSection>,
|
||||
raises: Option<RaisesSection<'a>>,
|
||||
parameters: Option<ParametersSection<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DocstringSections<'a> {
|
||||
@@ -530,10 +432,6 @@ impl<'a> DocstringSections<'a> {
|
||||
let mut docstring_sections = Self::default();
|
||||
for section in sections {
|
||||
match section.kind() {
|
||||
SectionKind::Args | SectionKind::Arguments | SectionKind::Parameters => {
|
||||
docstring_sections.parameters =
|
||||
Some(ParametersSection::from_section(§ion, style));
|
||||
}
|
||||
SectionKind::Raises => {
|
||||
docstring_sections.raises = Some(RaisesSection::from_section(§ion, style));
|
||||
}
|
||||
@@ -550,142 +448,18 @@ impl<'a> DocstringSections<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the entries in a "Parameters" section of a docstring.
|
||||
///
|
||||
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
|
||||
/// entries are found.
|
||||
fn parse_parameters(
|
||||
content: &str,
|
||||
content_start: TextSize,
|
||||
style: Option<SectionStyle>,
|
||||
) -> Vec<ParameterEntry<'_>> {
|
||||
match style {
|
||||
Some(SectionStyle::Google) => parse_parameters_google(content, content_start),
|
||||
Some(SectionStyle::Numpy) => parse_parameters_numpy(content, content_start),
|
||||
None => {
|
||||
let entries = parse_parameters_google(content, content_start);
|
||||
if entries.is_empty() {
|
||||
parse_parameters_numpy(content, content_start)
|
||||
} else {
|
||||
entries
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses Google-style "Args" sections of the form:
|
||||
///
|
||||
/// ```python
|
||||
/// Args:
|
||||
/// a (int): The first number to add.
|
||||
/// b (int): The second number to add.
|
||||
/// ```
|
||||
fn parse_parameters_google(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> {
|
||||
let mut entries: Vec<ParameterEntry> = Vec::new();
|
||||
// Find first entry to determine indentation
|
||||
let Some(first_arg) = content.lines().next() else {
|
||||
return entries;
|
||||
};
|
||||
let indentation = &first_arg[..first_arg.len() - first_arg.trim_start().len()];
|
||||
|
||||
let mut current_pos = TextSize::ZERO;
|
||||
for line in content.lines() {
|
||||
let line_start = current_pos;
|
||||
current_pos = content.full_line_end(line_start);
|
||||
|
||||
if let Some(entry) = line.strip_prefix(indentation) {
|
||||
if entry
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|first_char| !first_char.is_whitespace())
|
||||
{
|
||||
let Some((before_colon, _)) = entry.split_once(':') else {
|
||||
continue;
|
||||
};
|
||||
if let Some(param) = before_colon.split_whitespace().next() {
|
||||
let param_name = param.trim_start_matches('*');
|
||||
if is_identifier(param_name) {
|
||||
let param_start = line_start + indentation.text_len();
|
||||
let param_end = param_start + param.text_len();
|
||||
|
||||
entries.push(ParameterEntry {
|
||||
name: param_name,
|
||||
range: TextRange::new(
|
||||
content_start + param_start,
|
||||
content_start + param_end,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
/// Parses NumPy-style "Parameters" sections of the form:
|
||||
///
|
||||
/// ```python
|
||||
/// Parameters
|
||||
/// ----------
|
||||
/// a : int
|
||||
/// The first number to add.
|
||||
/// b : int
|
||||
/// The second number to add.
|
||||
/// ```
|
||||
fn parse_parameters_numpy(content: &str, content_start: TextSize) -> Vec<ParameterEntry<'_>> {
|
||||
let mut entries: Vec<ParameterEntry> = Vec::new();
|
||||
let mut lines = content.lines();
|
||||
let Some(dashes) = lines.next() else {
|
||||
return entries;
|
||||
};
|
||||
let indentation = &dashes[..dashes.len() - dashes.trim_start().len()];
|
||||
|
||||
let mut current_pos = content.full_line_end(dashes.text_len());
|
||||
for potential in lines {
|
||||
let line_start = current_pos;
|
||||
current_pos = content.full_line_end(line_start);
|
||||
|
||||
if let Some(entry) = potential.strip_prefix(indentation) {
|
||||
if entry
|
||||
.chars()
|
||||
.next()
|
||||
.is_some_and(|first_char| !first_char.is_whitespace())
|
||||
{
|
||||
if let Some(before_colon) = entry.split(':').next() {
|
||||
let param = before_colon.trim_end();
|
||||
let param_name = param.trim_start_matches('*');
|
||||
if is_identifier(param_name) {
|
||||
let param_start = line_start + indentation.text_len();
|
||||
let param_end = param_start + param.text_len();
|
||||
|
||||
entries.push(ParameterEntry {
|
||||
name: param_name,
|
||||
range: TextRange::new(
|
||||
content_start + param_start,
|
||||
content_start + param_end,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
entries
|
||||
}
|
||||
|
||||
/// Parse the entries in a "Raises" section of a docstring.
|
||||
///
|
||||
/// Attempts to parse using the specified [`SectionStyle`], falling back to the other style if no
|
||||
/// entries are found.
|
||||
fn parse_raises(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName<'_>> {
|
||||
fn parse_entries(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName<'_>> {
|
||||
match style {
|
||||
Some(SectionStyle::Google) => parse_raises_google(content),
|
||||
Some(SectionStyle::Numpy) => parse_raises_numpy(content),
|
||||
Some(SectionStyle::Google) => parse_entries_google(content),
|
||||
Some(SectionStyle::Numpy) => parse_entries_numpy(content),
|
||||
None => {
|
||||
let entries = parse_raises_google(content);
|
||||
let entries = parse_entries_google(content);
|
||||
if entries.is_empty() {
|
||||
parse_raises_numpy(content)
|
||||
parse_entries_numpy(content)
|
||||
} else {
|
||||
entries
|
||||
}
|
||||
@@ -693,14 +467,14 @@ fn parse_raises(content: &str, style: Option<SectionStyle>) -> Vec<QualifiedName
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses Google-style "Raises" section of the form:
|
||||
/// Parses Google-style docstring sections of the form:
|
||||
///
|
||||
/// ```python
|
||||
/// Raises:
|
||||
/// FasterThanLightError: If speed is greater than the speed of light.
|
||||
/// DivisionByZero: If attempting to divide by zero.
|
||||
/// ```
|
||||
fn parse_raises_google(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
fn parse_entries_google(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
let mut entries: Vec<QualifiedName> = Vec::new();
|
||||
for potential in content.lines() {
|
||||
let Some(colon_idx) = potential.find(':') else {
|
||||
@@ -712,7 +486,7 @@ fn parse_raises_google(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
entries
|
||||
}
|
||||
|
||||
/// Parses NumPy-style "Raises" section of the form:
|
||||
/// Parses NumPy-style docstring sections of the form:
|
||||
///
|
||||
/// ```python
|
||||
/// Raises
|
||||
@@ -722,7 +496,7 @@ fn parse_raises_google(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
/// DivisionByZero
|
||||
/// If attempting to divide by zero.
|
||||
/// ```
|
||||
fn parse_raises_numpy(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
fn parse_entries_numpy(content: &str) -> Vec<QualifiedName<'_>> {
|
||||
let mut entries: Vec<QualifiedName> = Vec::new();
|
||||
let mut lines = content.lines();
|
||||
let Some(dashes) = lines.next() else {
|
||||
@@ -1093,17 +867,6 @@ fn is_generator_function_annotated_as_returning_none(
|
||||
.is_some_and(GeneratorOrIteratorArguments::indicates_none_returned)
|
||||
}
|
||||
|
||||
fn parameters_from_signature<'a>(docstring: &'a Docstring) -> Vec<&'a str> {
|
||||
let mut parameters = Vec::new();
|
||||
let Some(function) = docstring.definition.as_function_def() else {
|
||||
return parameters;
|
||||
};
|
||||
for param in &function.parameters {
|
||||
parameters.push(param.name());
|
||||
}
|
||||
parameters
|
||||
}
|
||||
|
||||
fn is_one_line(docstring: &Docstring) -> bool {
|
||||
let mut non_empty_line_count = 0;
|
||||
for line in NewlineWithTrailingNewline::from(docstring.body().as_str()) {
|
||||
@@ -1117,7 +880,7 @@ fn is_one_line(docstring: &Docstring) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// DOC102, DOC201, DOC202, DOC402, DOC403, DOC501, DOC502
|
||||
/// DOC201, DOC202, DOC402, DOC403, DOC501, DOC502
|
||||
pub(crate) fn check_docstring(
|
||||
checker: &Checker,
|
||||
definition: &Definition,
|
||||
@@ -1157,8 +920,6 @@ pub(crate) fn check_docstring(
|
||||
visitor.finish()
|
||||
};
|
||||
|
||||
let signature_parameters = parameters_from_signature(docstring);
|
||||
|
||||
// DOC201
|
||||
if checker.is_rule_enabled(Rule::DocstringMissingReturns) {
|
||||
if should_document_returns(function_def)
|
||||
@@ -1247,25 +1008,6 @@ pub(crate) fn check_docstring(
|
||||
}
|
||||
}
|
||||
|
||||
// DOC102
|
||||
if checker.is_rule_enabled(Rule::DocstringExtraneousParameter) {
|
||||
// Don't report extraneous parameters if the signature defines *args or **kwargs
|
||||
if function_def.parameters.vararg.is_none() && function_def.parameters.kwarg.is_none() {
|
||||
if let Some(docstring_params) = docstring_sections.parameters {
|
||||
for docstring_param in &docstring_params.parameters {
|
||||
if !signature_parameters.contains(&docstring_param.name) {
|
||||
checker.report_diagnostic(
|
||||
DocstringExtraneousParameter {
|
||||
id: docstring_param.name.to_string(),
|
||||
},
|
||||
docstring_param.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid applying "extraneous" rules to abstract methods. An abstract method's docstring _could_
|
||||
// document that it raises an exception without including the exception in the implementation.
|
||||
if !visibility::is_abstract(&function_def.decorator_list, semantic) {
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
|
||||
---
|
||||
DOC102 Documented parameter `a` is not in the function's signature
|
||||
--> DOC102_google.py:7:9
|
||||
|
|
||||
6 | Args:
|
||||
7 | a (int): The first number to add.
|
||||
| ^
|
||||
8 | b (int): The second number to add.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `multiplier` is not in the function's signature
|
||||
--> DOC102_google.py:23:9
|
||||
|
|
||||
21 | Args:
|
||||
22 | lst (list of int): A list of integers.
|
||||
23 | multiplier (int): The multiplier for each element in the list.
|
||||
| ^^^^^^^^^^
|
||||
24 |
|
||||
25 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `numbers` is not in the function's signature
|
||||
--> DOC102_google.py:37:9
|
||||
|
|
||||
36 | Args:
|
||||
37 | numbers (list of int): A list of integers to search through.
|
||||
| ^^^^^^^
|
||||
38 |
|
||||
39 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `name` is not in the function's signature
|
||||
--> DOC102_google.py:51:9
|
||||
|
|
||||
50 | Args:
|
||||
51 | name (str): The name of the user.
|
||||
| ^^^^
|
||||
52 | age (int): The age of the user.
|
||||
53 | email (str): The user's email address.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `age` is not in the function's signature
|
||||
--> DOC102_google.py:52:9
|
||||
|
|
||||
50 | Args:
|
||||
51 | name (str): The name of the user.
|
||||
52 | age (int): The age of the user.
|
||||
| ^^^
|
||||
53 | email (str): The user's email address.
|
||||
54 | location (str): The location of the user.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `email` is not in the function's signature
|
||||
--> DOC102_google.py:53:9
|
||||
|
|
||||
51 | name (str): The name of the user.
|
||||
52 | age (int): The age of the user.
|
||||
53 | email (str): The user's email address.
|
||||
| ^^^^^
|
||||
54 | location (str): The location of the user.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `tax_rate` is not in the function's signature
|
||||
--> DOC102_google.py:74:9
|
||||
|
|
||||
72 | Args:
|
||||
73 | item_prices (list of float): A list of prices for each item.
|
||||
74 | tax_rate (float): The tax rate to apply.
|
||||
| ^^^^^^^^
|
||||
75 | discount (float): The discount to subtract from the total.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `to_address` is not in the function's signature
|
||||
--> DOC102_google.py:94:9
|
||||
|
|
||||
92 | subject (str): The subject of the email.
|
||||
93 | body (str): The content of the email.
|
||||
94 | to_address (str): The recipient's email address.
|
||||
| ^^^^^^^^^^
|
||||
95 | cc_address (str, optional): The email address for CC. Defaults to None.
|
||||
96 | bcc_address (str, optional): The email address for BCC. Defaults to None.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `cc_address` is not in the function's signature
|
||||
--> DOC102_google.py:95:9
|
||||
|
|
||||
93 | body (str): The content of the email.
|
||||
94 | to_address (str): The recipient's email address.
|
||||
95 | cc_address (str, optional): The email address for CC. Defaults to None.
|
||||
| ^^^^^^^^^^
|
||||
96 | bcc_address (str, optional): The email address for BCC. Defaults to None.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `items` is not in the function's signature
|
||||
--> DOC102_google.py:126:9
|
||||
|
|
||||
124 | Args:
|
||||
125 | order_id (int): The unique identifier for the order.
|
||||
126 | *items (str): Variable length argument list of items in the order.
|
||||
| ^^^^^^
|
||||
127 | **details (dict): Additional details such as shipping method and address.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `details` is not in the function's signature
|
||||
--> DOC102_google.py:127:9
|
||||
|
|
||||
125 | order_id (int): The unique identifier for the order.
|
||||
126 | *items (str): Variable length argument list of items in the order.
|
||||
127 | **details (dict): Additional details such as shipping method and address.
|
||||
| ^^^^^^^^^
|
||||
128 |
|
||||
129 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `value` is not in the function's signature
|
||||
--> DOC102_google.py:150:13
|
||||
|
|
||||
149 | Args:
|
||||
150 | value (int, optional): The initial value of the calculator. Defaults to 0.
|
||||
| ^^^^^
|
||||
151 | """
|
||||
152 | self.value = value
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `number` is not in the function's signature
|
||||
--> DOC102_google.py:160:13
|
||||
|
|
||||
159 | Args:
|
||||
160 | number (int or float): The number to add to the current value.
|
||||
| ^^^^^^
|
||||
161 |
|
||||
162 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `value_str` is not in the function's signature
|
||||
--> DOC102_google.py:175:13
|
||||
|
|
||||
174 | Args:
|
||||
175 | value_str (str): The string representing the initial value.
|
||||
| ^^^^^^^^^
|
||||
176 |
|
||||
177 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `number` is not in the function's signature
|
||||
--> DOC102_google.py:190:13
|
||||
|
|
||||
189 | Args:
|
||||
190 | number (any): The value to check.
|
||||
| ^^^^^^
|
||||
191 |
|
||||
192 | Returns:
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `a` is not in the function's signature
|
||||
--> DOC102_google.py:258:9
|
||||
|
|
||||
257 | Args:
|
||||
258 | a: The first number to add.
|
||||
| ^
|
||||
259 | b: The second number to add.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
@@ -1,189 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pydoclint/mod.rs
|
||||
---
|
||||
DOC102 Documented parameter `a` is not in the function's signature
|
||||
--> DOC102_numpy.py:8:5
|
||||
|
|
||||
6 | Parameters
|
||||
7 | ----------
|
||||
8 | a : int
|
||||
| ^
|
||||
9 | The first number to add.
|
||||
10 | b : int
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `multiplier` is not in the function's signature
|
||||
--> DOC102_numpy.py:30:5
|
||||
|
|
||||
28 | lst : list of int
|
||||
29 | A list of integers.
|
||||
30 | multiplier : int
|
||||
| ^^^^^^^^^^
|
||||
31 | The multiplier for each element in the list.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `numbers` is not in the function's signature
|
||||
--> DOC102_numpy.py:48:5
|
||||
|
|
||||
46 | Parameters
|
||||
47 | ----------
|
||||
48 | numbers : list of int
|
||||
| ^^^^^^^
|
||||
49 | A list of integers to search through.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `name` is not in the function's signature
|
||||
--> DOC102_numpy.py:66:5
|
||||
|
|
||||
64 | Parameters
|
||||
65 | ----------
|
||||
66 | name : str
|
||||
| ^^^^
|
||||
67 | The name of the user.
|
||||
68 | age : int
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `age` is not in the function's signature
|
||||
--> DOC102_numpy.py:68:5
|
||||
|
|
||||
66 | name : str
|
||||
67 | The name of the user.
|
||||
68 | age : int
|
||||
| ^^^
|
||||
69 | The age of the user.
|
||||
70 | email : str
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `email` is not in the function's signature
|
||||
--> DOC102_numpy.py:70:5
|
||||
|
|
||||
68 | age : int
|
||||
69 | The age of the user.
|
||||
70 | email : str
|
||||
| ^^^^^
|
||||
71 | The user's email address.
|
||||
72 | location : str, optional
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `tax_rate` is not in the function's signature
|
||||
--> DOC102_numpy.py:97:5
|
||||
|
|
||||
95 | item_prices : list of float
|
||||
96 | A list of prices for each item.
|
||||
97 | tax_rate : float
|
||||
| ^^^^^^^^
|
||||
98 | The tax rate to apply.
|
||||
99 | discount : float
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `to_address` is not in the function's signature
|
||||
--> DOC102_numpy.py:124:5
|
||||
|
|
||||
122 | body : str
|
||||
123 | The content of the email.
|
||||
124 | to_address : str
|
||||
| ^^^^^^^^^^
|
||||
125 | The recipient's email address.
|
||||
126 | cc_address : str, optional
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `cc_address` is not in the function's signature
|
||||
--> DOC102_numpy.py:126:5
|
||||
|
|
||||
124 | to_address : str
|
||||
125 | The recipient's email address.
|
||||
126 | cc_address : str, optional
|
||||
| ^^^^^^^^^^
|
||||
127 | The email address for CC, by default None.
|
||||
128 | bcc_address : str, optional
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `items` is not in the function's signature
|
||||
--> DOC102_numpy.py:168:5
|
||||
|
|
||||
166 | order_id : int
|
||||
167 | The unique identifier for the order.
|
||||
168 | *items : str
|
||||
| ^^^^^^
|
||||
169 | Variable length argument list of items in the order.
|
||||
170 | **details : dict
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `details` is not in the function's signature
|
||||
--> DOC102_numpy.py:170:5
|
||||
|
|
||||
168 | *items : str
|
||||
169 | Variable length argument list of items in the order.
|
||||
170 | **details : dict
|
||||
| ^^^^^^^^^
|
||||
171 | Additional details such as shipping method and address.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `value` is not in the function's signature
|
||||
--> DOC102_numpy.py:197:9
|
||||
|
|
||||
195 | Parameters
|
||||
196 | ----------
|
||||
197 | value : int, optional
|
||||
| ^^^^^
|
||||
198 | The initial value of the calculator, by default 0.
|
||||
199 | """
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `number` is not in the function's signature
|
||||
--> DOC102_numpy.py:209:9
|
||||
|
|
||||
207 | Parameters
|
||||
208 | ----------
|
||||
209 | number : int or float
|
||||
| ^^^^^^
|
||||
210 | The first number to add.
|
||||
211 | number2 : int or float
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `value_str` is not in the function's signature
|
||||
--> DOC102_numpy.py:230:9
|
||||
|
|
||||
228 | Parameters
|
||||
229 | ----------
|
||||
230 | value_str : str
|
||||
| ^^^^^^^^^
|
||||
231 | The string representing the initial value.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `number` is not in the function's signature
|
||||
--> DOC102_numpy.py:249:9
|
||||
|
|
||||
247 | Parameters
|
||||
248 | ----------
|
||||
249 | number : any
|
||||
| ^^^^^^
|
||||
250 | The value to check.
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
|
||||
DOC102 Documented parameter `a` is not in the function's signature
|
||||
--> DOC102_numpy.py:300:5
|
||||
|
|
||||
298 | Parameters
|
||||
299 | ----------
|
||||
300 | a
|
||||
| ^
|
||||
301 | The first number to add.
|
||||
302 | b
|
||||
|
|
||||
help: Remove the extraneous parameter from the docstring
|
||||
@@ -1,6 +1,9 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
|
||||
use crate::Violation;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{Violation, checkers::ast::Checker};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `continue` statements outside of loops.
|
||||
@@ -26,3 +29,28 @@ impl Violation for ContinueOutsideLoop {
|
||||
"`continue` not properly in loop".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// F702
|
||||
pub(crate) fn continue_outside_loop<'a>(
|
||||
checker: &Checker,
|
||||
stmt: &'a Stmt,
|
||||
parents: &mut impl Iterator<Item = &'a Stmt>,
|
||||
) {
|
||||
let mut child = stmt;
|
||||
for parent in parents {
|
||||
match parent {
|
||||
Stmt::For(ast::StmtFor { orelse, .. }) | Stmt::While(ast::StmtWhile { orelse, .. }) => {
|
||||
if !orelse.contains(child) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
|
||||
checker.report_diagnostic(ContinueOutsideLoop, stmt.range());
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ mod tests {
|
||||
use crate::rules::{isort, pyupgrade};
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::{test_path, test_snippet};
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
#[test_case(Rule::ConvertNamedTupleFunctionalToClass, Path::new("UP014.py"))]
|
||||
#[test_case(Rule::ConvertTypedDictFunctionalToClass, Path::new("UP013.py"))]
|
||||
@@ -140,28 +140,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.py"))]
|
||||
#[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.pyi"))]
|
||||
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_0.py"))]
|
||||
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_1.py"))]
|
||||
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047.py"))]
|
||||
fn type_var_default_preview(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}__preview_diff", path.to_string_lossy());
|
||||
assert_diagnostics_diff!(
|
||||
snapshot,
|
||||
Path::new("pyupgrade").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Disabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_0.py"))]
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_1.py"))]
|
||||
#[test_case(Rule::QuotedAnnotation, Path::new("UP037_2.pyi"))]
|
||||
|
||||
@@ -14,14 +14,13 @@ use ruff_python_ast::{
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_type_var_default_enabled;
|
||||
|
||||
pub(crate) use non_pep695_generic_class::*;
|
||||
pub(crate) use non_pep695_generic_function::*;
|
||||
pub(crate) use non_pep695_type_alias::*;
|
||||
pub(crate) use private_type_parameter::*;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
mod non_pep695_generic_class;
|
||||
mod non_pep695_generic_function;
|
||||
mod non_pep695_type_alias;
|
||||
@@ -123,10 +122,6 @@ impl Display for DisplayTypeVar<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(default) = self.type_var.default {
|
||||
f.write_str(" = ")?;
|
||||
f.write_str(&self.source[default.range()])?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -138,63 +133,66 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam {
|
||||
name,
|
||||
restriction,
|
||||
kind,
|
||||
default,
|
||||
default: _, // TODO(brent) see below
|
||||
}: &'a TypeVar<'a>,
|
||||
) -> Self {
|
||||
let default = default.map(|expr| Box::new(expr.clone()));
|
||||
match kind {
|
||||
TypeParamKind::TypeVar => TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
bound: match restriction {
|
||||
Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())),
|
||||
Some(TypeVarRestriction::Constraint(constraints)) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: constraints.iter().map(|expr| (*expr).clone()).collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})))
|
||||
}
|
||||
Some(TypeVarRestriction::AnyStr) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: vec![
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("str"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("bytes"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
],
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})))
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
default,
|
||||
}),
|
||||
TypeParamKind::TypeVar => {
|
||||
TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
bound: match restriction {
|
||||
Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())),
|
||||
Some(TypeVarRestriction::Constraint(constraints)) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: constraints.iter().map(|expr| (*expr).clone()).collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})))
|
||||
}
|
||||
Some(TypeVarRestriction::AnyStr) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
elts: vec![
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("str"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
Expr::Name(ExprName {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
id: Name::from("bytes"),
|
||||
ctx: ast::ExprContext::Load,
|
||||
}),
|
||||
],
|
||||
ctx: ast::ExprContext::Load,
|
||||
parenthesized: true,
|
||||
})))
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
// We don't handle defaults here yet. Should perhaps be a different rule since
|
||||
// defaults are only valid in 3.13+.
|
||||
default: None,
|
||||
})
|
||||
}
|
||||
TypeParamKind::TypeVarTuple => TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
default,
|
||||
default: None,
|
||||
}),
|
||||
TypeParamKind::ParamSpec => TypeParam::ParamSpec(TypeParamParamSpec {
|
||||
range: TextRange::default(),
|
||||
node_index: ruff_python_ast::AtomicNodeIndex::NONE,
|
||||
name: Identifier::new(*name, TextRange::default()),
|
||||
default,
|
||||
default: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -320,8 +318,8 @@ pub(crate) fn expr_name_to_type_var<'a>(
|
||||
.first()
|
||||
.is_some_and(Expr::is_string_literal_expr)
|
||||
{
|
||||
// `default` was added in PEP 696 and Python 3.13. We now support converting
|
||||
// TypeVars with defaults to PEP 695 type parameters.
|
||||
// TODO(brent) `default` was added in PEP 696 and Python 3.13 but can't be used in
|
||||
// generic type parameters before that
|
||||
//
|
||||
// ```python
|
||||
// T = TypeVar("T", default=Any, bound=str)
|
||||
@@ -369,22 +367,21 @@ fn in_nested_context(checker: &Checker) -> bool {
|
||||
}
|
||||
|
||||
/// Deduplicate `vars`, returning `None` if `vars` is empty or any duplicates are found.
|
||||
/// Also returns `None` if any `TypeVar` has a default value and preview mode is not enabled.
|
||||
fn check_type_vars<'a>(vars: Vec<TypeVar<'a>>, checker: &Checker) -> Option<Vec<TypeVar<'a>>> {
|
||||
fn check_type_vars(vars: Vec<TypeVar<'_>>) -> Option<Vec<TypeVar<'_>>> {
|
||||
if vars.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If any type variables have defaults and preview mode is not enabled, skip the rule
|
||||
if vars.iter().any(|tv| tv.default.is_some())
|
||||
&& !is_type_var_default_enabled(checker.settings())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// If any type variables were not unique, just bail out here. this is a runtime error and we
|
||||
// can't predict what the user wanted.
|
||||
(vars.iter().unique_by(|tvar| tvar.name).count() == vars.len()).then_some(vars)
|
||||
// can't predict what the user wanted. also bail out if any Python 3.13+ default values are
|
||||
// found on the type parameters
|
||||
(vars
|
||||
.iter()
|
||||
.unique_by(|tvar| tvar.name)
|
||||
.filter(|tvar| tvar.default.is_none())
|
||||
.count()
|
||||
== vars.len())
|
||||
.then_some(vars)
|
||||
}
|
||||
|
||||
/// Search `class_bases` for a `typing.Generic` base class. Returns the `Generic` expression (if
|
||||
|
||||
@@ -186,7 +186,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD
|
||||
//
|
||||
// just because we can't confirm that `SomethingElse` is a `TypeVar`
|
||||
if !visitor.any_skipped {
|
||||
let Some(type_vars) = check_type_vars(visitor.vars, checker) else {
|
||||
let Some(type_vars) = check_type_vars(visitor.vars) else {
|
||||
diagnostic.defuse();
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -154,7 +154,7 @@ pub(crate) fn non_pep695_generic_function(checker: &Checker, function_def: &Stmt
|
||||
}
|
||||
}
|
||||
|
||||
let Some(type_vars) = check_type_vars(type_vars, checker) else {
|
||||
let Some(type_vars) = check_type_vars(type_vars) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ use ruff_python_ast::{Expr, ExprCall, ExprName, Keyword, StmtAnnAssign, StmtAssi
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_type_var_default_enabled;
|
||||
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
@@ -233,10 +232,8 @@ pub(crate) fn non_pep695_type_alias(checker: &Checker, stmt: &StmtAnnAssign) {
|
||||
.unique_by(|tvar| tvar.name)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Skip if any TypeVar has defaults and preview mode is not enabled
|
||||
if vars.iter().any(|tv| tv.default.is_some())
|
||||
&& !is_type_var_default_enabled(checker.settings())
|
||||
{
|
||||
// TODO(brent) handle `default` arg for Python 3.13+
|
||||
if vars.iter().any(|tv| tv.default.is_some()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keywo
|
||||
44 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
45 |
|
||||
46 | # `default` was added in Python 3.13
|
||||
46 | # `default` should be skipped for now, added in Python 3.13
|
||||
|
|
||||
help: Use the `type` keyword
|
||||
41 |
|
||||
@@ -226,7 +226,7 @@ help: Use the `type` keyword
|
||||
- x: typing.TypeAlias = list[T]
|
||||
44 + type x = list[T]
|
||||
45 |
|
||||
46 | # `default` was added in Python 3.13
|
||||
46 | # `default` should be skipped for now, added in Python 3.13
|
||||
47 | T = typing.TypeVar("T", default=Any)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -355,26 +355,6 @@ help: Use the `type` keyword
|
||||
87 | # OK: Other name
|
||||
88 | T = TypeVar("T", bound=SupportGt)
|
||||
|
||||
UP040 [*] Type alias `AnyList` uses `TypeAliasType` assignment instead of the `type` keyword
|
||||
--> UP040.py:95:1
|
||||
|
|
||||
93 | # `default` was added in Python 3.13
|
||||
94 | T = typing.TypeVar("T", default=Any)
|
||||
95 | AnyList = TypeAliasType("AnyList", list[T], type_params=(T,))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
96 |
|
||||
97 | # unsafe fix if comments within the fix
|
||||
|
|
||||
help: Use the `type` keyword
|
||||
92 |
|
||||
93 | # `default` was added in Python 3.13
|
||||
94 | T = typing.TypeVar("T", default=Any)
|
||||
- AnyList = TypeAliasType("AnyList", list[T], type_params=(T,))
|
||||
95 + type AnyList[T = Any] = list[T]
|
||||
96 |
|
||||
97 | # unsafe fix if comments within the fix
|
||||
98 | T = TypeVar("T")
|
||||
|
||||
UP040 [*] Type alias `PositiveList` uses `TypeAliasType` assignment instead of the `type` keyword
|
||||
--> UP040.py:99:1
|
||||
|
|
||||
@@ -489,8 +469,6 @@ UP040 [*] Type alias `T` uses `TypeAlias` annotation instead of the `type` keywo
|
||||
129 | | # comment7
|
||||
130 | | ) # comment8
|
||||
| |_^
|
||||
131 |
|
||||
132 | # Test case for TypeVar with default - should be converted when preview mode is enabled
|
||||
|
|
||||
help: Use the `type` keyword
|
||||
119 | | str
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 2
|
||||
|
||||
--- Added ---
|
||||
UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
--> UP040.py:48:1
|
||||
|
|
||||
46 | # `default` was added in Python 3.13
|
||||
47 | T = typing.TypeVar("T", default=Any)
|
||||
48 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 |
|
||||
50 | # OK
|
||||
|
|
||||
help: Use the `type` keyword
|
||||
45 |
|
||||
46 | # `default` was added in Python 3.13
|
||||
47 | T = typing.TypeVar("T", default=Any)
|
||||
- x: typing.TypeAlias = list[T]
|
||||
48 + type x[T = Any] = list[T]
|
||||
49 |
|
||||
50 | # OK
|
||||
51 | x: TypeAlias
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
UP040 [*] Type alias `DefaultList` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
--> UP040.py:134:1
|
||||
|
|
||||
132 | # Test case for TypeVar with default - should be converted when preview mode is enabled
|
||||
133 | T_default = TypeVar("T_default", default=int)
|
||||
134 | DefaultList: TypeAlias = list[T_default]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use the `type` keyword
|
||||
131 |
|
||||
132 | # Test case for TypeVar with default - should be converted when preview mode is enabled
|
||||
133 | T_default = TypeVar("T_default", default=int)
|
||||
- DefaultList: TypeAlias = list[T_default]
|
||||
134 + type DefaultList[T_default = int] = list[T_default]
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 0
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 2
|
||||
|
||||
--- Added ---
|
||||
UP046 [*] Generic class `DefaultTypeVar` uses `Generic` subclass instead of type parameters
|
||||
--> UP046_0.py:129:22
|
||||
|
|
||||
129 | class DefaultTypeVar(Generic[V]): # -> [V: str = Any]
|
||||
| ^^^^^^^^^^
|
||||
130 | var: V
|
||||
|
|
||||
help: Use type parameters
|
||||
126 | V = TypeVar("V", default=Any, bound=str)
|
||||
127 |
|
||||
128 |
|
||||
- class DefaultTypeVar(Generic[V]): # -> [V: str = Any]
|
||||
129 + class DefaultTypeVar[V: str = Any]: # -> [V: str = Any]
|
||||
130 | var: V
|
||||
131 |
|
||||
132 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
UP046 [*] Generic class `DefaultOnlyTypeVar` uses `Generic` subclass instead of type parameters
|
||||
--> UP046_0.py:137:26
|
||||
|
|
||||
137 | class DefaultOnlyTypeVar(Generic[W]): # -> [W = int]
|
||||
| ^^^^^^^^^^
|
||||
138 | var: W
|
||||
|
|
||||
help: Use type parameters
|
||||
134 | W = TypeVar("W", default=int)
|
||||
135 |
|
||||
136 |
|
||||
- class DefaultOnlyTypeVar(Generic[W]): # -> [W = int]
|
||||
137 + class DefaultOnlyTypeVar[W = int]: # -> [W = int]
|
||||
138 | var: W
|
||||
139 |
|
||||
140 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 0
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 1
|
||||
|
||||
--- Added ---
|
||||
UP047 [*] Generic function `default_var` should use type parameters
|
||||
--> UP047.py:51:5
|
||||
|
|
||||
51 | def default_var(v: V) -> V:
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
52 | return v
|
||||
|
|
||||
help: Use type parameters
|
||||
48 | V = TypeVar("V", default=Any, bound=str)
|
||||
49 |
|
||||
50 |
|
||||
- def default_var(v: V) -> V:
|
||||
51 + def default_var[V: str = Any](v: V) -> V:
|
||||
52 | return v
|
||||
53 |
|
||||
54 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -26,7 +26,7 @@ use ruff_source_file::SourceFileBuilder;
|
||||
use crate::codes::Rule;
|
||||
use crate::fix::{FixResult, fix_file};
|
||||
use crate::linter::check_path;
|
||||
use crate::message::EmitterContext;
|
||||
use crate::message::{EmitterContext, create_syntax_error_diagnostic};
|
||||
use crate::package::PackageRoot;
|
||||
use crate::packaging::detect_package_root;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
@@ -405,7 +405,7 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e
|
||||
diagnostic
|
||||
})
|
||||
.chain(parsed.errors().iter().map(|parse_error| {
|
||||
Diagnostic::invalid_syntax(source_code.clone(), &parse_error.error, parse_error)
|
||||
create_syntax_error_diagnostic(source_code.clone(), &parse_error.error, parse_error)
|
||||
}))
|
||||
.sorted_by(Diagnostic::ruff_start_ordering)
|
||||
.collect();
|
||||
@@ -419,7 +419,7 @@ fn print_syntax_errors(errors: &[ParseError], path: &Path, source: &SourceKind)
|
||||
let messages: Vec<_> = errors
|
||||
.iter()
|
||||
.map(|parse_error| {
|
||||
Diagnostic::invalid_syntax(source_file.clone(), &parse_error.error, parse_error)
|
||||
create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -706,6 +706,8 @@ f'{1:hy "user"}'
|
||||
f'{1: abcd "{1}" }'
|
||||
f'{1: abcd "{'aa'}" }'
|
||||
f'{1=: "abcd {'aa'}}'
|
||||
# FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
# spec, which is valid even before 3.12.
|
||||
f'{x:a{z:hy "user"}} \'\'\''
|
||||
|
||||
# Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
@@ -748,7 +750,3 @@ print(f"{ # Tuple with multiple elements that doesn't fit on a single line gets
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/15536
|
||||
print(f"{ {}, 1, }")
|
||||
|
||||
|
||||
# The inner quotes should not be changed to double quotes before Python 3.12
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
|
||||
@@ -144,12 +144,6 @@ pub(crate) enum InterpolatedStringState {
|
||||
///
|
||||
/// The containing `FStringContext` is the surrounding f-string context.
|
||||
InsideInterpolatedElement(InterpolatedStringContext),
|
||||
/// The formatter is inside more than one nested f-string, such as in `nested` in:
|
||||
///
|
||||
/// ```py
|
||||
/// f"{f'''{'nested'} inner'''} outer"
|
||||
/// ```
|
||||
NestedInterpolatedElement(InterpolatedStringContext),
|
||||
/// The formatter is outside an f-string.
|
||||
#[default]
|
||||
Outside,
|
||||
@@ -158,18 +152,12 @@ pub(crate) enum InterpolatedStringState {
|
||||
impl InterpolatedStringState {
|
||||
pub(crate) fn can_contain_line_breaks(self) -> Option<bool> {
|
||||
match self {
|
||||
InterpolatedStringState::InsideInterpolatedElement(context)
|
||||
| InterpolatedStringState::NestedInterpolatedElement(context) => {
|
||||
InterpolatedStringState::InsideInterpolatedElement(context) => {
|
||||
Some(context.is_multiline())
|
||||
}
|
||||
InterpolatedStringState::Outside => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the interpolated string state is [`NestedInterpolatedElement`].
|
||||
pub(crate) fn is_nested(self) -> bool {
|
||||
matches!(self, Self::NestedInterpolatedElement(..))
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of a top-level statement in the module.
|
||||
|
||||
@@ -181,16 +181,10 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
|
||||
|
||||
let item = format_with(|f: &mut PyFormatter| {
|
||||
// Update the context to be inside the f-string expression element.
|
||||
let state = match f.context().interpolated_string_state() {
|
||||
InterpolatedStringState::InsideInterpolatedElement(_)
|
||||
| InterpolatedStringState::NestedInterpolatedElement(_) => {
|
||||
InterpolatedStringState::NestedInterpolatedElement(self.context)
|
||||
}
|
||||
InterpolatedStringState::Outside => {
|
||||
InterpolatedStringState::InsideInterpolatedElement(self.context)
|
||||
}
|
||||
};
|
||||
let f = &mut WithInterpolatedStringState::new(state, f);
|
||||
let f = &mut WithInterpolatedStringState::new(
|
||||
InterpolatedStringState::InsideInterpolatedElement(self.context),
|
||||
f,
|
||||
);
|
||||
|
||||
write!(f, [bracket_spacing, expression.format()])?;
|
||||
|
||||
|
||||
@@ -46,15 +46,8 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
|
||||
.unwrap_or(self.context.options().quote_style());
|
||||
let supports_pep_701 = self.context.options().target_version().supports_pep_701();
|
||||
|
||||
// Preserve the existing quote style for nested interpolations more than one layer deep, if
|
||||
// PEP 701 isn't supported.
|
||||
if !supports_pep_701 && self.context.interpolated_string_state().is_nested() {
|
||||
return QuoteStyle::Preserve;
|
||||
}
|
||||
|
||||
// For f-strings and t-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't.
|
||||
if let InterpolatedStringState::InsideInterpolatedElement(parent_context)
|
||||
| InterpolatedStringState::NestedInterpolatedElement(parent_context) =
|
||||
if let InterpolatedStringState::InsideInterpolatedElement(parent_context) =
|
||||
self.context.interpolated_string_state()
|
||||
{
|
||||
let parent_flags = parent_context.flags();
|
||||
|
||||
@@ -28,11 +28,12 @@ but none started with prefix {parentdir_prefix}"
|
||||
f'{{NOT \'a\' "formatted" "value"}}'
|
||||
f"some f-string with {a} {few():.2f} {formatted.values!r}"
|
||||
-f'some f-string with {a} {few(""):.2f} {formatted.values!r}'
|
||||
+f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
-f"{f'''{'nested'} inner'''} outer"
|
||||
-f"\"{f'{nested} inner'}\" outer"
|
||||
-f"space between opening braces: { {a for a in (1, 2, 3)}}"
|
||||
-f'Hello \'{tricky + "example"}\''
|
||||
+f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
|
||||
+f"{f'''{"nested"} inner'''} outer"
|
||||
+f'"{f"{nested} inner"}" outer'
|
||||
+f"space between opening braces: { {a for a in (1, 2, 3)} }"
|
||||
+f"Hello '{tricky + 'example'}'"
|
||||
@@ -48,7 +49,7 @@ f"{{NOT a formatted value}}"
|
||||
f'{{NOT \'a\' "formatted" "value"}}'
|
||||
f"some f-string with {a} {few():.2f} {formatted.values!r}"
|
||||
f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
f"{f'''{"nested"} inner'''} outer"
|
||||
f'"{f"{nested} inner"}" outer'
|
||||
f"space between opening braces: { {a for a in (1, 2, 3)} }"
|
||||
f"Hello '{tricky + 'example'}'"
|
||||
@@ -71,3 +72,17 @@ f'Hello \'{tricky + "example"}\''
|
||||
f"Tried directories {str(rootdirs)} \
|
||||
but none started with prefix {parentdir_prefix}"
|
||||
```
|
||||
|
||||
## New Unsupported Syntax Errors
|
||||
|
||||
error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:6:9
|
||||
|
|
||||
4 | f"some f-string with {a} {few():.2f} {formatted.values!r}"
|
||||
5 | f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
|
||||
6 | f"{f'''{"nested"} inner'''} outer"
|
||||
| ^
|
||||
7 | f'"{f"{nested} inner"}" outer'
|
||||
8 | f"space between opening braces: { {a for a in (1, 2, 3)} }"
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
@@ -712,6 +712,8 @@ f'{1:hy "user"}'
|
||||
f'{1: abcd "{1}" }'
|
||||
f'{1: abcd "{'aa'}" }'
|
||||
f'{1=: "abcd {'aa'}}'
|
||||
# FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
# spec, which is valid even before 3.12.
|
||||
f'{x:a{z:hy "user"}} \'\'\''
|
||||
|
||||
# Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
@@ -754,10 +756,6 @@ print(f"{ # Tuple with multiple elements that doesn't fit on a single line gets
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/15536
|
||||
print(f"{ {}, 1, }")
|
||||
|
||||
|
||||
# The inner quotes should not be changed to double quotes before Python 3.12
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
```
|
||||
|
||||
## Outputs
|
||||
@@ -1536,8 +1534,10 @@ f'{f"""other " """}'
|
||||
f'{1: hy "user"}'
|
||||
f'{1:hy "user"}'
|
||||
f'{1: abcd "{1}" }'
|
||||
f'{1: abcd "{'aa'}" }'
|
||||
f'{1: abcd "{"aa"}" }'
|
||||
f'{1=: "abcd {'aa'}}'
|
||||
# FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
# spec, which is valid even before 3.12.
|
||||
f"{x:a{z:hy \"user\"}} '''"
|
||||
|
||||
# Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
@@ -1585,10 +1585,6 @@ print(
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/15536
|
||||
print(f"{ {}, 1 }")
|
||||
|
||||
|
||||
# The inner quotes should not be changed to double quotes before Python 3.12
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
```
|
||||
|
||||
|
||||
@@ -2367,8 +2363,10 @@ f'{f"""other " """}'
|
||||
f'{1: hy "user"}'
|
||||
f'{1:hy "user"}'
|
||||
f'{1: abcd "{1}" }'
|
||||
f'{1: abcd "{'aa'}" }'
|
||||
f'{1: abcd "{"aa"}" }'
|
||||
f'{1=: "abcd {'aa'}}'
|
||||
# FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
# spec, which is valid even before 3.12.
|
||||
f"{x:a{z:hy \"user\"}} '''"
|
||||
|
||||
# Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
@@ -2416,14 +2414,34 @@ print(
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/15536
|
||||
print(f"{ {}, 1 }")
|
||||
|
||||
|
||||
# The inner quotes should not be changed to double quotes before Python 3.12
|
||||
f"{f'''{'nested'} inner'''} outer"
|
||||
```
|
||||
|
||||
|
||||
### Unsupported Syntax Errors
|
||||
error[invalid-syntax]: Cannot use an escape sequence (backslash) in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:764:19
|
||||
|
|
||||
762 | # FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
763 | # spec, which is valid even before 3.12.
|
||||
764 | f"{x:a{z:hy \"user\"}} '''"
|
||||
| ^
|
||||
765 |
|
||||
766 | # Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
error[invalid-syntax]: Cannot use an escape sequence (backslash) in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:764:13
|
||||
|
|
||||
762 | # FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
763 | # spec, which is valid even before 3.12.
|
||||
764 | f"{x:a{z:hy \"user\"}} '''"
|
||||
| ^
|
||||
765 |
|
||||
766 | # Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:178:8
|
||||
|
|
||||
@@ -2434,3 +2452,27 @@ error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python
|
||||
179 | f"foo {'"bar"'}"
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:773:14
|
||||
|
|
||||
771 | f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
|
||||
772 | f"{1=: abcd \'\'}" # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
|
||||
773 | f"{1=: abcd \"\"}" # Changing the quotes here is fine because the inner quotes are escaped
|
||||
| ^
|
||||
774 | # Don't change the quotes in the following cases:
|
||||
775 | f'{x=:hy "user"} \'\'\''
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
error[invalid-syntax]: Cannot reuse outer quote character in f-strings on Python 3.10 (syntax was added in Python 3.12)
|
||||
--> fstring.py:764:14
|
||||
|
|
||||
762 | # FIXME(brent) This should not be a syntax error on output. The escaped quotes are in the format
|
||||
763 | # spec, which is valid even before 3.12.
|
||||
764 | f"{x:a{z:hy \"user\"}} '''"
|
||||
| ^
|
||||
765 |
|
||||
766 | # Changing the outer quotes is fine because the format-spec is in a nested expression.
|
||||
|
|
||||
warning: Only accept new syntax errors if they are also present in the input. The formatter should not introduce syntax errors.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# parse_options: {"target-version": "3.12"}
|
||||
f"{1:""}" # this is a ParseError on all versions
|
||||
@@ -1,2 +0,0 @@
|
||||
# parse_options: {"target-version": "3.11"}
|
||||
f"{1:''}" # but this is okay on all versions
|
||||
@@ -5,5 +5,3 @@ f"""{f'''{f'{"# not a comment"}'}'''}"""
|
||||
f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression"""
|
||||
f"escape outside of \t {expr}\n"
|
||||
f"test\"abcd"
|
||||
f"{1:\x64}" # escapes are valid in the format spec
|
||||
f"{1:\"d\"}" # this also means that escaped outer quotes are valid
|
||||
|
||||
@@ -1571,8 +1571,6 @@ impl<'src> Parser<'src> {
|
||||
// f"""{f'''# before expression {f'# aro{f"#{1+1}#"}und #'}'''} # after expression"""
|
||||
// f"escape outside of \t {expr}\n"
|
||||
// f"test\"abcd"
|
||||
// f"{1:\x64}" # escapes are valid in the format spec
|
||||
// f"{1:\"d\"}" # this also means that escaped outer quotes are valid
|
||||
|
||||
// test_err pep701_f_string_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
@@ -1588,13 +1586,6 @@ impl<'src> Parser<'src> {
|
||||
// f"""{f"""{x}"""}""" # mark the whole triple quote
|
||||
// f"{'\n'.join(['\t', '\v', '\r'])}" # multiple escape sequences, multiple errors
|
||||
|
||||
// test_err nested_quote_in_format_spec_py312
|
||||
// # parse_options: {"target-version": "3.12"}
|
||||
// f"{1:""}" # this is a ParseError on all versions
|
||||
|
||||
// test_ok non_nested_quote_in_format_spec_py311
|
||||
// # parse_options: {"target-version": "3.11"}
|
||||
// f"{1:''}" # but this is okay on all versions
|
||||
let range = self.node_range(start);
|
||||
|
||||
if !self.options.target_version.supports_pep_701()
|
||||
@@ -1603,29 +1594,22 @@ impl<'src> Parser<'src> {
|
||||
let quote_bytes = flags.quote_str().as_bytes();
|
||||
let quote_len = flags.quote_len();
|
||||
for expr in elements.interpolations() {
|
||||
// We need to check the whole expression range, including any leading or trailing
|
||||
// debug text, but exclude the format spec, where escapes and escaped, reused quotes
|
||||
// are allowed.
|
||||
let range = expr
|
||||
.format_spec
|
||||
.as_ref()
|
||||
.map(|format_spec| TextRange::new(expr.start(), format_spec.start()))
|
||||
.unwrap_or(expr.range);
|
||||
for slash_position in memchr::memchr_iter(b'\\', self.source[range].as_bytes()) {
|
||||
for slash_position in memchr::memchr_iter(b'\\', self.source[expr.range].as_bytes())
|
||||
{
|
||||
let slash_position = TextSize::try_from(slash_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::Backslash),
|
||||
TextRange::at(range.start() + slash_position, '\\'.text_len()),
|
||||
TextRange::at(expr.range.start() + slash_position, '\\'.text_len()),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(quote_position) =
|
||||
memchr::memmem::find(self.source[range].as_bytes(), quote_bytes)
|
||||
memchr::memmem::find(self.source[expr.range].as_bytes(), quote_bytes)
|
||||
{
|
||||
let quote_position = TextSize::try_from(quote_position).unwrap();
|
||||
self.add_unsupported_syntax_error(
|
||||
UnsupportedSyntaxErrorKind::Pep701FString(FStringKind::NestedQuote),
|
||||
TextRange::at(range.start() + quote_position, quote_len),
|
||||
TextRange::at(expr.range.start() + quote_position, quote_len),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,11 +229,6 @@ impl SemanticSyntaxChecker {
|
||||
Self::add_error(ctx, SemanticSyntaxErrorKind::BreakOutsideLoop, *range);
|
||||
}
|
||||
}
|
||||
Stmt::Continue(ast::StmtContinue { range, .. }) => {
|
||||
if !ctx.in_loop_context() {
|
||||
Self::add_error(ctx, SemanticSyntaxErrorKind::ContinueOutsideLoop, *range);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1136,7 +1131,6 @@ impl Display for SemanticSyntaxError {
|
||||
write!(f, "Future feature `{name}` is not defined")
|
||||
}
|
||||
SemanticSyntaxErrorKind::BreakOutsideLoop => f.write_str("`break` outside loop"),
|
||||
SemanticSyntaxErrorKind::ContinueOutsideLoop => f.write_str("`continue` outside loop"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1513,9 +1507,6 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents the use of a `break` statement outside of a loop.
|
||||
BreakOutsideLoop,
|
||||
|
||||
/// Represents the use of a `continue` statement outside of a loop.
|
||||
ContinueOutsideLoop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/nested_quote_in_format_spec_py312.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..94,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 44..53,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 44..53,
|
||||
value: FStringValue {
|
||||
inner: Concatenated(
|
||||
[
|
||||
FString(
|
||||
FString {
|
||||
range: 44..50,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 46..49,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 47..48,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 49..49,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
Literal(
|
||||
StringLiteral {
|
||||
range: 50..53,
|
||||
node_index: NodeIndex(None),
|
||||
value: "}",
|
||||
flags: StringLiteralFlags {
|
||||
quote_style: Double,
|
||||
prefix: Empty,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Errors
|
||||
|
||||
|
|
||||
1 | # parse_options: {"target-version": "3.12"}
|
||||
2 | f"{1:""}" # this is a ParseError on all versions
|
||||
| ^ Syntax Error: f-string: expecting '}'
|
||||
|
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/ok/non_nested_quote_in_format_spec_py311.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..90,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 44..53,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 44..53,
|
||||
value: FStringValue {
|
||||
inner: Single(
|
||||
FString(
|
||||
FString {
|
||||
range: 44..53,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 46..52,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 47..48,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 49..51,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 49..51,
|
||||
node_index: NodeIndex(None),
|
||||
value: "''",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
@@ -8,7 +8,7 @@ input_file: crates/ruff_python_parser/resources/inline/ok/pep701_f_string_py311.
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..398,
|
||||
range: 0..278,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
@@ -604,130 +604,6 @@ Module(
|
||||
),
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 278..289,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 278..289,
|
||||
value: FStringValue {
|
||||
inner: Single(
|
||||
FString(
|
||||
FString {
|
||||
range: 278..289,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 280..288,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 281..282,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 283..287,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 283..287,
|
||||
node_index: NodeIndex(None),
|
||||
value: "d",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 330..342,
|
||||
value: FString(
|
||||
ExprFString {
|
||||
node_index: NodeIndex(None),
|
||||
range: 330..342,
|
||||
value: FStringValue {
|
||||
inner: Single(
|
||||
FString(
|
||||
FString {
|
||||
range: 330..342,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Interpolation(
|
||||
InterpolatedElement {
|
||||
range: 332..341,
|
||||
node_index: NodeIndex(None),
|
||||
expression: NumberLiteral(
|
||||
ExprNumberLiteral {
|
||||
node_index: NodeIndex(None),
|
||||
range: 333..334,
|
||||
value: Int(
|
||||
1,
|
||||
),
|
||||
},
|
||||
),
|
||||
debug_text: None,
|
||||
conversion: None,
|
||||
format_spec: Some(
|
||||
InterpolatedStringFormatSpec {
|
||||
range: 335..340,
|
||||
node_index: NodeIndex(None),
|
||||
elements: [
|
||||
Literal(
|
||||
InterpolatedStringLiteralElement {
|
||||
range: 335..340,
|
||||
node_index: NodeIndex(None),
|
||||
value: "\"d\"",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
flags: FStringFlags {
|
||||
quote_style: Double,
|
||||
prefix: Regular,
|
||||
triple_quoted: false,
|
||||
unclosed: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.1"
|
||||
version = "0.14.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
132
crates/ty/docs/rules.md
generated
132
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#L116" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L115" 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#L160" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159" 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#L186" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L185" 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#L211" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210" 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#L237" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236" 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#L302" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L301" 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#L323" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L322" 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#L527" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L526" 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#L551" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L550" 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#L355" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L354" 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#L597" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L596" 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#L637" 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>
|
||||
|
||||
|
||||
@@ -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#L1748" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1747" 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#L659" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L658" 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#L689" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L688" 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#L740" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739" 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#L761" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760" 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#L784" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L783" 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#L820" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L819" 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#L572" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L571" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -787,7 +787,7 @@ alice["height"] # KeyError: 'height'
|
||||
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#L846" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -822,7 +822,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#L895" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L894" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -856,7 +856,7 @@ class B(metaclass=f): ...
|
||||
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#L501" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L500" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -888,7 +888,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> ·
|
||||
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#L922" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L921" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -938,7 +938,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#L1021" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1020" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -964,7 +964,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-protocol" target="_blank">Related issues</a> ·
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L437" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L436" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -998,7 +998,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#L1041" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1040" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1047,7 +1047,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#L618" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L617" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1072,7 +1072,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#L1084" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1130,7 +1130,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#L874" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L873" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1157,7 +1157,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#L1123" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1122" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1187,7 +1187,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#L1147" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1146" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1217,7 +1217,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#L1199" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1198" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1251,7 +1251,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#L1171" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1170" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1285,7 +1285,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#L1227" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1226" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1320,7 +1320,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#L1256" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1255" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1345,7 +1345,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#L1849" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1848" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1378,7 +1378,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#L1275" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1274" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1407,7 +1407,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#L1298" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1297" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1431,7 +1431,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#L1316" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1315" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1457,7 +1457,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#L1367" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1366" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1484,7 +1484,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#L1602" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1601" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1542,7 +1542,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#L1724" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1723" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1572,7 +1572,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#L1458" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1457" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1601,7 +1601,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#L1503" 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>
|
||||
|
||||
|
||||
@@ -1628,7 +1628,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#L1481" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1480" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1656,7 +1656,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#L1524" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1523" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1702,7 +1702,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#L1581" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1580" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1729,7 +1729,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#L1623" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1757,7 +1757,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#L1645" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1782,7 +1782,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#L1664" target="_blank">View source</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>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1807,7 +1807,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#L1336" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1335" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1844,7 +1844,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#L1683" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1682" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1872,7 +1872,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#L1705" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1704" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1897,7 +1897,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#L466" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L465" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1938,7 +1938,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#L281" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L280" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -1995,7 +1995,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#L1388" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1387" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2023,7 +2023,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#L134" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L133" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2055,7 +2055,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#L1410" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1409" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2087,7 +2087,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#L1776" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1775" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2114,7 +2114,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#L1563" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1562" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2169,7 +2169,7 @@ a = 20 / 0 # ty: ignore[division-by-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.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#L1797" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1796" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2227,7 +2227,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#L707" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L706" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2266,7 +2266,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#L965" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L964" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2329,7 +2329,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#L263" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L262" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
@@ -2353,7 +2353,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#L1436" target="_blank">View source</a>
|
||||
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1435" target="_blank">View source</a>
|
||||
</small>
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use anyhow::Result;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::args::{CheckCommand, Command, TerminalColor};
|
||||
use crate::logging::{VerbosityLevel, setup_tracing};
|
||||
use crate::logging::setup_tracing;
|
||||
use crate::printer::Printer;
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap::{CommandFactory, Parser};
|
||||
@@ -128,8 +128,6 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
|
||||
let mut db = ProjectDatabase::new(project_metadata, system)?;
|
||||
|
||||
db.project()
|
||||
.set_verbose(&mut db, verbosity >= VerbosityLevel::Verbose);
|
||||
if !check_paths.is_empty() {
|
||||
db.project().set_included_paths(&mut db, check_paths);
|
||||
}
|
||||
|
||||
@@ -92,42 +92,42 @@ fn test_quiet_output() -> anyhow::Result<()> {
|
||||
#[test]
|
||||
fn test_run_in_sub_directory() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([("test.py", "~"), ("subdir/nothing", "")])?;
|
||||
assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r"
|
||||
assert_cmd_snapshot!(case.command().current_dir(case.root().join("subdir")).arg(".."), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Expected an expression
|
||||
error[invalid-syntax]
|
||||
--> <temp_dir>/test.py:1:2
|
||||
|
|
||||
1 | ~
|
||||
| ^
|
||||
| ^ Expected an expression
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include_hidden_files_by_default() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([(".test.py", "~")])?;
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Expected an expression
|
||||
error[invalid-syntax]
|
||||
--> .test.py:1:2
|
||||
|
|
||||
1 | ~
|
||||
| ^
|
||||
| ^ Expected an expression
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -146,57 +146,57 @@ fn test_respect_ignore_files() -> anyhow::Result<()> {
|
||||
"###);
|
||||
|
||||
// Test that we can set to false via CLI
|
||||
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r"
|
||||
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Expected an expression
|
||||
error[invalid-syntax]
|
||||
--> test.py:1:2
|
||||
|
|
||||
1 | ~
|
||||
| ^
|
||||
| ^ Expected an expression
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
// Test that we can set to false via config file
|
||||
case.write_file("ty.toml", "src.respect-ignore-files = false")?;
|
||||
assert_cmd_snapshot!(case.command(), @r"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Expected an expression
|
||||
error[invalid-syntax]
|
||||
--> test.py:1:2
|
||||
|
|
||||
1 | ~
|
||||
| ^
|
||||
| ^ Expected an expression
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
// Ensure CLI takes precedence
|
||||
case.write_file("ty.toml", "src.respect-ignore-files = true")?;
|
||||
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r"
|
||||
assert_cmd_snapshot!(case.command().arg("--no-respect-ignore-files"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Expected an expression
|
||||
error[invalid-syntax]
|
||||
--> test.py:1:2
|
||||
|
|
||||
1 | ~
|
||||
| ^
|
||||
| ^ Expected an expression
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -782,8 +782,6 @@ impl CliTest {
|
||||
let mut settings = insta::Settings::clone_current();
|
||||
settings.add_filter(&tempdir_filter(&project_dir), "<temp_dir>/");
|
||||
settings.add_filter(r#"\\(\w\w|\s|\.|")"#, "/$1");
|
||||
// 0.003s
|
||||
settings.add_filter(r"\d.\d\d\ds", "0.000s");
|
||||
settings.add_filter(
|
||||
r#"The system cannot find the file specified."#,
|
||||
"No such file or directory",
|
||||
|
||||
@@ -26,7 +26,7 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -37,19 +37,12 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
5 | print(sys.last_exc)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
info: Python 3.11 was assumed when accessing `last_exc`
|
||||
--> pyproject.toml:3:18
|
||||
|
|
||||
2 | [tool.ty.environment]
|
||||
3 | python-version = "3.11"
|
||||
| ^^^^^^ Python 3.11 assumed due to this configuration setting
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version").arg("3.12"), @r###"
|
||||
success: true
|
||||
@@ -428,94 +421,6 @@ fn lib64_site_packages_directory_on_unix() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_search_paths() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
("extra1/foo1.py", ""),
|
||||
("extra2/foo2.py", ""),
|
||||
("extra3/foo3.py", ""),
|
||||
("extra4/foo4.py", ""),
|
||||
("extra5/foo5.py", ""),
|
||||
("extra6/foo6.py", ""),
|
||||
("test.py", "import foo1, baz"),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(
|
||||
case.command()
|
||||
.arg("--python-platform").arg("linux")
|
||||
.arg("--extra-search-path").arg("extra1")
|
||||
.arg("--extra-search-path").arg("extra2")
|
||||
.arg("--extra-search-path").arg("extra3")
|
||||
.arg("--extra-search-path").arg("extra4")
|
||||
.arg("--extra-search-path").arg("extra5")
|
||||
.arg("--extra-search-path").arg("extra6"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-import]: Cannot resolve imported module `baz`
|
||||
--> test.py:1:14
|
||||
|
|
||||
1 | import foo1, baz
|
||||
| ^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/extra1 (extra search path specified on the CLI or in your config file)
|
||||
info: 2. <temp_dir>/extra2 (extra search path specified on the CLI or in your config file)
|
||||
info: 3. <temp_dir>/extra3 (extra search path specified on the CLI or in your config file)
|
||||
info: 4. <temp_dir>/extra4 (extra search path specified on the CLI or in your config file)
|
||||
info: 5. <temp_dir>/extra5 (extra search path specified on the CLI or in your config file)
|
||||
info: ... and 3 more paths. Run with `-v` to see all paths.
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
|
||||
// Shows all with `-v`
|
||||
assert_cmd_snapshot!(
|
||||
case.command()
|
||||
.arg("--python-platform").arg("linux")
|
||||
.arg("--extra-search-path").arg("extra1")
|
||||
.arg("--extra-search-path").arg("extra2")
|
||||
.arg("--extra-search-path").arg("extra3")
|
||||
.arg("--extra-search-path").arg("extra4")
|
||||
.arg("--extra-search-path").arg("extra5")
|
||||
.arg("--extra-search-path").arg("extra6")
|
||||
.arg("-v"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[unresolved-import]: Cannot resolve imported module `baz`
|
||||
--> test.py:1:14
|
||||
|
|
||||
1 | import foo1, baz
|
||||
| ^^^
|
||||
|
|
||||
info: Searched in the following paths during module resolution:
|
||||
info: 1. <temp_dir>/extra1 (extra search path specified on the CLI or in your config file)
|
||||
info: 2. <temp_dir>/extra2 (extra search path specified on the CLI or in your config file)
|
||||
info: 3. <temp_dir>/extra3 (extra search path specified on the CLI or in your config file)
|
||||
info: 4. <temp_dir>/extra4 (extra search path specified on the CLI or in your config file)
|
||||
info: 5. <temp_dir>/extra5 (extra search path specified on the CLI or in your config file)
|
||||
info: 6. <temp_dir>/extra6 (extra search path specified on the CLI or in your config file)
|
||||
info: 7. <temp_dir>/ (first-party code)
|
||||
info: 8. vendored://stdlib (stdlib typeshed stubs vendored by ty)
|
||||
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
INFO Python version: Python 3.14, platform: linux
|
||||
INFO Indexed 7 file(s) in 0.000s
|
||||
");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pyvenv_cfg_file_annotation_showing_where_python_version_set() -> anyhow::Result<()> {
|
||||
let case = CliTest::with_files([
|
||||
@@ -655,15 +560,15 @@ fn config_file_annotation_showing_where_python_version_set_syntax_error() -> any
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10)
|
||||
error[invalid-syntax]
|
||||
--> test.py:2:1
|
||||
|
|
||||
2 | match object():
|
||||
| ^^^^^
|
||||
| ^^^^^ Cannot use `match` statement on Python 3.8 (syntax was added in Python 3.10)
|
||||
3 | case int():
|
||||
4 | pass
|
||||
|
|
||||
@@ -678,17 +583,17 @@ fn config_file_annotation_showing_where_python_version_set_syntax_error() -> any
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r"
|
||||
assert_cmd_snapshot!(case.command().arg("--python-version=3.9"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error[invalid-syntax]: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
error[invalid-syntax]
|
||||
--> test.py:2:1
|
||||
|
|
||||
2 | match object():
|
||||
| ^^^^^
|
||||
| ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
3 | case int():
|
||||
4 | pass
|
||||
|
|
||||
@@ -697,7 +602,7 @@ fn config_file_annotation_showing_where_python_version_set_syntax_error() -> any
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -958,7 +863,7 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
),
|
||||
])?;
|
||||
|
||||
assert_cmd_snapshot!(case.command(), @r#"
|
||||
assert_cmd_snapshot!(case.command(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
@@ -970,20 +875,12 @@ fn defaults_to_a_new_python_version() -> anyhow::Result<()> {
|
||||
4 | os.grantpt(1) # only available on unix, Python 3.13 or newer
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
info: Python 3.10 was assumed when accessing `grantpt`
|
||||
--> 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-attribute` is enabled by default
|
||||
|
||||
Found 1 diagnostic
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
"###);
|
||||
|
||||
// Use default (which should be latest supported)
|
||||
let case = CliTest::with_files([
|
||||
|
||||
@@ -131,7 +131,7 @@ impl<'db> Completion<'db> {
|
||||
| Type::BytesLiteral(_) => CompletionKind::Value,
|
||||
Type::EnumLiteral(_) => CompletionKind::Enum,
|
||||
Type::ProtocolInstance(_) => CompletionKind::Interface,
|
||||
Type::TypeVar(_) => CompletionKind::TypeParameter,
|
||||
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => CompletionKind::TypeParameter,
|
||||
Type::Union(union) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
|
||||
@@ -336,7 +336,9 @@ impl<'db> SemanticTokenVisitor<'db> {
|
||||
|
||||
match ty {
|
||||
Type::ClassLiteral(_) => (SemanticTokenType::Class, modifiers),
|
||||
Type::TypeVar(_) => (SemanticTokenType::TypeParameter, modifiers),
|
||||
Type::NonInferableTypeVar(_) | Type::TypeVar(_) => {
|
||||
(SemanticTokenType::TypeParameter, modifiers)
|
||||
}
|
||||
Type::FunctionLiteral(_) => {
|
||||
// Check if this is a method based on current scope
|
||||
if self.in_class_scope {
|
||||
|
||||
@@ -457,10 +457,6 @@ impl SemanticDb for ProjectDatabase {
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
ty_python_semantic::default_lint_registry()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
self.project().verbose(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
@@ -613,10 +609,6 @@ pub(crate) mod tests {
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
ty_python_semantic::default_lint_registry()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
@@ -113,9 +113,6 @@ pub struct Project {
|
||||
/// the project including the virtual files that might exists in the editor.
|
||||
#[default]
|
||||
check_mode: CheckMode,
|
||||
|
||||
#[default]
|
||||
verbose_flag: bool,
|
||||
}
|
||||
|
||||
/// A progress reporter.
|
||||
@@ -371,16 +368,6 @@ impl Project {
|
||||
self.reload_files(db);
|
||||
}
|
||||
|
||||
pub fn set_verbose(self, db: &mut dyn Db, verbose: bool) {
|
||||
if self.verbose_flag(db) != verbose {
|
||||
self.set_verbose_flag(db).to(verbose);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verbose(self, db: &dyn Db) -> bool {
|
||||
self.verbose_flag(db)
|
||||
}
|
||||
|
||||
/// Returns the paths that should be checked.
|
||||
///
|
||||
/// The default is to check the entire project in which case this method returns
|
||||
|
||||
@@ -114,7 +114,7 @@ h: list[list[int]] = [[], [42]]
|
||||
reveal_type(h) # revealed: list[list[int]]
|
||||
|
||||
i: list[typing.Any] = [1, 2, "3", ([4],)]
|
||||
reveal_type(i) # revealed: list[Any]
|
||||
reveal_type(i) # revealed: list[Any | int | str | tuple[list[Unknown | int]]]
|
||||
|
||||
j: list[tuple[str | int, ...]] = [(1, 2), ("foo", "bar"), ()]
|
||||
reveal_type(j) # revealed: list[tuple[str | int, ...]]
|
||||
@@ -123,7 +123,7 @@ k: list[tuple[list[int], ...]] = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
|
||||
reveal_type(k) # revealed: list[tuple[list[int], ...]]
|
||||
|
||||
l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
|
||||
reveal_type(l) # revealed: tuple[list[int], list[Any], list[Any], list[str]]
|
||||
reveal_type(l) # revealed: tuple[list[int], list[Any | int], list[Any | int], list[str]]
|
||||
|
||||
type IntList = list[int]
|
||||
|
||||
@@ -144,12 +144,6 @@ reveal_type(q) # revealed: dict[int | str, int]
|
||||
|
||||
r: dict[int | str, int | str] = {1: 1, 2: 2, 3: 3}
|
||||
reveal_type(r) # revealed: dict[int | str, int | str]
|
||||
|
||||
s: dict[int | str, int | str]
|
||||
s = {1: 1, 2: 2, 3: 3}
|
||||
reveal_type(s) # revealed: dict[int | str, int | str]
|
||||
(s := {1: 1, 2: 2, 3: 3})
|
||||
reveal_type(s) # revealed: dict[int | str, int | str]
|
||||
```
|
||||
|
||||
## Optional collection literal annotations are understood
|
||||
@@ -187,7 +181,7 @@ h: list[list[int]] | None = [[], [42]]
|
||||
reveal_type(h) # revealed: list[list[int]]
|
||||
|
||||
i: list[typing.Any] | None = [1, 2, "3", ([4],)]
|
||||
reveal_type(i) # revealed: list[Any]
|
||||
reveal_type(i) # revealed: list[Any | int | str | tuple[list[Unknown | int]]]
|
||||
|
||||
j: list[tuple[str | int, ...]] | None = [(1, 2), ("foo", "bar"), ()]
|
||||
reveal_type(j) # revealed: list[tuple[str | int, ...]]
|
||||
@@ -196,7 +190,8 @@ k: list[tuple[list[int], ...]] | None = [([],), ([1, 2], [3, 4]), ([5], [6], [7]
|
||||
reveal_type(k) # revealed: list[tuple[list[int], ...]]
|
||||
|
||||
l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] | None = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
|
||||
reveal_type(l) # revealed: tuple[list[int], list[Any], list[Any], list[str]]
|
||||
# TODO: this should be `tuple[list[int], list[Any | int], list[Any | int], list[str]]`
|
||||
reveal_type(l) # revealed: tuple[list[Unknown | int], list[Unknown | int], list[Unknown | int], list[Unknown | str]]
|
||||
|
||||
type IntList = list[int]
|
||||
|
||||
@@ -282,7 +277,7 @@ reveal_type(k) # revealed: list[Literal[1, 2, 3]]
|
||||
type Y[T] = list[T]
|
||||
|
||||
l: Y[Y[Literal[1]]] = [[1]]
|
||||
reveal_type(l) # revealed: list[Y[Literal[1]]]
|
||||
reveal_type(l) # revealed: list[list[Literal[1]]]
|
||||
|
||||
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
|
||||
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
|
||||
@@ -302,12 +297,6 @@ reveal_type(q) # revealed: list[int]
|
||||
|
||||
r: list[Literal[1, 2, 3, 4]] = [1, 2]
|
||||
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
|
||||
|
||||
s: list[Literal[1]]
|
||||
s = [1]
|
||||
reveal_type(s) # revealed: list[Literal[1]]
|
||||
(s := [1])
|
||||
reveal_type(s) # revealed: list[Literal[1]]
|
||||
```
|
||||
|
||||
## PEP-604 annotations are supported
|
||||
@@ -427,14 +416,13 @@ a = f("a")
|
||||
reveal_type(a) # revealed: list[Literal["a"]]
|
||||
|
||||
b: list[int | Literal["a"]] = f("a")
|
||||
reveal_type(b) # revealed: list[Literal["a"] | int]
|
||||
reveal_type(b) # revealed: list[int | Literal["a"]]
|
||||
|
||||
c: list[int | str] = f("a")
|
||||
reveal_type(c) # revealed: list[str | int]
|
||||
reveal_type(c) # revealed: list[int | str]
|
||||
|
||||
d: list[int | tuple[int, int]] = f((1, 2))
|
||||
# TODO: We could avoid reordering the union elements here.
|
||||
reveal_type(d) # revealed: list[tuple[int, int] | int]
|
||||
reveal_type(d) # revealed: list[int | tuple[int, int]]
|
||||
|
||||
e: list[int] = f(True)
|
||||
reveal_type(e) # revealed: list[int]
|
||||
@@ -449,49 +437,8 @@ def f2[T: int](x: T) -> T:
|
||||
return x
|
||||
|
||||
i: int = f2(True)
|
||||
reveal_type(i) # revealed: Literal[True]
|
||||
reveal_type(i) # revealed: int
|
||||
|
||||
j: int | str = f2(True)
|
||||
reveal_type(j) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
Types are not widened unnecessarily:
|
||||
|
||||
```py
|
||||
def id[T](x: T) -> T:
|
||||
return x
|
||||
|
||||
def lst[T](x: T) -> list[T]:
|
||||
return [x]
|
||||
|
||||
def _(i: int):
|
||||
a: int | None = i
|
||||
b: int | None = id(i)
|
||||
c: int | str | None = id(i)
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: int
|
||||
reveal_type(c) # revealed: int
|
||||
|
||||
a: list[int | None] | None = [i]
|
||||
b: list[int | None] | None = id([i])
|
||||
c: list[int | None] | int | None = id([i])
|
||||
reveal_type(a) # revealed: list[int | None]
|
||||
# TODO: these should reveal `list[int | None]`
|
||||
# we currently do not use the call expression annotation as type context for argument inference
|
||||
reveal_type(b) # revealed: list[Unknown | int]
|
||||
reveal_type(c) # revealed: list[Unknown | int]
|
||||
|
||||
a: list[int | None] | None = [i]
|
||||
b: list[int | None] | None = lst(i)
|
||||
c: list[int | None] | int | None = lst(i)
|
||||
reveal_type(a) # revealed: list[int | None]
|
||||
reveal_type(b) # revealed: list[int | None]
|
||||
reveal_type(c) # revealed: list[int | None]
|
||||
|
||||
a: list | None = []
|
||||
b: list | None = id([])
|
||||
c: list | int | None = id([])
|
||||
reveal_type(a) # revealed: list[Unknown]
|
||||
reveal_type(b) # revealed: list[Unknown]
|
||||
reveal_type(c) # revealed: list[Unknown]
|
||||
```
|
||||
|
||||
@@ -2363,55 +2363,6 @@ reveal_type(B().x) # revealed: Unknown | Literal[1]
|
||||
reveal_type(A().x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
And cycles between many attributes:
|
||||
|
||||
```py
|
||||
class ManyCycles:
|
||||
def __init__(self: "ManyCycles"):
|
||||
self.x1 = 0
|
||||
self.x2 = 0
|
||||
self.x3 = 0
|
||||
self.x4 = 0
|
||||
self.x5 = 0
|
||||
self.x6 = 0
|
||||
self.x7 = 1
|
||||
|
||||
def f1(self: "ManyCycles"):
|
||||
self.x1 = self.x2 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x2 = self.x1 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x3 = self.x1 + self.x2 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x4 = self.x1 + self.x2 + self.x3 + self.x5 + self.x6 + self.x7
|
||||
self.x5 = self.x1 + self.x2 + self.x3 + self.x4 + self.x6 + self.x7
|
||||
self.x6 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x7
|
||||
self.x7 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x6
|
||||
|
||||
def f2(self: "ManyCycles"):
|
||||
self.x1 = self.x2 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x2 = self.x1 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x3 = self.x1 + self.x2 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x4 = self.x1 + self.x2 + self.x3 + self.x5 + self.x6 + self.x7
|
||||
self.x5 = self.x1 + self.x2 + self.x3 + self.x4 + self.x6 + self.x7
|
||||
self.x6 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x7
|
||||
self.x7 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x6
|
||||
|
||||
def f3(self: "ManyCycles"):
|
||||
self.x1 = self.x2 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x2 = self.x1 + self.x3 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x3 = self.x1 + self.x2 + self.x4 + self.x5 + self.x6 + self.x7
|
||||
self.x4 = self.x1 + self.x2 + self.x3 + self.x5 + self.x6 + self.x7
|
||||
self.x5 = self.x1 + self.x2 + self.x3 + self.x4 + self.x6 + self.x7
|
||||
self.x6 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x7
|
||||
self.x7 = self.x1 + self.x2 + self.x3 + self.x4 + self.x5 + self.x6
|
||||
|
||||
reveal_type(self.x1) # revealed: Unknown | int
|
||||
reveal_type(self.x2) # revealed: Unknown | int
|
||||
reveal_type(self.x3) # revealed: Unknown | int
|
||||
reveal_type(self.x4) # revealed: Unknown | int
|
||||
reveal_type(self.x5) # revealed: Unknown | int
|
||||
reveal_type(self.x6) # revealed: Unknown | int
|
||||
reveal_type(self.x7) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
This case additionally tests our union/intersection simplification logic:
|
||||
|
||||
```py
|
||||
@@ -2558,28 +2509,6 @@ class C:
|
||||
reveal_type(C().x) # revealed: Unknown | tuple[Divergent, Literal[1]]
|
||||
```
|
||||
|
||||
## Attributes of standard library modules that aren't yet defined
|
||||
|
||||
For attributes of stdlib modules that exist in future versions, we can give better diagnostics.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
import datetime
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(datetime.UTC) # revealed: Unknown
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
Some of the tests in the *Class and instance variables* section draw inspiration from
|
||||
|
||||
@@ -694,51 +694,6 @@ def _(
|
||||
f1(*args10) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
A union of heterogeneous tuples provided to a variadic parameter:
|
||||
|
||||
```py
|
||||
# Test inspired by ecosystem code at:
|
||||
# - <https://github.com/home-assistant/core/blob/bde4eb50111a72f9717fe73ee5929e50eb06911b/homeassistant/components/lovelace/websocket.py#L50-L59>
|
||||
# - <https://github.com/pydata/xarray/blob/3572f4e70f2b12ef9935c1f8c3c1b74045d2a092/xarray/tests/test_groupby.py#L3058-L3059>
|
||||
|
||||
def f2(a: str, b: bool): ...
|
||||
def f3(coinflip: bool):
|
||||
if coinflip:
|
||||
args = "foo", True
|
||||
else:
|
||||
args = "bar", False
|
||||
|
||||
# revealed: tuple[Literal["foo"], Literal[True]] | tuple[Literal["bar"], Literal[False]]
|
||||
reveal_type(args)
|
||||
f2(*args) # fine
|
||||
|
||||
if coinflip:
|
||||
other_args = "foo", True
|
||||
else:
|
||||
other_args = "bar", (True,)
|
||||
|
||||
# revealed: tuple[Literal["foo"], Literal[True]] | tuple[Literal["bar"], tuple[Literal[True]]]
|
||||
reveal_type(other_args)
|
||||
# error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `bool`, found `Literal[True] | tuple[Literal[True]]`"
|
||||
f2(*other_args)
|
||||
|
||||
def f4(a=None, b=None, c=None, d=None, e=None): ...
|
||||
|
||||
my_args = ((1, 2), (3, 4), (5, 6))
|
||||
|
||||
for tup in my_args:
|
||||
f4(*tup, e=None) # fine
|
||||
|
||||
my_other_args = (
|
||||
(1, 2, 3, 4, 5),
|
||||
(6, 7, 8, 9, 10),
|
||||
)
|
||||
|
||||
for tup in my_other_args:
|
||||
# error: [parameter-already-assigned] "Multiple values provided for parameter `e` of function `f4`"
|
||||
f4(*tup, e=None)
|
||||
```
|
||||
|
||||
### Mixed argument and parameter containing variadic
|
||||
|
||||
```toml
|
||||
|
||||
@@ -122,6 +122,9 @@ class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
# TODO: this is not supported yet
|
||||
# error: [unknown-argument]
|
||||
# error: [unknown-argument]
|
||||
CustomerModel(id=1, name="Test")
|
||||
```
|
||||
|
||||
@@ -213,7 +216,11 @@ class OrderedModelBase: ...
|
||||
class TestWithBase(OrderedModelBase):
|
||||
inner: int
|
||||
|
||||
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: bool
|
||||
# TODO: No errors here, should reveal `bool`
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [too-many-positional-arguments]
|
||||
# error: [unsupported-operator]
|
||||
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `kw_only_default`
|
||||
@@ -270,7 +277,8 @@ class ModelBase: ...
|
||||
class TestBase(ModelBase):
|
||||
name: str
|
||||
|
||||
reveal_type(TestBase.__init__) # revealed: (self: TestBase, *, name: str) -> None
|
||||
# TODO: This should be `(self: TestBase, *, name: str) -> None`
|
||||
reveal_type(TestBase.__init__) # revealed: def __init__(self) -> None
|
||||
```
|
||||
|
||||
### `frozen_default`
|
||||
@@ -325,9 +333,12 @@ class ModelBase: ...
|
||||
class TestMeta(ModelBase):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
t = TestMeta(name="test")
|
||||
|
||||
t.name = "new" # error: [invalid-assignment]
|
||||
# TODO: this should be an `invalid-assignment` error
|
||||
t.name = "new"
|
||||
```
|
||||
|
||||
### Combining parameters
|
||||
@@ -426,15 +437,19 @@ class DefaultFrozenModel:
|
||||
class Frozen(DefaultFrozenModel):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
f = Frozen(name="test")
|
||||
f.name = "new" # error: [invalid-assignment]
|
||||
# TODO: this should be an `invalid-assignment` error
|
||||
f.name = "new"
|
||||
|
||||
class Mutable(DefaultFrozenModel, frozen=False):
|
||||
name: str
|
||||
|
||||
# TODO: no error here
|
||||
# error: [unknown-argument]
|
||||
m = Mutable(name="test")
|
||||
# TODO: This should not be an error
|
||||
m.name = "new" # error: [invalid-assignment]
|
||||
m.name = "new" # No error
|
||||
```
|
||||
|
||||
## `field_specifiers`
|
||||
@@ -463,7 +478,7 @@ class Person:
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str = Unknown, *, age: int | None = Unknown) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
|
||||
@@ -491,7 +506,7 @@ class Person(FancyBase):
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str = Unknown, *, age: int | None = Unknown) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
|
||||
@@ -500,113 +515,6 @@ reveal_type(alice.name) # revealed: str
|
||||
reveal_type(alice.age) # revealed: int | None
|
||||
```
|
||||
|
||||
### For base-class-based transformers
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
class FancyBase:
|
||||
def __init_subclass__(cls):
|
||||
...
|
||||
super().__init_subclass__()
|
||||
|
||||
class Person(FancyBase):
|
||||
id: int = fancy_field(init=False)
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
|
||||
alice = Person("Alice", age=30)
|
||||
|
||||
reveal_type(alice.id) # revealed: int
|
||||
reveal_type(alice.name) # revealed: str
|
||||
reveal_type(alice.age) # revealed: int | None
|
||||
```
|
||||
|
||||
### With default arguments
|
||||
|
||||
Field specifiers can have default arguments that should be respected:
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
|
||||
def fancy_field(*, init: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field()
|
||||
name: str = fancy_field(init=True)
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str) -> None
|
||||
|
||||
Person(name="Alice")
|
||||
```
|
||||
|
||||
### With overloaded field specifiers
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, overload, Any
|
||||
|
||||
@overload
|
||||
def fancy_field(*, init: bool = True) -> Any: ...
|
||||
@overload
|
||||
def fancy_field(*, kw_only: bool = False) -> Any: ...
|
||||
def fancy_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(fancy_field,))
|
||||
def fancy_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
@fancy_model
|
||||
class Person:
|
||||
id: int = fancy_field(init=False)
|
||||
name: str = fancy_field()
|
||||
age: int | None = fancy_field(kw_only=True)
|
||||
|
||||
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
|
||||
```
|
||||
|
||||
### Nested dataclass-transformers
|
||||
|
||||
Make sure that models are only affected by the field specifiers of their own transformer:
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Any
|
||||
from dataclasses import field
|
||||
|
||||
def outer_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(outer_field,))
|
||||
def outer_model[T](cls: type[T]) -> type[T]:
|
||||
# ...
|
||||
return cls
|
||||
|
||||
def inner_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
|
||||
@dataclass_transform(field_specifiers=(inner_field,))
|
||||
def inner_model[T](cls: type[T]) -> type[T]:
|
||||
# ...
|
||||
return cls
|
||||
|
||||
@outer_model
|
||||
class Outer:
|
||||
@inner_model
|
||||
class Inner:
|
||||
inner_a: int = inner_field(init=False)
|
||||
inner_b: str = outer_field(init=False)
|
||||
|
||||
outer_a: int = outer_field(init=False)
|
||||
outer_b: str = inner_field(init=False)
|
||||
|
||||
reveal_type(Outer.__init__) # revealed: (self: Outer, outer_b: str = Any) -> None
|
||||
reveal_type(Outer.Inner.__init__) # revealed: (self: Inner, inner_b: str = Any) -> None
|
||||
```
|
||||
|
||||
## Overloaded dataclass-like decorators
|
||||
|
||||
In the case of an overloaded decorator, the `dataclass_transform` decorator can be applied to the
|
||||
|
||||
@@ -497,8 +497,6 @@ class A:
|
||||
a: str = field(kw_only=False)
|
||||
b: int = 0
|
||||
|
||||
reveal_type(A.__init__) # revealed: (self: A, a: str, *, b: int = Literal[0]) -> None
|
||||
|
||||
A("hi")
|
||||
```
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class Data:
|
||||
content: list[int] = field(default_factory=list)
|
||||
timestamp: datetime = field(default_factory=datetime.now, init=False)
|
||||
|
||||
# revealed: (self: Data, content: list[int] = Unknown) -> None
|
||||
# revealed: (self: Data, content: list[int] = list[Unknown]) -> None
|
||||
reveal_type(Data.__init__)
|
||||
|
||||
data = Data([1, 2, 3])
|
||||
@@ -63,7 +63,6 @@ class Person:
|
||||
age: int | None = field(default=None, kw_only=True)
|
||||
role: str = field(default="user", kw_only=True)
|
||||
|
||||
# TODO: this would ideally show a default value of `None` for `age`
|
||||
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = Literal["user"]) -> None
|
||||
reveal_type(Person.__init__)
|
||||
|
||||
@@ -83,8 +82,7 @@ def get_default() -> str:
|
||||
|
||||
reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
|
||||
reveal_type(field(default=None)) # revealed: dataclasses.Field[None]
|
||||
# TODO: this could ideally be `dataclasses.Field[str]` with a better generics solver
|
||||
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[Unknown]
|
||||
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str]
|
||||
```
|
||||
|
||||
## dataclass_transform field_specifiers
|
||||
@@ -110,7 +108,7 @@ class A:
|
||||
name: str = field(init=False)
|
||||
|
||||
# field(init=False) should be ignored for dataclass_transform without explicit field_specifiers
|
||||
reveal_type(A.__init__) # revealed: (self: A, name: str) -> None
|
||||
reveal_type(A.__init__) # revealed: (self: A, name: str = Unknown) -> None
|
||||
|
||||
@dataclass
|
||||
class B:
|
||||
|
||||
@@ -138,27 +138,3 @@ def _(n: int):
|
||||
# error: [unknown-argument]
|
||||
y = f("foo", name="bar", unknown="quux")
|
||||
```
|
||||
|
||||
### Truncation for long unions and literals
|
||||
|
||||
This test demonstrates a call where the expected type is a large mixed union. The diagnostic must
|
||||
therefore truncate the long expected union type to avoid overwhelming output.
|
||||
|
||||
```py
|
||||
from typing import Literal, Union
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
class D: ...
|
||||
class E: ...
|
||||
class F: ...
|
||||
|
||||
def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||
return 0
|
||||
|
||||
def _(n: int):
|
||||
x = n
|
||||
# error: [invalid-argument-type]
|
||||
f1(x)
|
||||
```
|
||||
|
||||
@@ -192,26 +192,6 @@ from string.templatelib import Template # error: [unresolved-import]
|
||||
from importlib.resources import abc # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## Attempting to import a stdlib submodule when both parts haven't yet been added
|
||||
|
||||
`compression` and `compression.zstd` were both added in 3.14 so there is a typeshed `VERSIONS` entry
|
||||
for `compression` but not `compression.zstd`. We can't be confident `compression.zstd` exists but we
|
||||
do know `compression` does and can still give good diagnostics about it.
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
import compression.zstd # error: [unresolved-import]
|
||||
from compression import zstd # error: [unresolved-import]
|
||||
import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
from compression import fakebutwhocansay # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## Attempting to import a stdlib module that was previously removed
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# Legacy namespace packages
|
||||
|
||||
## `__import__("pkgutil").extend_path`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["/airflow-core/src", "/providers/amazon/src/"]
|
||||
```
|
||||
|
||||
`/airflow-core/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
||||
__version__ = "3.2.0"
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/amazon/__init__.py`:
|
||||
|
||||
```py
|
||||
__version__ = "9.15.0"
|
||||
```
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from airflow import __version__ as airflow_version
|
||||
from airflow.providers.amazon import __version__ as amazon_provider_version
|
||||
|
||||
reveal_type(airflow_version) # revealed: Literal["3.2.0"]
|
||||
reveal_type(amazon_provider_version) # revealed: Literal["9.15.0"]
|
||||
```
|
||||
|
||||
## `pkgutil.extend_path`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["/airflow-core/src", "/providers/amazon/src/"]
|
||||
```
|
||||
|
||||
`/airflow-core/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
||||
__version__ = "3.2.0"
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/amazon/__init__.py`:
|
||||
|
||||
```py
|
||||
__version__ = "9.15.0"
|
||||
```
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from airflow import __version__ as airflow_version
|
||||
from airflow.providers.amazon import __version__ as amazon_provider_version
|
||||
|
||||
reveal_type(airflow_version) # revealed: Literal["3.2.0"]
|
||||
reveal_type(amazon_provider_version) # revealed: Literal["9.15.0"]
|
||||
```
|
||||
|
||||
## `extend_path` with keyword arguments
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["/airflow-core/src", "/providers/amazon/src/"]
|
||||
```
|
||||
|
||||
`/airflow-core/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(name=__name__, path=__path__)
|
||||
__version__ = "3.2.0"
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(name=__name__, path=__path__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/__init__.py`:
|
||||
|
||||
```py
|
||||
import pkgutil
|
||||
|
||||
__path__ = pkgutil.extend_path(name=__name__, path=__path__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/amazon/__init__.py`:
|
||||
|
||||
```py
|
||||
__version__ = "9.15.0"
|
||||
```
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from airflow import __version__ as airflow_version
|
||||
from airflow.providers.amazon import __version__ as amazon_provider_version
|
||||
|
||||
reveal_type(airflow_version) # revealed: Literal["3.2.0"]
|
||||
reveal_type(amazon_provider_version) # revealed: Literal["9.15.0"]
|
||||
```
|
||||
|
||||
## incorrect `__import__` arguments
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["/airflow-core/src", "/providers/amazon/src/"]
|
||||
```
|
||||
|
||||
`/airflow-core/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("not_pkgutil").extend_path(__path__, __name__)
|
||||
__version__ = "3.2.0"
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("not_pkgutil").extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("not_pkgutil").extend_path(__path__, __name__)
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/amazon/__init__.py`:
|
||||
|
||||
```py
|
||||
__version__ = "9.15.0"
|
||||
```
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from airflow.providers.amazon import __version__ as amazon_provider_version # error: [unresolved-import]
|
||||
from airflow import __version__ as airflow_version
|
||||
|
||||
reveal_type(airflow_version) # revealed: Literal["3.2.0"]
|
||||
```
|
||||
|
||||
## incorrect `extend_path` arguments
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
extra-paths = ["/airflow-core/src", "/providers/amazon/src/"]
|
||||
```
|
||||
|
||||
`/airflow-core/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, "other_module")
|
||||
__version__ = "3.2.0"
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, "other_module")
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/__init__.py`:
|
||||
|
||||
```py
|
||||
__path__ = __import__("pkgutil").extend_path(__path__, "other_module")
|
||||
```
|
||||
|
||||
`/providers/amazon/src/airflow/providers/amazon/__init__.py`:
|
||||
|
||||
```py
|
||||
__version__ = "9.15.0"
|
||||
```
|
||||
|
||||
`test.py`:
|
||||
|
||||
```py
|
||||
from airflow.providers.amazon import __version__ as amazon_provider_version # error: [unresolved-import]
|
||||
from airflow import __version__ as airflow_version
|
||||
|
||||
reveal_type(airflow_version) # revealed: Literal["3.2.0"]
|
||||
```
|
||||
@@ -260,22 +260,8 @@ def g(
|
||||
reveal_type(x) # revealed: int | str
|
||||
for y in b:
|
||||
reveal_type(y) # revealed: str | int
|
||||
```
|
||||
|
||||
## Union type as iterable where some elements in the union have precise tuple specs
|
||||
|
||||
If all elements in a union can be iterated over, we "union together" their "tuple specs" and are
|
||||
able to infer the iterable element precisely when iterating over the union, in the same way that we
|
||||
infer a precise type for the iterable element when iterating over a `Literal` string or bytes type:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal["foo", b"bar"], y: Literal["foo"] | range):
|
||||
for item in x:
|
||||
reveal_type(item) # revealed: Literal["f", "o", 98, 97, 114]
|
||||
for item in y:
|
||||
reveal_type(item) # revealed: Literal["f", "o"] | int
|
||||
for z in c:
|
||||
reveal_type(z) # revealed: LiteralString | int
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
|
||||
@@ -310,13 +310,13 @@ no longer valid in the inner lazy scope.
|
||||
def f(l: list[str | None]):
|
||||
if l[0] is not None:
|
||||
def _():
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
reveal_type(l[0]) # revealed: str | None | Unknown
|
||||
l = [None]
|
||||
|
||||
def f(l: list[str | None]):
|
||||
l[0] = "a"
|
||||
def _():
|
||||
reveal_type(l[0]) # revealed: str | None
|
||||
reveal_type(l[0]) # revealed: str | None | Unknown
|
||||
l = [None]
|
||||
|
||||
def f(l: list[str | None]):
|
||||
|
||||
@@ -71,140 +71,98 @@ reveal_type(x) # revealed: object
|
||||
|
||||
## Value patterns
|
||||
|
||||
Value patterns are evaluated by equality, which is overridable. Therefore successfully matching on
|
||||
one can only give us information where we know how the subject type implements equality.
|
||||
|
||||
Consider the following example.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
def _(x: Literal["foo"] | int):
|
||||
match x:
|
||||
case "foo":
|
||||
reveal_type(x) # revealed: Literal["foo"] | int
|
||||
x = get_object()
|
||||
|
||||
match x:
|
||||
case "bar":
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
In the first `match`'s `case "foo"` all we know is `x == "foo"`. `x` could be an instance of an
|
||||
arbitrary `int` subclass with an arbitrary `__eq__`, so we can't actually narrow to
|
||||
`Literal["foo"]`.
|
||||
match x:
|
||||
case "foo":
|
||||
reveal_type(x) # revealed: Literal["foo"]
|
||||
case 42:
|
||||
reveal_type(x) # revealed: Literal[42]
|
||||
case 6.0:
|
||||
reveal_type(x) # revealed: float
|
||||
case 1j:
|
||||
reveal_type(x) # revealed: complex
|
||||
case b"foo":
|
||||
reveal_type(x) # revealed: Literal[b"foo"]
|
||||
|
||||
In the second `match`'s `case "bar"` we know `x == "bar"`. As discussed above, this isn't enough to
|
||||
rule out `int`, but we know that `"foo" == "bar"` is false so we can eliminate `Literal["foo"]`.
|
||||
|
||||
More examples follow.
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class C:
|
||||
pass
|
||||
|
||||
def _(x: Literal["foo", "bar", 42, b"foo"] | bool | complex):
|
||||
match x:
|
||||
case "foo":
|
||||
reveal_type(x) # revealed: Literal["foo"] | int | float | complex
|
||||
case 42:
|
||||
reveal_type(x) # revealed: int | float | complex
|
||||
case 6.0:
|
||||
reveal_type(x) # revealed: Literal["bar", b"foo"] | (int & ~Literal[42]) | float | complex
|
||||
case 1j:
|
||||
reveal_type(x) # revealed: Literal["bar", b"foo"] | (int & ~Literal[42]) | float | complex
|
||||
case b"foo":
|
||||
reveal_type(x) # revealed: (int & ~Literal[42]) | Literal[b"foo"] | float | complex
|
||||
case _:
|
||||
reveal_type(x) # revealed: Literal["bar"] | (int & ~Literal[42]) | float | complex
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Value patterns with guard
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
class C:
|
||||
pass
|
||||
x = get_object()
|
||||
|
||||
def _(x: Literal["foo", b"bar"] | int):
|
||||
match x:
|
||||
case "foo" if reveal_type(x): # revealed: Literal["foo"] | int
|
||||
pass
|
||||
case b"bar" if reveal_type(x): # revealed: Literal[b"bar"] | int
|
||||
pass
|
||||
case 42 if reveal_type(x): # revealed: int
|
||||
pass
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
match x:
|
||||
case "foo" if reveal_type(x): # revealed: Literal["foo"]
|
||||
pass
|
||||
case 42 if reveal_type(x): # revealed: Literal[42]
|
||||
pass
|
||||
case 6.0 if reveal_type(x): # revealed: float
|
||||
pass
|
||||
case 1j if reveal_type(x): # revealed: complex
|
||||
pass
|
||||
case b"foo" if reveal_type(x): # revealed: Literal[b"foo"]
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Or patterns
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
from enum import Enum
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
x = get_object()
|
||||
|
||||
def _(color: Color):
|
||||
match color:
|
||||
case Color.RED | Color.GREEN:
|
||||
reveal_type(color) # revealed: Literal[Color.RED, Color.GREEN]
|
||||
case Color.BLUE:
|
||||
reveal_type(color) # revealed: Literal[Color.BLUE]
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
match color:
|
||||
case Color.RED | Color.GREEN | Color.BLUE:
|
||||
reveal_type(color) # revealed: Color
|
||||
match x:
|
||||
case "foo" | 42 | None:
|
||||
reveal_type(x) # revealed: Literal["foo", 42] | None
|
||||
case "foo" | tuple():
|
||||
reveal_type(x) # revealed: tuple[Unknown, ...]
|
||||
case True | False:
|
||||
reveal_type(x) # revealed: bool
|
||||
case 3.14 | 2.718 | 1.414:
|
||||
reveal_type(x) # revealed: float
|
||||
|
||||
match color:
|
||||
case Color.RED:
|
||||
reveal_type(color) # revealed: Literal[Color.RED]
|
||||
case _:
|
||||
reveal_type(color) # revealed: Literal[Color.GREEN, Color.BLUE]
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
match x:
|
||||
case A() | B():
|
||||
reveal_type(x) # revealed: A | B
|
||||
case C():
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
case _:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
match x:
|
||||
case A() | B() | C():
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
case _:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
match x:
|
||||
case A():
|
||||
reveal_type(x) # revealed: A
|
||||
case _:
|
||||
reveal_type(x) # revealed: (B & ~A) | (C & ~A)
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Or patterns with guard
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
def _(x: Literal["foo", b"bar"] | int):
|
||||
match x:
|
||||
case "foo" | 42 if reveal_type(x): # revealed: Literal["foo"] | int
|
||||
pass
|
||||
case b"bar" if reveal_type(x): # revealed: Literal[b"bar"] | int
|
||||
pass
|
||||
case _ if reveal_type(x): # revealed: Literal["foo", b"bar"] | int
|
||||
pass
|
||||
x = get_object()
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
match x:
|
||||
case "foo" | 42 | None if reveal_type(x): # revealed: Literal["foo", 42] | None
|
||||
pass
|
||||
case "foo" | tuple() if reveal_type(x): # revealed: Literal["foo"] | tuple[Unknown, ...]
|
||||
pass
|
||||
case True | False if reveal_type(x): # revealed: bool
|
||||
pass
|
||||
case 3.14 | 2.718 | 1.414 if reveal_type(x): # revealed: float
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Narrowing due to guard
|
||||
@@ -221,7 +179,7 @@ match x:
|
||||
case str() | float() if type(x) is str:
|
||||
reveal_type(x) # revealed: str
|
||||
case "foo" | 42 | None if isinstance(x, int):
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(x) # revealed: Literal[42]
|
||||
case False if x:
|
||||
reveal_type(x) # revealed: Never
|
||||
case "foo" if x := "bar":
|
||||
@@ -243,7 +201,7 @@ reveal_type(x) # revealed: object
|
||||
match x:
|
||||
case str() | float() if type(x) is str and reveal_type(x): # revealed: str
|
||||
pass
|
||||
case "foo" | 42 | None if isinstance(x, int) and reveal_type(x): # revealed: int
|
||||
case "foo" | 42 | None if isinstance(x, int) and reveal_type(x): # revealed: Literal[42]
|
||||
pass
|
||||
case False if x and reveal_type(x): # revealed: Never
|
||||
pass
|
||||
|
||||
@@ -360,14 +360,3 @@ type X = tuple[X, int]
|
||||
def _(x: X):
|
||||
reveal_type(x is x) # revealed: bool
|
||||
```
|
||||
|
||||
### Recursive invariant
|
||||
|
||||
```py
|
||||
type X = dict[str, X]
|
||||
type Y = X | str | dict[str, Y]
|
||||
|
||||
def _(y: Y):
|
||||
if isinstance(y, dict):
|
||||
reveal_type(y) # revealed: dict[str, X] | dict[str, Y]
|
||||
```
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: attributes.md - Attributes - Attributes of standard library modules that aren't yet defined
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## main.py
|
||||
|
||||
```
|
||||
1 | import datetime
|
||||
2 |
|
||||
3 | # error: [unresolved-attribute]
|
||||
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
||||
5 | # error: [unresolved-attribute]
|
||||
6 | reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<module 'datetime'>` has no attribute `UTC`
|
||||
--> src/main.py:4:13
|
||||
|
|
||||
3 | # error: [unresolved-attribute]
|
||||
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
||||
| ^^^^^^^^^^^^
|
||||
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: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-attribute]: Type `<module 'datetime'>` has no attribute `fakenotreal`
|
||||
--> src/main.py:6:13
|
||||
|
|
||||
4 | reveal_type(datetime.UTC) # revealed: Unknown
|
||||
5 | # error: [unresolved-attribute]
|
||||
6 | reveal_type(datetime.fakenotreal) # revealed: Unknown
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: rule `unresolved-attribute` is enabled by default
|
||||
|
||||
```
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: basic.md - Structures - Attempting to import a stdlib submodule when both parts haven't yet been added
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/import/basic.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | import compression.zstd # error: [unresolved-import]
|
||||
2 | from compression import zstd # error: [unresolved-import]
|
||||
3 | import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
4 | from compression import fakebutwhocansay # error: [unresolved-import]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[unresolved-import]: Cannot resolve imported module `compression.zstd`
|
||||
--> src/mdtest_snippet.py:1:8
|
||||
|
|
||||
1 | import compression.zstd # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
2 | from compression import zstd # error: [unresolved-import]
|
||||
3 | import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
|
|
||||
info: The stdlib module `compression` is only available on Python 3.14+
|
||||
info: Python 3.10 was assumed when resolving modules because it was specified on the command line
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-import]: Cannot resolve imported module `compression`
|
||||
--> src/mdtest_snippet.py:2:6
|
||||
|
|
||||
1 | import compression.zstd # error: [unresolved-import]
|
||||
2 | from compression import zstd # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^
|
||||
3 | import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
4 | from compression import fakebutwhocansay # error: [unresolved-import]
|
||||
|
|
||||
info: The stdlib module `compression` is only available on Python 3.14+
|
||||
info: Python 3.10 was assumed when resolving modules because it was specified on the command line
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-import]: Cannot resolve imported module `compression.fakebutwhocansay`
|
||||
--> src/mdtest_snippet.py:3:8
|
||||
|
|
||||
1 | import compression.zstd # error: [unresolved-import]
|
||||
2 | from compression import zstd # error: [unresolved-import]
|
||||
3 | import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
4 | from compression import fakebutwhocansay # error: [unresolved-import]
|
||||
|
|
||||
info: The stdlib module `compression` is only available on Python 3.14+
|
||||
info: Python 3.10 was assumed when resolving modules because it was specified on the command line
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error[unresolved-import]: Cannot resolve imported module `compression`
|
||||
--> src/mdtest_snippet.py:4:6
|
||||
|
|
||||
2 | from compression import zstd # error: [unresolved-import]
|
||||
3 | import compression.fakebutwhocansay # error: [unresolved-import]
|
||||
4 | from compression import fakebutwhocansay # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
info: The stdlib module `compression` is only available on Python 3.14+
|
||||
info: Python 3.10 was assumed when resolving modules because it was specified on the command line
|
||||
info: rule `unresolved-import` is enabled by default
|
||||
|
||||
```
|
||||
@@ -33,13 +33,13 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syn
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-syntax]: cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
error[invalid-syntax]
|
||||
--> src/mdtest_snippet.py:6:19
|
||||
|
|
||||
4 | async def f():
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax…
|
||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)
|
||||
7 | async def test():
|
||||
8 | # error: [not-iterable] "Object of type `range` is not async-iterable"
|
||||
|
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
source: crates/ty_test/src/lib.rs
|
||||
assertion_line: 427
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: union_call.md - Calling a union of function types - Try to cover all possible reasons - Truncation for long unions and literals
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing import Literal, Union
|
||||
2 |
|
||||
3 | class A: ...
|
||||
4 | class B: ...
|
||||
5 | class C: ...
|
||||
6 | class D: ...
|
||||
7 | class E: ...
|
||||
8 | class F: ...
|
||||
9 |
|
||||
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||
11 | return 0
|
||||
12 |
|
||||
13 | def _(n: int):
|
||||
14 | x = n
|
||||
15 | # error: [invalid-argument-type]
|
||||
16 | f1(x)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-argument-type]: Argument to function `f1` is incorrect
|
||||
--> src/mdtest_snippet.py:16:8
|
||||
|
|
||||
14 | x = n
|
||||
15 | # error: [invalid-argument-type]
|
||||
16 | f1(x)
|
||||
| ^ Expected `Literal[1, 2, 3, 4, 5, ... omitted 3 literals] | A | B | ... omitted 4 union elements`, found `int`
|
||||
|
|
||||
info: Function defined here
|
||||
--> src/mdtest_snippet.py:10:5
|
||||
|
|
||||
8 | class F: ...
|
||||
9 |
|
||||
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||
| ^^ ----------------------------------------------------------- Parameter declared here
|
||||
11 | return 0
|
||||
|
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
@@ -20,11 +20,11 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/version_rela
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[invalid-syntax]: Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
error[invalid-syntax]
|
||||
--> src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | match 2: # error: 1 [invalid-syntax] "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
| ^^^^^
|
||||
| ^^^^^ Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)
|
||||
2 | case 1:
|
||||
3 | print("it's one")
|
||||
|
|
||||
|
||||
@@ -68,10 +68,6 @@ reveal_type((1,).__class__()) # revealed: tuple[Literal[1]]
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `iterable`"
|
||||
reveal_type((1, 2).__class__()) # revealed: tuple[Literal[1], Literal[2]]
|
||||
|
||||
def g(x: tuple[int, str] | tuple[bytes, bool], y: tuple[int, str] | tuple[bytes, bool, bytes]):
|
||||
reveal_type(tuple(x)) # revealed: tuple[int, str] | tuple[bytes, bool]
|
||||
reveal_type(tuple(y)) # revealed: tuple[int, str] | tuple[bytes, bool, bytes]
|
||||
```
|
||||
|
||||
## Instantiating tuple subclasses
|
||||
|
||||
@@ -233,12 +233,10 @@ Person({"name": "Alice"})
|
||||
|
||||
# error: [missing-typed-dict-key] "Missing required key 'age' in TypedDict `Person` constructor"
|
||||
accepts_person({"name": "Alice"})
|
||||
|
||||
# TODO: this should be an error, similar to the above
|
||||
house.owner = {"name": "Alice"}
|
||||
|
||||
a_person: Person
|
||||
# error: [missing-typed-dict-key] "Missing required key 'age' in TypedDict `Person` constructor"
|
||||
# TODO: this should be an error, similar to the above
|
||||
a_person = {"name": "Alice"}
|
||||
```
|
||||
|
||||
@@ -256,12 +254,9 @@ Person({"name": None, "age": 30})
|
||||
accepts_person({"name": None, "age": 30})
|
||||
# TODO: this should be an error, similar to the above
|
||||
house.owner = {"name": None, "age": 30}
|
||||
|
||||
a_person: Person
|
||||
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `Person`: value of type `None`"
|
||||
# TODO: this should be an error, similar to the above
|
||||
a_person = {"name": None, "age": 30}
|
||||
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `Person`: value of type `None`"
|
||||
(a_person := {"name": None, "age": 30})
|
||||
```
|
||||
|
||||
All of these have an extra field that is not defined in the `TypedDict`:
|
||||
@@ -278,12 +273,9 @@ Person({"name": "Alice", "age": 30, "extra": True})
|
||||
accepts_person({"name": "Alice", "age": 30, "extra": True})
|
||||
# TODO: this should be an error
|
||||
house.owner = {"name": "Alice", "age": 30, "extra": True}
|
||||
|
||||
# TODO: this should be an error
|
||||
a_person: Person
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
a_person = {"name": "Alice", "age": 30, "extra": True}
|
||||
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "extra""
|
||||
(a_person := {"name": "Alice", "age": 30, "extra": True})
|
||||
```
|
||||
|
||||
## Type ignore compatibility issues
|
||||
@@ -915,7 +907,7 @@ grandchild: Node = {"name": "grandchild", "parent": child}
|
||||
|
||||
nested: Node = {"name": "n1", "parent": {"name": "n2", "parent": {"name": "n3", "parent": None}}}
|
||||
|
||||
# error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `Node`: value of type `Literal[3]`"
|
||||
# TODO: this should be an error (invalid type for `name` in innermost node)
|
||||
nested_invalid: Node = {"name": "n1", "parent": {"name": "n2", "parent": {"name": 3, "parent": None}}}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
spark # too many iterations (in `exported_names` query), `should not be able to access instance member `spark` of type variable IndexOpsLike@astype in inferable position`
|
||||
steam.py # dependency graph cycle when querying TypeVarInstance < 'db >::lazy_default_(Id(2e007)), set cycle_fn/cycle_initial to fixpoint iterate.
|
||||
spark # too many iterations (in `exported_names` query)
|
||||
steam.py # hangs (single threaded)
|
||||
|
||||
@@ -12,9 +12,6 @@ pub trait Db: SourceDb {
|
||||
fn rule_selection(&self, file: File) -> &RuleSelection;
|
||||
|
||||
fn lint_registry(&self) -> &LintRegistry;
|
||||
|
||||
/// Whether ty is running with logging verbosity INFO or higher (`-v` or more).
|
||||
fn verbose(&self) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -129,10 +126,6 @@ pub(crate) mod tests {
|
||||
fn lint_registry(&self) -> &LintRegistry {
|
||||
default_lint_registry()
|
||||
}
|
||||
|
||||
fn verbose(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user