Compare commits
1 Commits
david/repr
...
cjm/recrel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89228c344c |
20
.github/workflows/ci.yaml
vendored
20
.github/workflows/ci.yaml
vendored
@@ -429,7 +429,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -451,7 +451,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -652,7 +652,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -682,7 +682,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@808dcb1b503398677d089d3216c51ac7cc11e7ab # v1.14.2
|
||||
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -722,7 +722,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -765,7 +765,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -897,7 +897,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -911,7 +911,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
@@ -930,7 +930,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -944,7 +944,7 @@ jobs:
|
||||
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
|
||||
|
||||
- name: "Run benchmarks"
|
||||
uses: CodSpeedHQ/action@0b6e7a3d96c9d2a6057e7bcea6b45aaf2f7ce60b # v3.8.0
|
||||
uses: CodSpeedHQ/action@c28fe9fbe7d57a3da1b7834ae3761c1d8217612d # v3.7.0
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
109
.github/workflows/typing_conformance.yaml
vendored
109
.github/workflows/typing_conformance.yaml
vendored
@@ -1,109 +0,0 @@
|
||||
name: Run typing conformance
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "crates/ty*/**"
|
||||
- "crates/ruff_db"
|
||||
- "crates/ruff_python_ast"
|
||||
- "crates/ruff_python_parser"
|
||||
- ".github/workflows/typing_conformance.yaml"
|
||||
- ".github/workflows/typing_conformance_comment.yaml"
|
||||
- "Cargo.lock"
|
||||
- "!**.md"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
typing_conformance:
|
||||
name: Compute diagnostic diff
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: python/typing
|
||||
ref: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
|
||||
path: typing
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Compute diagnostic diff
|
||||
shell: bash
|
||||
run: |
|
||||
RUFF_DIR="$GITHUB_WORKSPACE/ruff"
|
||||
|
||||
# Build the executable for the old and new commit
|
||||
(
|
||||
cd ruff
|
||||
|
||||
echo "new commit"
|
||||
git checkout -b new_commit "${{ github.event.pull_request.head.sha }}"
|
||||
git rev-list --format=%s --max-count=1 new_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-new
|
||||
|
||||
echo "old commit (merge base)"
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b old_commit "$MERGE_BASE"
|
||||
git rev-list --format=%s --max-count=1 old_commit
|
||||
cargo build --release --bin ty
|
||||
mv target/release/ty ty-old
|
||||
)
|
||||
|
||||
(
|
||||
cd typing/conformance/tests
|
||||
|
||||
echo "Running ty on old commit (merge base)"
|
||||
"$RUFF_DIR/ty-old" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/old-output.txt" 2>&1 || true
|
||||
|
||||
echo "Running ty on new commit"
|
||||
"$RUFF_DIR/ty-new" check --color=never --output-format=concise . > "$GITHUB_WORKSPACE/new-output.txt" 2>&1 || true
|
||||
)
|
||||
|
||||
if ! diff -u old-output.txt new-output.txt > typing_conformance_diagnostics.diff; then
|
||||
echo "Differences found between base and PR"
|
||||
else
|
||||
echo "No differences found"
|
||||
touch typing_conformance_diagnostics.diff
|
||||
fi
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: typing_conformance_diagnostics_diff
|
||||
path: typing_conformance_diagnostics.diff
|
||||
|
||||
- name: Upload pr-number
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
@@ -81,7 +81,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.5
|
||||
rev: v0.12.4
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff-check
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,35 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.7
|
||||
|
||||
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
|
||||
|
||||
## 0.12.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
|
||||
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
|
||||
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
|
||||
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
|
||||
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
|
||||
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
|
||||
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
|
||||
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
|
||||
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
|
||||
|
||||
### Performance
|
||||
|
||||
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
|
||||
|
||||
## 0.12.5
|
||||
|
||||
### Preview features
|
||||
|
||||
358
Cargo.lock
generated
358
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
@@ -77,52 +77,52 @@ checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-lossy"
|
||||
version = "1.1.4"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04d3a5dc826f84d0ea11882bb8054ff7f3d482602e11bb181101303a279ea01f"
|
||||
checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.3"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-svg"
|
||||
version = "0.1.9"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c"
|
||||
checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"anstyle-lossy",
|
||||
"anstyle-parse",
|
||||
"html-escape",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.9"
|
||||
version = "3.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -210,9 +210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
@@ -301,9 +301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@@ -337,18 +337,18 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.4"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
version = "1.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -357,9 +357,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
@@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.42"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.42"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -431,9 +431,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.5.55"
|
||||
version = "4.5.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||
checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@@ -451,9 +451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete_nushell"
|
||||
version = "4.5.8"
|
||||
version = "4.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce"
|
||||
checksum = "c6a8b1593457dfc2fe539002b795710d022dc62a65bf15023f039f9760c7b18a"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
@@ -473,9 +473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
|
||||
[[package]]
|
||||
name = "clearscreen"
|
||||
@@ -492,9 +492,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29180405ab3b37bb020246ea66bf8ae233708766fd59581ae929feaef10ce91"
|
||||
checksum = "922018102595f6668cdd09c03f4bff2d951ce2318c6dca4fe11bdcb24b65b2bf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 1.3.3",
|
||||
@@ -510,9 +510,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2454d874ca820ffd71273565530ad318f413195bbc99dce6c958ca07db362c63"
|
||||
checksum = "24d8ad82d2383cb74995f58993cbdd2914aed57b2f91f46580310dd81dc3d05a"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-criterion-compat-walltime",
|
||||
@@ -521,16 +521,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-criterion-compat-walltime"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093a9383cdd1a5a0bd1a47cdafb49ae0c6dcd0793c8fb8f79768bab423128c9c"
|
||||
checksum = "61badaa6c452d192a29f8387147888f0ab358553597c3fe9bf8a162ef7c2fa64"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"codspeed",
|
||||
"criterion-plot 0.5.0",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
@@ -546,9 +546,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1c73bce1e3f47738bf74a6b58b72a49b4f40c837ce420d8d65a270298592aac"
|
||||
checksum = "3acf1d6fe367c2ff5ff136ca723f678490c3691d59d7f2b83d5e53b7b25ac91e"
|
||||
dependencies = [
|
||||
"codspeed",
|
||||
"codspeed-divan-compat-macros",
|
||||
@@ -557,9 +557,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-macros"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea51dd8add7eba774cc24b4a98324252ac3ec092ccb5f07e52bbe1cb72a6d373"
|
||||
checksum = "bcfa2013d7bee54a497d0e1410751d5de690fd67a3e9eb728ca049b6a3d16d0b"
|
||||
dependencies = [
|
||||
"divan-macros",
|
||||
"itertools 0.14.0",
|
||||
@@ -571,9 +571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "codspeed-divan-compat-walltime"
|
||||
version = "3.0.4"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417e9edfc4b0289d4b9b48e62f98c6168d5e30c0e612b2935e394b0dd930fe83"
|
||||
checksum = "e513100fb0e7ba02fb3824546ecd2abfb8f334262f0972225b463aad07f99ff0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"clap",
|
||||
@@ -586,15 +586,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "collection_literals"
|
||||
version = "1.0.2"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b3f65b8fb8e88ba339f7d23a390fe1b0896217da05e2a66c584c9b29a91df8"
|
||||
checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
@@ -704,24 +704,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.7.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
|
||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot 0.6.0",
|
||||
"criterion-plot",
|
||||
"itertools 0.13.0",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
@@ -742,16 +742,6 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
@@ -810,9 +800,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
@@ -1000,9 +990,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
@@ -1030,12 +1020,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1096,9 +1086,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.2"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -1161,9 +1151,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.6.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca171f9f8ed2f416ac044de2dc4acde3e356662a14ac990345639653bdc7fc28"
|
||||
checksum = "028f3cfad7c3e3b1d8d04ef0a1c03576f2d62800803fe1301a4cd262849f2dea"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1172,9 +1162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.6.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965bc5c1c5fe05c5bbd398bb9b3f0f14d750261ebdd1af959f2c8a603fedb5ad"
|
||||
checksum = "3a09c2043819a3def7bfbb4927e7df96aab0da4cfd8824484b22d0c94e84458e"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
@@ -1184,11 +1174,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.23"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width 0.2.1",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1199,7 +1189,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1290,9 +1280,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
@@ -1385,9 +1381,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.0.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
|
||||
checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
@@ -1401,9 +1397,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.0.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
|
||||
checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
@@ -1615,7 +1611,7 @@ version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.5.1",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -1805,9 +1801,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.8"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
@@ -1974,23 +1970,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2001,9 +1997,9 @@ checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "newtype-uuid"
|
||||
version = "1.2.4"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a17d82edb1c8a6c20c238747ae7aae9181133e766bc92cd2556fdd764407d0d1"
|
||||
checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
@@ -2093,11 +2089,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.17.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -2107,12 +2103,6 @@ version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
@@ -2137,9 +2127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.2"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224"
|
||||
checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -2147,9 +2137,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "7.1.1"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63eceb7b5d757011a87d08eb2123db15d87fb0c281f65d101ce30a1e96c3ad5c"
|
||||
checksum = "c86e2db86dd008b4c88c77a9bb83d9286bf77204e255bb3fda3b2eebcae66b62"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -2162,9 +2152,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
@@ -2172,9 +2162,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
@@ -2289,9 +2279,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
@@ -2300,9 +2290,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
|
||||
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
@@ -2310,9 +2300,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
|
||||
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
@@ -2323,10 +2313,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.1"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
|
||||
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
@@ -2383,9 +2374,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
@@ -2571,9 +2562,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
@@ -2662,9 +2653,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
@@ -2743,7 +2734,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2795,7 +2786,7 @@ dependencies = [
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"tikv-jemallocator",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
"wild",
|
||||
@@ -2811,7 +2802,7 @@ dependencies = [
|
||||
"ruff_annotate_snippets",
|
||||
"serde",
|
||||
"snapbox",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tryfn",
|
||||
"unicode-width 0.2.1",
|
||||
]
|
||||
@@ -2927,7 +2918,7 @@ dependencies = [
|
||||
"similar",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-indicatif",
|
||||
"tracing-subscriber",
|
||||
@@ -2995,7 +2986,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3048,7 +3039,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.1",
|
||||
@@ -3298,7 +3289,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
@@ -3327,7 +3318,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3388,7 +3379,7 @@ dependencies = [
|
||||
"shellexpand",
|
||||
"strum",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3415,22 +3406,22 @@ checksum = "781442f29170c5c93b7185ad559492601acdc71d5bb0706f5868094f45cfcd08"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.8"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
@@ -3441,7 +3432,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=f3dc2f30f9a250618161e35600a00de7fe744953#f3dc2f30f9a250618161e35600a00de7fe744953"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3466,12 +3457,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=f3dc2f30f9a250618161e35600a00de7fe744953#f3dc2f30f9a250618161e35600a00de7fe744953"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=f3dc2f30f9a250618161e35600a00de7fe744953#f3dc2f30f9a250618161e35600a00de7fe744953"
|
||||
source = "git+https://github.com/salsa-rs/salsa?rev=dba66f1a37acca014c2402f231ed5b361bd7d8fe#dba66f1a37acca014c2402f231ed5b361bd7d8fe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3938,11 +3929,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4023,9 +4015,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.4"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -4096,9 +4088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4128,9 +4120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-indicatif"
|
||||
version = "0.3.12"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1983afead46ff13a3c93581e0cec31d20b29efdd22cbdaa8b9f850eccf2c352"
|
||||
checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200"
|
||||
dependencies = [
|
||||
"indicatif",
|
||||
"tracing",
|
||||
@@ -4203,7 +4195,7 @@ dependencies = [
|
||||
"ruff_python_trivia",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"tracing-flame",
|
||||
"tracing-subscriber",
|
||||
@@ -4264,7 +4256,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_vendored",
|
||||
@@ -4387,7 +4379,7 @@ dependencies = [
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.2",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -4711,9 +4703,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
@@ -4892,9 +4884,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
version = "0.61.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
@@ -4927,28 +4919,37 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -4964,7 +4965,7 @@ version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.3",
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4985,11 +4986,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.3"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
@@ -5098,9 +5098,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.12"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
|
||||
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -5167,18 +5167,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.26"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.26"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -73,7 +73,7 @@ console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0.0" }
|
||||
countme = { version = "3.0.1" }
|
||||
compact_str = "0.9.0"
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
criterion = { version = "0.6.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
@@ -83,7 +83,7 @@ etcetera = { version = "0.10.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.6.0", features = [
|
||||
get-size2 = { version = "0.5.0", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -141,7 +141,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", rev = "f3dc2f30f9a250618161e35600a00de7fe744953" }
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "dba66f1a37acca014c2402f231ed5b361bd7d8fe" }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
|
||||
@@ -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.12.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.7/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.12.5/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.5/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.12.7
|
||||
rev: v0.12.5
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -57,40 +57,33 @@ fn dependencies() -> Result<()> {
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f(): pass
|
||||
"#})?;
|
||||
root.child("ruff")
|
||||
.child("e.pyi")
|
||||
.write_str(indoc::indoc! {r#"
|
||||
def f() -> None: ...
|
||||
"#})?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec(),
|
||||
}, {
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py",
|
||||
"ruff/e.pyi"
|
||||
],
|
||||
"ruff/e.py": [],
|
||||
"ruff/e.pyi": []
|
||||
}
|
||||
assert_cmd_snapshot!(command().current_dir(&root), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
{
|
||||
"ruff/__init__.py": [],
|
||||
"ruff/a.py": [
|
||||
"ruff/b.py"
|
||||
],
|
||||
"ruff/b.py": [
|
||||
"ruff/c.py"
|
||||
],
|
||||
"ruff/c.py": [
|
||||
"ruff/d.py"
|
||||
],
|
||||
"ruff/d.py": [
|
||||
"ruff/e.py"
|
||||
],
|
||||
"ruff/e.py": []
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"#);
|
||||
----- stderr -----
|
||||
"###);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -351,41 +351,6 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_tuple_implicit_instance_attributes(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
criterion.bench_function("ty_micro[many_tuple_assignments]", |b| {
|
||||
b.iter_batched_ref(
|
||||
|| {
|
||||
// This is a regression benchmark for a case that used to hang:
|
||||
// https://github.com/astral-sh/ty/issues/765
|
||||
setup_micro_case(
|
||||
r#"
|
||||
from typing import Any
|
||||
|
||||
class A:
|
||||
foo: tuple[Any, ...]
|
||||
|
||||
class B(A):
|
||||
def __init__(self, parent: "C", x: tuple[Any]):
|
||||
self.foo = parent.foo + x
|
||||
|
||||
class C(A):
|
||||
def __init__(self, parent: B, x: tuple[Any]):
|
||||
self.foo = parent.foo + x
|
||||
"#,
|
||||
)
|
||||
},
|
||||
|case| {
|
||||
let Case { db, .. } = case;
|
||||
let result = db.check();
|
||||
assert_eq!(result.len(), 0);
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_complex_constrained_attributes_1(criterion: &mut Criterion) {
|
||||
setup_rayon();
|
||||
|
||||
@@ -665,7 +630,6 @@ criterion_group!(
|
||||
micro,
|
||||
benchmark_many_string_assignments,
|
||||
benchmark_many_tuple_assignments,
|
||||
benchmark_tuple_implicit_instance_attributes,
|
||||
benchmark_complex_constrained_attributes_1,
|
||||
benchmark_complex_constrained_attributes_2,
|
||||
benchmark_many_enum_members,
|
||||
|
||||
@@ -6,9 +6,7 @@ use ruff_source_file::{LineColumn, SourceCode, SourceFile};
|
||||
use ruff_annotate_snippets::Level as AnnotateLevel;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
pub use self::render::{
|
||||
DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input, ceil_char_boundary,
|
||||
};
|
||||
pub use self::render::{DisplayDiagnostic, DisplayDiagnostics, FileResolver, Input};
|
||||
use crate::{Db, files::File};
|
||||
|
||||
mod render;
|
||||
@@ -21,7 +19,7 @@ mod stylesheet;
|
||||
/// characteristics in the inputs given to the tool. Typically, but not always,
|
||||
/// a characteristic is a deficiency. An example of a characteristic that is
|
||||
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
@@ -479,7 +477,7 @@ impl Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
@@ -555,7 +553,7 @@ impl Eq for RenderingSortKey<'_> {}
|
||||
/// Currently, the order in which sub-diagnostics are rendered relative to one
|
||||
/// another (for a single parent diagnostic) is the order in which they were
|
||||
/// attached to the diagnostic.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
@@ -659,7 +657,7 @@ impl SubDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
struct SubDiagnosticInner {
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: DiagnosticMessage,
|
||||
@@ -687,7 +685,7 @@ struct SubDiagnosticInner {
|
||||
///
|
||||
/// Messages attached to annotations should also be as brief and specific as
|
||||
/// possible. Long messages could negative impact the quality of rendering.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
@@ -807,7 +805,7 @@ impl Annotation {
|
||||
///
|
||||
/// These tags are used to provide additional information about the annotation.
|
||||
/// and are passed through to the language server protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
@@ -1016,7 +1014,7 @@ impl std::fmt::Display for DiagnosticId {
|
||||
///
|
||||
/// This enum presents a unified interface to these two types for the sake of creating [`Span`]s and
|
||||
/// emitting diagnostics from both ty and ruff.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub enum UnifiedFile {
|
||||
Ty(File),
|
||||
Ruff(SourceFile),
|
||||
@@ -1080,7 +1078,7 @@ impl DiagnosticSource {
|
||||
/// It consists of a `File` and an optional range into that file. When the
|
||||
/// range isn't present, it semantically implies that the diagnostic refers to
|
||||
/// the entire file. For example, when the file should be executable but isn't.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub struct Span {
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
@@ -1158,7 +1156,7 @@ impl From<crate::files::FileRange> for Span {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
pub enum Severity {
|
||||
Info,
|
||||
Warning,
|
||||
@@ -1193,7 +1191,7 @@ impl Severity {
|
||||
/// This type only exists to add an additional `Help` severity that isn't present in `Severity` or
|
||||
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
|
||||
/// deleted and the two combined again.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
|
||||
pub enum SubDiagnosticSeverity {
|
||||
Help,
|
||||
Info,
|
||||
@@ -1428,7 +1426,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
/// In most cases, callers shouldn't need to use this. Instead, there is
|
||||
/// a blanket trait implementation for `IntoDiagnosticMessage` for
|
||||
/// anything that implements `std::fmt::Display`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub struct DiagnosticMessage(Box<str>);
|
||||
|
||||
impl DiagnosticMessage {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -8,7 +7,7 @@ use ruff_annotate_snippets::{
|
||||
};
|
||||
use ruff_notebook::{Notebook, NotebookIndex};
|
||||
use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::stylesheet::DiagnosticStylesheet;
|
||||
use crate::{
|
||||
@@ -521,7 +520,7 @@ impl<'r> RenderableSnippets<'r> {
|
||||
#[derive(Debug)]
|
||||
struct RenderableSnippet<'r> {
|
||||
/// The actual snippet text.
|
||||
snippet: Cow<'r, str>,
|
||||
snippet: &'r str,
|
||||
/// The absolute line number corresponding to where this
|
||||
/// snippet begins.
|
||||
line_start: OneIndexed,
|
||||
@@ -581,12 +580,6 @@ impl<'r> RenderableSnippet<'r> {
|
||||
.iter()
|
||||
.map(|ann| RenderableAnnotation::new(snippet_start, ann))
|
||||
.collect();
|
||||
|
||||
let EscapedSourceCode {
|
||||
text: snippet,
|
||||
annotations,
|
||||
} = replace_unprintable(snippet, annotations).fix_up_empty_spans_after_line_terminator();
|
||||
|
||||
RenderableSnippet {
|
||||
snippet,
|
||||
line_start,
|
||||
@@ -597,7 +590,7 @@ impl<'r> RenderableSnippet<'r> {
|
||||
|
||||
/// Convert this to an "annotate" snippet.
|
||||
fn to_annotate<'a>(&'a self, path: &'a str) -> AnnotateSnippet<'a> {
|
||||
AnnotateSnippet::source(&self.snippet)
|
||||
AnnotateSnippet::source(self.snippet)
|
||||
.origin(path)
|
||||
.line_start(self.line_start.get())
|
||||
.annotations(
|
||||
@@ -827,204 +820,6 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
|
||||
path
|
||||
}
|
||||
|
||||
/// Given some source code and annotation ranges, this routine replaces
|
||||
/// unprintable characters with printable representations of them.
|
||||
///
|
||||
/// The source code and annotations returned are updated to reflect changes made
|
||||
/// to the source code (if any).
|
||||
///
|
||||
/// We don't need to normalize whitespace, such as converting tabs to spaces,
|
||||
/// because `annotate-snippets` handles that internally. Similarly, it's safe to
|
||||
/// modify the annotation ranges by inserting 3-byte Unicode replacements
|
||||
/// because `annotate-snippets` will account for their actual width when
|
||||
/// rendering and displaying the column to the user.
|
||||
fn replace_unprintable<'r>(
|
||||
source: &'r str,
|
||||
mut annotations: Vec<RenderableAnnotation<'r>>,
|
||||
) -> EscapedSourceCode<'r> {
|
||||
// Updates the annotation ranges given by the caller whenever a single byte (at `index` in
|
||||
// `source`) is replaced with `len` bytes.
|
||||
//
|
||||
// When the index occurs before the start of the range, the range is
|
||||
// offset by `len`. When the range occurs after or at the start but before
|
||||
// the end, then the end of the range only is offset by `len`.
|
||||
let mut update_ranges = |index: usize, len: u32| {
|
||||
for ann in &mut annotations {
|
||||
if index < usize::from(ann.range.start()) {
|
||||
ann.range += TextSize::new(len - 1);
|
||||
} else if index < usize::from(ann.range.end()) {
|
||||
ann.range = ann.range.add_end(TextSize::new(len - 1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If `c` is an unprintable character, then this returns a printable
|
||||
// representation of it (using a fancier Unicode codepoint).
|
||||
let unprintable_replacement = |c: char| -> Option<char> {
|
||||
match c {
|
||||
'\x07' => Some('␇'),
|
||||
'\x08' => Some('␈'),
|
||||
'\x1b' => Some('␛'),
|
||||
'\x7f' => Some('␡'),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
let mut last_end = 0;
|
||||
let mut result = String::new();
|
||||
for (index, c) in source.char_indices() {
|
||||
if let Some(printable) = unprintable_replacement(c) {
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
let len = printable.text_len().to_u32();
|
||||
update_ranges(result.text_len().to_usize(), len);
|
||||
|
||||
result.push(printable);
|
||||
last_end = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// No tabs or unprintable chars
|
||||
if result.is_empty() {
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Borrowed(source),
|
||||
}
|
||||
} else {
|
||||
result.push_str(&source[last_end..]);
|
||||
EscapedSourceCode {
|
||||
annotations,
|
||||
text: Cow::Owned(result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EscapedSourceCode<'r> {
|
||||
text: Cow<'r, str>,
|
||||
annotations: Vec<RenderableAnnotation<'r>>,
|
||||
}
|
||||
|
||||
impl<'r> EscapedSourceCode<'r> {
|
||||
// This attempts to "fix up" the spans on each annotation in the case where
|
||||
// it's an empty span immediately following a line terminator.
|
||||
//
|
||||
// At present, `annotate-snippets` (both upstream and our vendored copy)
|
||||
// will render annotations of such spans to point to the space immediately
|
||||
// following the previous line. But ideally, this should point to the space
|
||||
// immediately preceding the next line.
|
||||
//
|
||||
// After attempting to fix `annotate-snippets` and giving up after a couple
|
||||
// hours, this routine takes a different tact: it adjusts the span to be
|
||||
// non-empty and it will cover the first codepoint of the following line.
|
||||
// This forces `annotate-snippets` to point to the right place.
|
||||
//
|
||||
// See also: <https://github.com/astral-sh/ruff/issues/15509> and
|
||||
// `ruff_linter::message::text::SourceCode::fix_up_empty_spans_after_line_terminator`,
|
||||
// from which this was adapted.
|
||||
fn fix_up_empty_spans_after_line_terminator(mut self) -> EscapedSourceCode<'r> {
|
||||
for ann in &mut self.annotations {
|
||||
let range = ann.range;
|
||||
if !range.is_empty()
|
||||
|| range.start() == TextSize::from(0)
|
||||
|| range.start() >= self.text.text_len()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !matches!(
|
||||
self.text.as_bytes()[range.start().to_usize() - 1],
|
||||
b'\n' | b'\r'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let start = range.start();
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
ann.range = TextRange::new(start, end);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextLen, TextSize};
|
||||
///
|
||||
/// let source = "❤️🧡💛💚💙💜";
|
||||
/// assert_eq!(source.text_len(), TextSize::from(26));
|
||||
/// assert!(!source.is_char_boundary(13));
|
||||
///
|
||||
/// let closest = ceil_char_boundary(source, TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&source[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::ceil_char_boundary;
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
///
|
||||
/// let source = "Hello";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let source = "α";
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// ceil_char_boundary(source, TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(text: &str, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(text.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| text.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -2564,7 +2359,7 @@ watermelon
|
||||
}
|
||||
|
||||
/// Returns a builder for tersely constructing diagnostics.
|
||||
pub(super) fn builder(
|
||||
fn builder(
|
||||
&mut self,
|
||||
identifier: &'static str,
|
||||
severity: Severity,
|
||||
@@ -2631,7 +2426,7 @@ watermelon
|
||||
///
|
||||
/// See the docs on `TestEnvironment::span` for the meaning of
|
||||
/// `path`, `line_offset_start` and `line_offset_end`.
|
||||
pub(super) fn primary(
|
||||
fn primary(
|
||||
mut self,
|
||||
path: &str,
|
||||
line_offset_start: &str,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::diagnostic::{
|
||||
DiagnosticFormat, Severity,
|
||||
render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics},
|
||||
DiagnosticFormat,
|
||||
render::tests::{create_diagnostics, create_syntax_error_diagnostics},
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -63,139 +63,4 @@ mod tests {
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit c9b99e4.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[no-indented-block]: Expected an indented block
|
||||
/// --> example.py:3:1
|
||||
/// |
|
||||
/// 2 | if False:
|
||||
/// | ^
|
||||
/// 3 | print()
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the end of the previous line instead of the start of the next.
|
||||
#[test]
|
||||
fn empty_span_after_line_terminator() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add(
|
||||
"example.py",
|
||||
r#"
|
||||
if False:
|
||||
print()
|
||||
"#,
|
||||
);
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"no-indented-block",
|
||||
Severity::Error,
|
||||
"Expected an indented block",
|
||||
)
|
||||
.primary("example.py", "3:0", "3:0", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[no-indented-block]: Expected an indented block
|
||||
--> example.py:3:1
|
||||
|
|
||||
2 | if False:
|
||||
3 | print()
|
||||
| ^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
/// Check that the new `full` rendering code in `ruff_db` handles cases fixed by commit 2922490.
|
||||
///
|
||||
/// For example, without the fix, we get diagnostics like this:
|
||||
///
|
||||
/// ```
|
||||
/// error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
/// --> example.py:1:25
|
||||
/// |
|
||||
/// 1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
/// | ^
|
||||
/// |
|
||||
/// ```
|
||||
///
|
||||
/// where the caret points to the `f` in the f-string instead of the start of the invalid
|
||||
/// character (`^Z`).
|
||||
#[test]
|
||||
fn unprintable_characters() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "nested_fstrings = f'{f'{f''}'}'");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:24", "1:24", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:25
|
||||
|
|
||||
1 | nested_fstrings = f'␈{f'{f'␛'}'}'
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_unprintable_characters() -> std::io::Result<()> {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env
|
||||
.builder(
|
||||
"invalid-character-sub",
|
||||
Severity::Error,
|
||||
r#"Invalid unescaped character SUB, use "\x1A" instead"#,
|
||||
)
|
||||
.primary("example.py", "1:1", "1:1", "")
|
||||
.build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r#"
|
||||
error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
|
||||
--> example.py:1:2
|
||||
|
|
||||
1 | ␈␛
|
||||
| ^
|
||||
|
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure that the header column matches the column in the user's input, even if we've replaced
|
||||
/// tabs with spaces for rendering purposes.
|
||||
#[test]
|
||||
fn tab_replacement() {
|
||||
let mut env = TestEnvironment::new();
|
||||
env.add("example.py", "def foo():\n\treturn 1");
|
||||
env.format(DiagnosticFormat::Full);
|
||||
|
||||
let diagnostic = env.err().primary("example.py", "2:1", "2:9", "").build();
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[test-diagnostic]: main diagnostic message
|
||||
--> example.py:2:2
|
||||
|
|
||||
1 | def foo():
|
||||
2 | return 1
|
||||
| ^^^^^^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::source::source_text;
|
||||
/// reflected in the changed AST offsets.
|
||||
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
|
||||
/// for determining if a query result is unchanged.
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
let _span = tracing::trace_span!("parsed_module", ?file).entered();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::Db;
|
||||
use crate::files::{File, FilePath};
|
||||
|
||||
/// Reads the source text of a python text file (must be valid UTF8) or notebook.
|
||||
#[salsa::tracked(heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
let path = file.path(db);
|
||||
let _span = tracing::trace_span!("source_text", file = %path).entered();
|
||||
@@ -69,21 +69,21 @@ impl SourceText {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match &self.inner.kind {
|
||||
SourceTextKind::Text(source) => source,
|
||||
SourceTextKind::Notebook { notebook } => notebook.source_code(),
|
||||
SourceTextKind::Notebook(notebook) => notebook.source_code(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying notebook if this is a notebook file.
|
||||
pub fn as_notebook(&self) -> Option<&Notebook> {
|
||||
match &self.inner.kind {
|
||||
SourceTextKind::Notebook { notebook } => Some(notebook),
|
||||
SourceTextKind::Notebook(notebook) => Some(notebook),
|
||||
SourceTextKind::Text(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a notebook source file.
|
||||
pub fn is_notebook(&self) -> bool {
|
||||
matches!(&self.inner.kind, SourceTextKind::Notebook { .. })
|
||||
matches!(&self.inner.kind, SourceTextKind::Notebook(_))
|
||||
}
|
||||
|
||||
/// Returns `true` if there was an error when reading the content of the file.
|
||||
@@ -108,7 +108,7 @@ impl std::fmt::Debug for SourceText {
|
||||
SourceTextKind::Text(text) => {
|
||||
dbg.field(text);
|
||||
}
|
||||
SourceTextKind::Notebook { notebook } => {
|
||||
SourceTextKind::Notebook(notebook) => {
|
||||
dbg.field(notebook);
|
||||
}
|
||||
}
|
||||
@@ -123,15 +123,23 @@ struct SourceTextInner {
|
||||
read_error: Option<SourceTextError>,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, get_size2::GetSize)]
|
||||
#[derive(Eq, PartialEq)]
|
||||
enum SourceTextKind {
|
||||
Text(String),
|
||||
Notebook {
|
||||
// Jupyter notebooks are not very relevant for memory profiling, and contain
|
||||
// arbitrary JSON values that do not implement the `GetSize` trait.
|
||||
#[get_size(ignore)]
|
||||
notebook: Box<Notebook>,
|
||||
},
|
||||
Notebook(Box<Notebook>),
|
||||
}
|
||||
|
||||
impl get_size2::GetSize for SourceTextKind {
|
||||
fn get_heap_size(&self) -> usize {
|
||||
match self {
|
||||
SourceTextKind::Text(text) => text.get_heap_size(),
|
||||
// TODO: The `get-size` derive does not support ignoring enum variants.
|
||||
//
|
||||
// Jupyter notebooks are not very relevant for memory profiling, and contain
|
||||
// arbitrary JSON values that do not implement the `GetSize` trait.
|
||||
SourceTextKind::Notebook(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SourceTextKind {
|
||||
@@ -142,9 +150,7 @@ impl From<String> for SourceTextKind {
|
||||
|
||||
impl From<Notebook> for SourceTextKind {
|
||||
fn from(notebook: Notebook) -> Self {
|
||||
SourceTextKind::Notebook {
|
||||
notebook: Box::new(notebook),
|
||||
}
|
||||
SourceTextKind::Notebook(Box::new(notebook))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +163,7 @@ pub enum SourceTextError {
|
||||
}
|
||||
|
||||
/// Computes the [`LineIndex`] for `file`.
|
||||
#[salsa::tracked(heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
|
||||
let _span = tracing::trace_span!("line_index", ?file).entered();
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ pub enum IsolationLevel {
|
||||
}
|
||||
|
||||
/// A collection of [`Edit`] elements to be applied to a source file.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, get_size2::GetSize)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Fix {
|
||||
/// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order.
|
||||
|
||||
@@ -42,11 +42,13 @@ impl ModuleImports {
|
||||
// Resolve the imports.
|
||||
let mut resolved_imports = ModuleImports::default();
|
||||
for import in imports {
|
||||
for resolved in Resolver::new(db).resolve(import) {
|
||||
if let Some(path) = resolved.as_system_path() {
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
let Some(resolved) = Resolver::new(db).resolve(import) else {
|
||||
continue;
|
||||
};
|
||||
let Some(path) = resolved.as_system_path() else {
|
||||
continue;
|
||||
};
|
||||
resolved_imports.insert(path.to_path_buf());
|
||||
}
|
||||
|
||||
Ok(resolved_imports)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_db::files::FilePath;
|
||||
use ty_python_semantic::{ModuleName, resolve_module, resolve_real_module};
|
||||
use ty_python_semantic::resolve_module;
|
||||
|
||||
use crate::ModuleDb;
|
||||
use crate::collector::CollectedImport;
|
||||
@@ -16,67 +16,24 @@ impl<'a> Resolver<'a> {
|
||||
}
|
||||
|
||||
/// Resolve the [`CollectedImport`] into a [`FilePath`].
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> impl Iterator<Item = &'a FilePath> {
|
||||
pub(crate) fn resolve(&self, import: CollectedImport) -> Option<&'a FilePath> {
|
||||
match import {
|
||||
CollectedImport::Import(import) => {
|
||||
// Attempt to resolve the module (e.g., given `import foo`, look for `foo`).
|
||||
let file = self.resolve_module(&import);
|
||||
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
let module = resolve_module(self.db, &import)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
CollectedImport::ImportFrom(import) => {
|
||||
// Attempt to resolve the member (e.g., given `from foo import bar`, look for `foo.bar`).
|
||||
if let Some(file) = self.resolve_module(&import) {
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = (file.extension() == Some("pyi"))
|
||||
.then(|| self.resolve_real_module(&import))
|
||||
.flatten();
|
||||
|
||||
return std::iter::once(Some(file))
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten();
|
||||
}
|
||||
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
let parent = import.parent();
|
||||
let file = parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_module(parent));
|
||||
|
||||
// If the file is a stub, look for the corresponding source file.
|
||||
let source_file = file
|
||||
.is_some_and(|file| file.extension() == Some("pyi"))
|
||||
.then(|| {
|
||||
parent
|
||||
.as_ref()
|
||||
.and_then(|parent| self.resolve_real_module(parent))
|
||||
})
|
||||
.flatten();
|
||||
let module = resolve_module(self.db, &import).or_else(|| {
|
||||
// Attempt to resolve the module (e.g., given `from foo import bar`, look for `foo`).
|
||||
|
||||
std::iter::once(file)
|
||||
.chain(std::iter::once(source_file))
|
||||
.flatten()
|
||||
resolve_module(self.db, &parent?)
|
||||
})?;
|
||||
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module.
|
||||
fn resolve_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
|
||||
/// Resolves a module name to a module (stubs not allowed).
|
||||
fn resolve_real_module(&self, module_name: &ModuleName) -> Option<&'a FilePath> {
|
||||
let module = resolve_real_module(self.db, module_name)?;
|
||||
Some(module.file(self.db)?.path(self.db))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -650,17 +650,3 @@ f"""This is a test. {
|
||||
if True else
|
||||
"Don't add a trailing comma here ->"
|
||||
}"""
|
||||
|
||||
type X[
|
||||
T
|
||||
] = T
|
||||
def f[
|
||||
T
|
||||
](): pass
|
||||
class C[
|
||||
T
|
||||
]: pass
|
||||
|
||||
type X[T,] = T
|
||||
def f[T,](): pass
|
||||
class C[T,]: pass
|
||||
@@ -39,11 +39,6 @@ class NonEmptyWithInit:
|
||||
pass
|
||||
|
||||
|
||||
class NonEmptyChildWithInlineComment:
|
||||
value: int
|
||||
... # preserve me
|
||||
|
||||
|
||||
class EmptyClass:
|
||||
...
|
||||
|
||||
|
||||
@@ -38,10 +38,6 @@ class NonEmptyWithInit:
|
||||
def __init__():
|
||||
pass
|
||||
|
||||
class NonEmptyChildWithInlineComment:
|
||||
value: int
|
||||
... # preserve me
|
||||
|
||||
# Not violations
|
||||
|
||||
class EmptyClass: ...
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""Hello, world!"""\
|
||||
|
||||
x = 1; y = 2
|
||||
@@ -289,19 +289,4 @@ def f():
|
||||
i = "xyz"
|
||||
result = []
|
||||
for i in range(3):
|
||||
result.append((x for x in [i]))
|
||||
|
||||
G_INDEX = None
|
||||
def f():
|
||||
global G_INDEX
|
||||
result = []
|
||||
for G_INDEX in range(3):
|
||||
result.append(G_INDEX)
|
||||
|
||||
def f():
|
||||
NL_INDEX = None
|
||||
def x():
|
||||
nonlocal NL_INDEX
|
||||
result = []
|
||||
for NL_INDEX in range(3):
|
||||
result.append(NL_INDEX)
|
||||
result.append((x for x in [i]))
|
||||
@@ -59,7 +59,3 @@ kwargs = {x: x for x in range(10)}
|
||||
"{1}_{0}".format(1, 2, *args)
|
||||
|
||||
"{1}_{0}".format(1, 2)
|
||||
|
||||
r"\d{{1,2}} {0}".format(42)
|
||||
|
||||
"{{{0}}}".format(123)
|
||||
|
||||
@@ -55,12 +55,3 @@ _ = Decimal(0.1)
|
||||
_ = Decimal(-0.5)
|
||||
_ = Decimal(5.0)
|
||||
_ = decimal.Decimal(4.2)
|
||||
|
||||
# Cases with int and bool - should produce safe fixes
|
||||
_ = Decimal.from_float(1)
|
||||
_ = Decimal.from_float(True)
|
||||
|
||||
# Cases with non-finite floats - should produce safe fixes
|
||||
_ = Decimal.from_float(float("-nan"))
|
||||
_ = Decimal.from_float(float("\x2dnan"))
|
||||
_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
@@ -52,7 +52,3 @@ f"{repr(lambda: 1)}"
|
||||
f"{repr(x := 2)}"
|
||||
|
||||
f"{str(object=3)}"
|
||||
|
||||
f"{str(x for x in [])}"
|
||||
|
||||
f"{str((x for x in []))}"
|
||||
|
||||
@@ -590,16 +590,6 @@ impl<'a> Checker<'a> {
|
||||
member,
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the [`LintContext`] for the current analysis.
|
||||
///
|
||||
/// Note that you should always prefer calling methods like `settings`, `report_diagnostic`, or
|
||||
/// `is_rule_enabled` directly on [`Checker`] when possible. This method exists only for the
|
||||
/// rare cases where rules or helper functions need to be accessed by both a `Checker` and a
|
||||
/// `LintContext` in different analysis phases.
|
||||
pub(crate) const fn context(&self) -> &'a LintContext<'a> {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TypingImporter<'a, 'b> {
|
||||
@@ -3226,11 +3216,6 @@ impl<'a> LintContext<'a> {
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &Diagnostic> {
|
||||
self.diagnostics.get_mut().iter()
|
||||
}
|
||||
|
||||
/// The [`LinterSettings`] for the current analysis, including the enabled rules.
|
||||
pub(crate) const fn settings(&self) -> &LinterSettings {
|
||||
self.settings
|
||||
}
|
||||
}
|
||||
|
||||
/// An abstraction for mutating a diagnostic.
|
||||
|
||||
@@ -16,6 +16,7 @@ use crate::rules::{
|
||||
eradicate, flake8_commas, flake8_executable, flake8_fixme, flake8_implicit_str_concat,
|
||||
flake8_pyi, flake8_todos, pycodestyle, pygrep_hooks, pylint, pyupgrade, ruff,
|
||||
};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
use super::ast::LintContext;
|
||||
|
||||
@@ -26,6 +27,7 @@ pub(crate) fn check_tokens(
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
stylist: &Stylist,
|
||||
settings: &LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
context: &mut LintContext,
|
||||
@@ -40,8 +42,15 @@ pub(crate) fn check_tokens(
|
||||
Rule::BlankLinesAfterFunctionOrClass,
|
||||
Rule::BlankLinesBeforeNestedDefinition,
|
||||
]) {
|
||||
BlankLinesChecker::new(locator, stylist, source_type, cell_offsets, context)
|
||||
.check_lines(tokens);
|
||||
BlankLinesChecker::new(
|
||||
locator,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
)
|
||||
.check_lines(tokens);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::BlanketTypeIgnore) {
|
||||
@@ -54,12 +63,12 @@ pub(crate) fn check_tokens(
|
||||
|
||||
if context.is_rule_enabled(Rule::AmbiguousUnicodeCharacterComment) {
|
||||
for range in comment_ranges {
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range);
|
||||
ruff::rules::ambiguous_unicode_character_comment(context, locator, range, settings);
|
||||
}
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::CommentedOutCode) {
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges);
|
||||
eradicate::rules::commented_out_code(context, locator, comment_ranges, settings);
|
||||
}
|
||||
|
||||
if context.is_rule_enabled(Rule::UTF8EncodingDeclaration) {
|
||||
@@ -101,7 +110,7 @@ pub(crate) fn check_tokens(
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
Rule::MultiLineImplicitStringConcatenation,
|
||||
]) {
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer);
|
||||
flake8_implicit_str_concat::rules::implicit(context, tokens, locator, indexer, settings);
|
||||
}
|
||||
|
||||
if context.any_rule_enabled(&[
|
||||
|
||||
@@ -56,19 +56,13 @@ impl<'a> Insertion<'a> {
|
||||
stylist: &Stylist,
|
||||
) -> Insertion<'static> {
|
||||
// Skip over any docstrings.
|
||||
let mut location = if let Some(mut location) = match_docstring_end(body) {
|
||||
let mut location = if let Some(location) = match_docstring_end(body) {
|
||||
// If the first token after the docstring is a semicolon, insert after the semicolon as
|
||||
// an inline statement.
|
||||
if let Some(offset) = match_semicolon(locator.after(location)) {
|
||||
return Insertion::inline(" ", location.add(offset).add(TextSize::of(';')), ";");
|
||||
}
|
||||
|
||||
// If the first token after the docstring is a continuation character (i.e. "\"), advance
|
||||
// an additional row to prevent inserting in the same logical line.
|
||||
if match_continuation(locator.after(location)).is_some() {
|
||||
location = locator.full_line_end(location);
|
||||
}
|
||||
|
||||
// Otherwise, advance to the next row.
|
||||
locator.full_line_end(location)
|
||||
} else {
|
||||
@@ -369,16 +363,6 @@ mod tests {
|
||||
Insertion::own_line("", TextSize::from(20), "\n")
|
||||
);
|
||||
|
||||
let contents = r#"
|
||||
"""Hello, world!"""\
|
||||
|
||||
"#
|
||||
.trim_start();
|
||||
assert_eq!(
|
||||
insert(contents)?,
|
||||
Insertion::own_line("", TextSize::from(22), "\n")
|
||||
);
|
||||
|
||||
let contents = r"
|
||||
x = 1
|
||||
"
|
||||
|
||||
@@ -188,6 +188,7 @@ pub fn check_path(
|
||||
locator,
|
||||
indexer,
|
||||
stylist,
|
||||
settings,
|
||||
source_type,
|
||||
source_kind.as_ipy_notebook().map(Notebook::cell_offsets),
|
||||
&mut context,
|
||||
|
||||
@@ -118,6 +118,86 @@ impl<'a> Locator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the closest [`TextSize`] not less than the offset given for which
|
||||
/// `is_char_boundary` is `true`. Unless the offset given is greater than
|
||||
/// the length of the underlying contents, in which case, the length of the
|
||||
/// contents is returned.
|
||||
///
|
||||
/// Can be replaced with `str::ceil_char_boundary` once it's stable.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// From `std`:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("❤️🧡💛💚💙💜");
|
||||
/// assert_eq!(locator.text_len(), TextSize::from(26));
|
||||
/// assert!(!locator.contents().is_char_boundary(13));
|
||||
///
|
||||
/// let closest = locator.ceil_char_boundary(TextSize::from(13));
|
||||
/// assert_eq!(closest, TextSize::from(14));
|
||||
/// assert_eq!(&locator.contents()[..closest.to_usize()], "❤️🧡💛");
|
||||
/// ```
|
||||
///
|
||||
/// Additional examples:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
/// use ruff_linter::Locator;
|
||||
///
|
||||
/// let locator = Locator::new("Hello");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(5)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(6)),
|
||||
/// TextSize::from(5)
|
||||
/// );
|
||||
///
|
||||
/// let locator = Locator::new("α");
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(0)),
|
||||
/// TextSize::from(0)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(1)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(2)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// locator.ceil_char_boundary(TextSize::from(3)),
|
||||
/// TextSize::from(2)
|
||||
/// );
|
||||
/// ```
|
||||
pub fn ceil_char_boundary(&self, offset: TextSize) -> TextSize {
|
||||
let upper_bound = offset
|
||||
.to_u32()
|
||||
.saturating_add(4)
|
||||
.min(self.text_len().to_u32());
|
||||
(offset.to_u32()..upper_bound)
|
||||
.map(TextSize::from)
|
||||
.find(|offset| self.contents.is_char_boundary(offset.to_usize()))
|
||||
.unwrap_or_else(|| TextSize::from(upper_bound))
|
||||
}
|
||||
|
||||
/// Take the source code between the given [`TextRange`].
|
||||
#[inline]
|
||||
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {
|
||||
|
||||
@@ -6,13 +6,13 @@ use bitflags::bitflags;
|
||||
use colored::Colorize;
|
||||
use ruff_annotate_snippets::{Level, Renderer, Snippet};
|
||||
|
||||
use ruff_db::diagnostic::{
|
||||
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode, ceil_char_boundary,
|
||||
};
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, SecondaryCode};
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::line_width::{IndentWidth, LineWidthBuilder};
|
||||
use crate::message::diff::Diff;
|
||||
use crate::message::{Emitter, EmitterContext};
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
@@ -228,7 +228,7 @@ impl Display for MessageCodeFrame<'_> {
|
||||
let start_offset = source_code.line_start(start_index);
|
||||
let end_offset = source_code.line_end(end_index);
|
||||
|
||||
let source = replace_unprintable(
|
||||
let source = replace_whitespace_and_unprintable(
|
||||
source_code.slice(TextRange::new(start_offset, end_offset)),
|
||||
self.message.expect_range() - start_offset,
|
||||
)
|
||||
@@ -271,20 +271,16 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}
|
||||
|
||||
/// Given some source code and an annotation range, this routine replaces
|
||||
/// unprintable characters with printable representations of them.
|
||||
/// tabs with ASCII whitespace, and unprintable characters with printable
|
||||
/// representations of them.
|
||||
///
|
||||
/// The source code returned has an annotation that is updated to reflect
|
||||
/// changes made to the source code (if any).
|
||||
///
|
||||
/// We don't need to normalize whitespace, such as converting tabs to spaces,
|
||||
/// because `annotate-snippets` handles that internally. Similarly, it's safe to
|
||||
/// modify the annotation ranges by inserting 3-byte Unicode replacements
|
||||
/// because `annotate-snippets` will account for their actual width when
|
||||
/// rendering and displaying the column to the user.
|
||||
fn replace_unprintable(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
fn replace_whitespace_and_unprintable(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
let mut line_width = LineWidthBuilder::new(IndentWidth::default());
|
||||
|
||||
// Updates the range given by the caller whenever a single byte (at
|
||||
// `index` in `source`) is replaced with `len` bytes.
|
||||
@@ -313,7 +309,19 @@ fn replace_unprintable(source: &str, annotation_range: TextRange) -> SourceCode
|
||||
};
|
||||
|
||||
for (index, c) in source.char_indices() {
|
||||
if let Some(printable) = unprintable_replacement(c) {
|
||||
let old_width = line_width.get();
|
||||
line_width = line_width.add_char(c);
|
||||
|
||||
if matches!(c, '\t') {
|
||||
let tab_width = u32::try_from(line_width.get() - old_width)
|
||||
.expect("small width because of tab size");
|
||||
result.push_str(&source[last_end..index]);
|
||||
for _ in 0..tab_width {
|
||||
result.push(' ');
|
||||
}
|
||||
last_end = index + 1;
|
||||
update_range(index, tab_width);
|
||||
} else if let Some(printable) = unprintable_replacement(c) {
|
||||
result.push_str(&source[last_end..index]);
|
||||
result.push(printable);
|
||||
last_end = index + 1;
|
||||
@@ -368,8 +376,9 @@ impl<'a> SourceCode<'a> {
|
||||
if self.text.as_bytes()[self.annotation_range.start().to_usize() - 1] != b'\n' {
|
||||
return self;
|
||||
}
|
||||
let locator = Locator::new(&self.text);
|
||||
let start = self.annotation_range.start();
|
||||
let end = ceil_char_boundary(&self.text, start + TextSize::from(1));
|
||||
let end = locator.ceil_char_boundary(start + TextSize::from(1));
|
||||
SourceCode {
|
||||
annotation_range: TextRange::new(start, end),
|
||||
..self
|
||||
|
||||
@@ -225,8 +225,3 @@ pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSet
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19390
|
||||
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use ruff_text_size::TextRange;
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
use crate::rules::eradicate::detection::comment_contains_code;
|
||||
@@ -50,6 +51,7 @@ pub(crate) fn commented_out_code(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let mut comments = comment_ranges.into_iter().peekable();
|
||||
// Iterate over all comments in the document.
|
||||
@@ -63,9 +65,7 @@ pub(crate) fn commented_out_code(
|
||||
}
|
||||
|
||||
// Verify that the comment is on its own line, and that it contains code.
|
||||
if is_own_line_comment(line)
|
||||
&& comment_contains_code(line, &context.settings().task_tags[..])
|
||||
{
|
||||
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||
if let Some(mut diagnostic) =
|
||||
context.report_diagnostic_if_enabled(CommentedOutCode, range)
|
||||
{
|
||||
|
||||
@@ -27,23 +27,4 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview__{}", path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_commas").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::preview::is_trailing_comma_type_params_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Violation};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
@@ -26,8 +24,6 @@ enum TokenType {
|
||||
Def,
|
||||
For,
|
||||
Lambda,
|
||||
Class,
|
||||
Type,
|
||||
Irrelevant,
|
||||
}
|
||||
|
||||
@@ -73,8 +69,6 @@ impl From<(TokenKind, TextRange)> for SimpleToken {
|
||||
TokenKind::Lbrace => TokenType::OpeningCurlyBracket,
|
||||
TokenKind::Rbrace => TokenType::ClosingBracket,
|
||||
TokenKind::Def => TokenType::Def,
|
||||
TokenKind::Class => TokenType::Class,
|
||||
TokenKind::Type => TokenType::Type,
|
||||
TokenKind::For => TokenType::For,
|
||||
TokenKind::Lambda => TokenType::Lambda,
|
||||
// Import treated like a function.
|
||||
@@ -104,8 +98,6 @@ enum ContextType {
|
||||
Dict,
|
||||
/// Lambda parameter list, e.g. `lambda a, b`.
|
||||
LambdaParameters,
|
||||
/// Type parameter list, e.g. `def foo[T, U](): ...`
|
||||
TypeParameters,
|
||||
}
|
||||
|
||||
/// Comma context - described a comma-delimited "situation".
|
||||
@@ -298,7 +290,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
|
||||
check_token(token, prev, prev_prev, context, locator, lint_context);
|
||||
|
||||
@@ -334,7 +326,6 @@ fn check_token(
|
||||
ContextType::No => false,
|
||||
ContextType::FunctionParameters => true,
|
||||
ContextType::CallArguments => true,
|
||||
ContextType::TypeParameters => true,
|
||||
// `(1)` is not equivalent to `(1,)`.
|
||||
ContextType::Tuple => context.num_commas != 0,
|
||||
// `x[1]` is not equivalent to `x[1,]`.
|
||||
@@ -417,7 +408,6 @@ fn update_context(
|
||||
prev: SimpleToken,
|
||||
prev_prev: SimpleToken,
|
||||
stack: &mut Vec<Context>,
|
||||
settings: &LinterSettings,
|
||||
) -> Context {
|
||||
let new_context = match token.ty {
|
||||
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
|
||||
@@ -427,17 +417,6 @@ fn update_context(
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
|
||||
match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
|
||||
Context::new(ContextType::TypeParameters)
|
||||
}
|
||||
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
}
|
||||
}
|
||||
TokenType::OpeningSquareBracket => match prev.ty {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
Context::new(ContextType::Subscript)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here
|
||||
|
|
||||
1 | # Check for `flake8-commas` violation for a file containing syntax errors.
|
||||
2 | (
|
||||
3 | *args
|
||||
| ^^^^^
|
||||
4 | )
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:9: SyntaxError: Type parameter list cannot be empty
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^
|
||||
7 | pass
|
||||
|
|
||||
|
||||
COM81_syntax_error.py:6:38: COM819 Trailing comma prohibited
|
||||
|
|
||||
4 | )
|
||||
5 |
|
||||
6 | def foo[(param1='test', param2='test',):
|
||||
| ^ COM819
|
||||
7 | pass
|
||||
|
|
||||
= help: Remove trailing comma
|
||||
@@ -11,6 +11,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
@@ -107,15 +108,13 @@ pub(crate) fn implicit(
|
||||
tokens: &Tokens,
|
||||
locator: &Locator,
|
||||
indexer: &Indexer,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
for (a_token, b_token) in tokens
|
||||
.iter()
|
||||
.filter(|token| {
|
||||
token.kind() != TokenKind::Comment
|
||||
&& (context
|
||||
.settings()
|
||||
.flake8_implicit_str_concat
|
||||
.allow_multiline
|
||||
&& (settings.flake8_implicit_str_concat.allow_multiline
|
||||
|| token.kind() != TokenKind::NonLogicalNewline)
|
||||
})
|
||||
.tuple_windows()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::whitespace::trailing_comment_start_offset;
|
||||
use ruff_python_ast::{Stmt, StmtExpr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
use crate::{Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Removes ellipses (`...`) in otherwise non-empty class bodies.
|
||||
@@ -51,21 +50,15 @@ pub(crate) fn ellipsis_in_non_empty_class_body(checker: &Checker, body: &[Stmt])
|
||||
}
|
||||
|
||||
for stmt in body {
|
||||
let Stmt::Expr(StmtExpr { value, .. }) = stmt else {
|
||||
let Stmt::Expr(StmtExpr { value, .. }) = &stmt else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if value.is_ellipsis_literal_expr() {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(EllipsisInNonEmptyClassBody, stmt.range());
|
||||
|
||||
// Try to preserve trailing comment if it exists
|
||||
let edit = if let Some(index) = trailing_comment_start_offset(stmt, checker.source()) {
|
||||
Edit::range_deletion(stmt.range().add_end(index))
|
||||
} else {
|
||||
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer())
|
||||
};
|
||||
|
||||
let edit =
|
||||
fix::edits::delete_stmt(stmt, Some(stmt), checker.locator(), checker.indexer());
|
||||
diagnostic.set_fix(Fix::safe_edit(edit).isolate(Checker::isolation(
|
||||
checker.semantic().current_statement_id(),
|
||||
)));
|
||||
|
||||
@@ -145,22 +145,3 @@ PYI013.py:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
37 36 |
|
||||
38 37 | def __init__():
|
||||
39 38 | pass
|
||||
|
||||
PYI013.py:44:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
42 | class NonEmptyChildWithInlineComment:
|
||||
43 | value: int
|
||||
44 | ... # preserve me
|
||||
| ^^^ PYI013
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 |
|
||||
42 42 | class NonEmptyChildWithInlineComment:
|
||||
43 43 | value: int
|
||||
44 |- ... # preserve me
|
||||
44 |+ # preserve me
|
||||
45 45 |
|
||||
46 46 |
|
||||
47 47 | class EmptyClass:
|
||||
|
||||
@@ -17,10 +17,9 @@ PYI013.pyi:5:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
3 3 | class OneAttributeClass:
|
||||
4 4 | value: int
|
||||
5 |- ... # Error
|
||||
5 |+ # Error
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClass2:
|
||||
8 8 | ... # Error
|
||||
6 5 |
|
||||
7 6 | class OneAttributeClass2:
|
||||
8 7 | ... # Error
|
||||
|
||||
PYI013.pyi:8:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -36,10 +35,9 @@ PYI013.pyi:8:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
6 6 |
|
||||
7 7 | class OneAttributeClass2:
|
||||
8 |- ... # Error
|
||||
8 |+ # Error
|
||||
9 9 | value: int
|
||||
10 10 |
|
||||
11 11 | class MyClass:
|
||||
9 8 | value: int
|
||||
10 9 |
|
||||
11 10 | class MyClass:
|
||||
|
||||
PYI013.pyi:12:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -93,10 +91,9 @@ PYI013.pyi:17:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
15 15 | class TwoEllipsesClass:
|
||||
16 16 | ...
|
||||
17 |- ... # Error
|
||||
17 |+ # Error
|
||||
18 18 |
|
||||
19 19 | class DocstringClass:
|
||||
20 20 | """
|
||||
18 17 |
|
||||
19 18 | class DocstringClass:
|
||||
20 19 | """
|
||||
|
||||
PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -114,10 +111,9 @@ PYI013.pyi:24:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
22 22 | """
|
||||
23 23 |
|
||||
24 |- ... # Error
|
||||
24 |+ # Error
|
||||
25 25 |
|
||||
26 26 | class NonEmptyChild(Exception):
|
||||
27 27 | value: int
|
||||
25 24 |
|
||||
26 25 | class NonEmptyChild(Exception):
|
||||
27 26 | value: int
|
||||
|
||||
PYI013.pyi:28:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -135,10 +131,9 @@ PYI013.pyi:28:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
26 26 | class NonEmptyChild(Exception):
|
||||
27 27 | value: int
|
||||
28 |- ... # Error
|
||||
28 |+ # Error
|
||||
29 29 |
|
||||
30 30 | class NonEmptyChild2(Exception):
|
||||
31 31 | ... # Error
|
||||
29 28 |
|
||||
30 29 | class NonEmptyChild2(Exception):
|
||||
31 30 | ... # Error
|
||||
|
||||
PYI013.pyi:31:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -154,10 +149,9 @@ PYI013.pyi:31:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
29 29 |
|
||||
30 30 | class NonEmptyChild2(Exception):
|
||||
31 |- ... # Error
|
||||
31 |+ # Error
|
||||
32 32 | value: int
|
||||
33 33 |
|
||||
34 34 | class NonEmptyWithInit:
|
||||
32 31 | value: int
|
||||
33 32 |
|
||||
34 33 | class NonEmptyWithInit:
|
||||
|
||||
PYI013.pyi:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
@@ -175,28 +169,6 @@ PYI013.pyi:36:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
34 34 | class NonEmptyWithInit:
|
||||
35 35 | value: int
|
||||
36 |- ... # Error
|
||||
36 |+ # Error
|
||||
37 37 |
|
||||
38 38 | def __init__():
|
||||
39 39 | pass
|
||||
|
||||
PYI013.pyi:43:5: PYI013 [*] Non-empty class body must not contain `...`
|
||||
|
|
||||
41 | class NonEmptyChildWithInlineComment:
|
||||
42 | value: int
|
||||
43 | ... # preserve me
|
||||
| ^^^ PYI013
|
||||
44 |
|
||||
45 | # Not violations
|
||||
|
|
||||
= help: Remove unnecessary `...`
|
||||
|
||||
ℹ Safe fix
|
||||
40 40 |
|
||||
41 41 | class NonEmptyChildWithInlineComment:
|
||||
42 42 | value: int
|
||||
43 |- ... # preserve me
|
||||
43 |+ # preserve me
|
||||
44 44 |
|
||||
45 45 | # Not violations
|
||||
46 46 |
|
||||
37 36 |
|
||||
38 37 | def __init__():
|
||||
39 38 | pass
|
||||
|
||||
@@ -794,7 +794,6 @@ mod tests {
|
||||
#[test_case(Path::new("comments_and_newlines.py"))]
|
||||
#[test_case(Path::new("docstring.py"))]
|
||||
#[test_case(Path::new("docstring.pyi"))]
|
||||
#[test_case(Path::new("docstring_followed_by_continuation.py"))]
|
||||
#[test_case(Path::new("docstring_only.py"))]
|
||||
#[test_case(Path::new("docstring_with_continuation.py"))]
|
||||
#[test_case(Path::new("docstring_with_semicolon.py"))]
|
||||
@@ -829,7 +828,6 @@ mod tests {
|
||||
#[test_case(Path::new("comments_and_newlines.py"))]
|
||||
#[test_case(Path::new("docstring.py"))]
|
||||
#[test_case(Path::new("docstring.pyi"))]
|
||||
#[test_case(Path::new("docstring_followed_by_continuation.py"))]
|
||||
#[test_case(Path::new("docstring_only.py"))]
|
||||
#[test_case(Path::new("docstring_with_continuation.py"))]
|
||||
#[test_case(Path::new("docstring_with_semicolon.py"))]
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
docstring_followed_by_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations`
|
||||
ℹ Safe fix
|
||||
1 1 | """Hello, world!"""\
|
||||
2 2 |
|
||||
3 |+from __future__ import annotations
|
||||
3 4 | x = 1; y = 2
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
||||
---
|
||||
docstring_followed_by_continuation.py:1:1: I002 [*] Missing required import: `from __future__ import annotations as _annotations`
|
||||
ℹ Safe fix
|
||||
1 1 | """Hello, world!"""\
|
||||
2 2 |
|
||||
3 |+from __future__ import annotations as _annotations
|
||||
3 4 | x = 1; y = 2
|
||||
@@ -249,11 +249,6 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
|
||||
.iter()
|
||||
.find(|binding| for_stmt.target.range() == binding.range)
|
||||
.unwrap();
|
||||
// If the target variable is global (e.g., `global INDEX`) or nonlocal (e.g., `nonlocal INDEX`),
|
||||
// then it is intended to be used elsewhere outside the for loop.
|
||||
if target_binding.is_global() || target_binding.is_nonlocal() {
|
||||
return;
|
||||
}
|
||||
// If any references to the loop target variable are after the loop,
|
||||
// then converting it into a comprehension would cause a NameError
|
||||
if target_binding
|
||||
|
||||
@@ -263,7 +263,5 @@ PERF401.py:292:9: PERF401 Use a list comprehension to create a transformed list
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -612,8 +612,6 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 | for i in range(3):
|
||||
292 | result.append((x for x in [i]))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
293 |
|
||||
294 | G_INDEX = None
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -625,6 +623,3 @@ PERF401.py:292:9: PERF401 [*] Use a list comprehension to create a transformed l
|
||||
291 |- for i in range(3):
|
||||
292 |- result.append((x for x in [i]))
|
||||
290 |+ result = [(x for x in [i]) for i in range(3)]
|
||||
293 291 |
|
||||
294 292 | G_INDEX = None
|
||||
295 293 | def f():
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::checkers::ast::{DiagnosticGuard, LintContext};
|
||||
use crate::checkers::logical_lines::expand_indent;
|
||||
use crate::line_width::IndentWidth;
|
||||
use crate::rules::pycodestyle::helpers::is_non_logical_token;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, Locator, Violation};
|
||||
|
||||
/// Number of blank lines around top level classes and functions.
|
||||
@@ -693,12 +694,14 @@ pub(crate) struct BlankLinesChecker<'a, 'b> {
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
settings: &'a LinterSettings,
|
||||
}
|
||||
|
||||
impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
pub(crate) fn new(
|
||||
locator: &'a Locator<'a>,
|
||||
stylist: &'a Stylist<'a>,
|
||||
settings: &'a LinterSettings,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&'a CellOffsets>,
|
||||
context: &'a LintContext<'b>,
|
||||
@@ -709,6 +712,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
source_type,
|
||||
cell_offsets,
|
||||
context,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,7 +733,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
let line_preprocessor = LinePreprocessor::new(
|
||||
tokens,
|
||||
self.locator,
|
||||
self.context.settings().tab_size,
|
||||
self.settings.tab_size,
|
||||
self.cell_offsets,
|
||||
);
|
||||
|
||||
@@ -875,8 +879,7 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
// `isort` defaults to 2 if before a class or function definition (except in stubs where it is one) and 1 otherwise.
|
||||
// Defaulting to 2 (or 1 in stubs) here is correct because the variable is only used when testing the
|
||||
// blank lines before a class or function definition.
|
||||
u32::try_from(self.context.settings().isort.lines_after_imports)
|
||||
.unwrap_or(max_lines_level)
|
||||
u32::try_from(self.settings.isort.lines_after_imports).unwrap_or(max_lines_level)
|
||||
} else {
|
||||
max_lines_level
|
||||
}
|
||||
@@ -938,10 +941,8 @@ impl<'a, 'b> BlankLinesChecker<'a, 'b> {
|
||||
(LogicalLineKind::Import, Follows::FromImport)
|
||||
| (LogicalLineKind::FromImport, Follows::Import)
|
||||
) {
|
||||
max_lines_level.max(
|
||||
u32::try_from(self.context.settings().isort.lines_between_types)
|
||||
.unwrap_or(u32::MAX),
|
||||
)
|
||||
max_lines_level
|
||||
.max(u32::try_from(self.settings.isort.lines_between_types).unwrap_or(u32::MAX))
|
||||
} else {
|
||||
expected_blank_lines_before_definition
|
||||
};
|
||||
|
||||
@@ -15,8 +15,8 @@ E101.py:15:1: E101 Indentation contains mixed spaces and tabs
|
||||
|
|
||||
13 | def func_mixed_start_with_space():
|
||||
14 | # E101
|
||||
15 | print("mixed starts with space")
|
||||
| ^^^^^^^^^^^^^^^^^^^^ E101
|
||||
15 | print("mixed starts with space")
|
||||
| ^^^^^^^^^^^^^^^ E101
|
||||
16 |
|
||||
17 | def xyz():
|
||||
|
|
||||
@@ -25,6 +25,6 @@ E101.py:19:1: E101 Indentation contains mixed spaces and tabs
|
||||
|
|
||||
17 | def xyz():
|
||||
18 | # E101
|
||||
19 | print("xyz");
|
||||
| ^^^^^^^ E101
|
||||
19 | print("xyz");
|
||||
| ^^^^ E101
|
||||
|
|
||||
|
||||
@@ -47,7 +47,7 @@ E20.py:6:15: E201 [*] Whitespace after '{'
|
||||
6 | spam(ham[1], { eggs: 2})
|
||||
| ^ E201
|
||||
7 | #: E201:1:6
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
|
|
||||
= help: Remove whitespace before '{'
|
||||
|
||||
@@ -65,10 +65,10 @@ E20.py:8:6: E201 [*] Whitespace after '('
|
||||
|
|
||||
6 | spam(ham[1], { eggs: 2})
|
||||
7 | #: E201:1:6
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
| ^^^^ E201
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
| ^^^ E201
|
||||
9 | #: E201:1:10
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
|
|
||||
= help: Remove whitespace before '('
|
||||
|
||||
@@ -84,12 +84,12 @@ E20.py:8:6: E201 [*] Whitespace after '('
|
||||
|
||||
E20.py:10:10: E201 [*] Whitespace after '['
|
||||
|
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
8 | spam( ham[1], {eggs: 2})
|
||||
9 | #: E201:1:10
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
| ^^^^ E201
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
| ^^^ E201
|
||||
11 | #: E201:1:15
|
||||
12 | spam(ham[1], { eggs: 2})
|
||||
12 | spam(ham[1], { eggs: 2})
|
||||
|
|
||||
= help: Remove whitespace before '['
|
||||
|
||||
@@ -105,10 +105,10 @@ E20.py:10:10: E201 [*] Whitespace after '['
|
||||
|
||||
E20.py:12:15: E201 [*] Whitespace after '{'
|
||||
|
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
10 | spam(ham[ 1], {eggs: 2})
|
||||
11 | #: E201:1:15
|
||||
12 | spam(ham[1], { eggs: 2})
|
||||
| ^^^^ E201
|
||||
12 | spam(ham[1], { eggs: 2})
|
||||
| ^^ E201
|
||||
13 | #: Okay
|
||||
14 | spam(ham[1], {eggs: 2})
|
||||
|
|
||||
|
||||
@@ -49,7 +49,7 @@ E20.py:23:11: E202 [*] Whitespace before ']'
|
||||
23 | spam(ham[1 ], {eggs: 2})
|
||||
| ^ E202
|
||||
24 | #: E202:1:23
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
|
|
||||
= help: Remove whitespace before ']'
|
||||
|
||||
@@ -67,10 +67,10 @@ E20.py:25:23: E202 [*] Whitespace before ')'
|
||||
|
|
||||
23 | spam(ham[1 ], {eggs: 2})
|
||||
24 | #: E202:1:23
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
| ^^^^ E202
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
| ^^ E202
|
||||
26 | #: E202:1:22
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
|
|
||||
= help: Remove whitespace before ')'
|
||||
|
||||
@@ -86,12 +86,12 @@ E20.py:25:23: E202 [*] Whitespace before ')'
|
||||
|
||||
E20.py:27:22: E202 [*] Whitespace before '}'
|
||||
|
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
25 | spam(ham[1], {eggs: 2} )
|
||||
26 | #: E202:1:22
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
| ^^^^ E202
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
| ^^^ E202
|
||||
28 | #: E202:1:11
|
||||
29 | spam(ham[1 ], {eggs: 2})
|
||||
29 | spam(ham[1 ], {eggs: 2})
|
||||
|
|
||||
= help: Remove whitespace before '}'
|
||||
|
||||
@@ -107,10 +107,10 @@ E20.py:27:22: E202 [*] Whitespace before '}'
|
||||
|
||||
E20.py:29:11: E202 [*] Whitespace before ']'
|
||||
|
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
27 | spam(ham[1], {eggs: 2 })
|
||||
28 | #: E202:1:11
|
||||
29 | spam(ham[1 ], {eggs: 2})
|
||||
| ^^^^ E202
|
||||
29 | spam(ham[1 ], {eggs: 2})
|
||||
| ^^ E202
|
||||
30 | #: Okay
|
||||
31 | spam(ham[1], {eggs: 2})
|
||||
|
|
||||
|
||||
@@ -25,8 +25,8 @@ E20.py:55:10: E203 [*] Whitespace before ':'
|
||||
|
|
||||
53 | x, y = y, x
|
||||
54 | #: E203:1:10
|
||||
55 | if x == 4 :
|
||||
| ^^^^ E203
|
||||
55 | if x == 4 :
|
||||
| ^^^ E203
|
||||
56 | print(x, y)
|
||||
57 | x, y = y, x
|
||||
|
|
||||
@@ -67,8 +67,8 @@ E20.py:63:16: E203 [*] Whitespace before ';'
|
||||
|
|
||||
61 | #: E203:2:15 E702:2:16
|
||||
62 | if x == 4:
|
||||
63 | print(x, y) ; x, y = y, x
|
||||
| ^^^^ E203
|
||||
63 | print(x, y) ; x, y = y, x
|
||||
| ^ E203
|
||||
64 | #: E203:3:13
|
||||
65 | if x == 4:
|
||||
|
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
E22.py:43:2: E223 [*] Tab before operator
|
||||
|
|
||||
41 | #: E223
|
||||
42 | foobart = 4
|
||||
43 | a = 3 # aligned with tab
|
||||
| ^^^^ E223
|
||||
43 | a = 3 # aligned with tab
|
||||
| ^^^ E223
|
||||
44 | #:
|
||||
|
|
||||
= help: Replace with single space
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
E24.py:6:8: E242 [*] Tab after comma
|
||||
|
|
||||
4 | b = (1, 20)
|
||||
5 | #: E242
|
||||
6 | a = (1, 2) # tab before 2
|
||||
| ^^^^ E242
|
||||
6 | a = (1, 2) # tab before 2
|
||||
| ^ E242
|
||||
7 | #: Okay
|
||||
8 | b = (1, 20) # space before 20
|
||||
|
|
||||
|
||||
@@ -66,7 +66,7 @@ E27.py:8:3: E271 [*] Multiple spaces after keyword
|
||||
|
||||
E27.py:15:6: E271 [*] Multiple spaces after keyword
|
||||
|
|
||||
13 | True and False
|
||||
13 | True and False
|
||||
14 | #: E271
|
||||
15 | a and b
|
||||
| ^^ E271
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
E27.py:21:2: E272 [*] Multiple spaces before keyword
|
||||
|
|
||||
@@ -50,7 +51,7 @@ E27.py:25:5: E272 [*] Multiple spaces before keyword
|
||||
25 | this and False
|
||||
| ^^ E272
|
||||
26 | #: E273
|
||||
27 | a and b
|
||||
27 | a and b
|
||||
|
|
||||
= help: Replace with single space
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ E27.py:11:9: E273 [*] Tab after keyword
|
||||
11 | True and False
|
||||
| ^^^^^^^^ E273
|
||||
12 | #: E273 E274
|
||||
13 | True and False
|
||||
13 | True and False
|
||||
|
|
||||
= help: Replace with single space
|
||||
|
||||
@@ -26,7 +26,7 @@ E27.py:13:5: E273 [*] Tab after keyword
|
||||
|
|
||||
11 | True and False
|
||||
12 | #: E273 E274
|
||||
13 | True and False
|
||||
13 | True and False
|
||||
| ^^^^^^^^ E273
|
||||
14 | #: E271
|
||||
15 | a and b
|
||||
@@ -47,8 +47,8 @@ E27.py:13:10: E273 [*] Tab after keyword
|
||||
|
|
||||
11 | True and False
|
||||
12 | #: E273 E274
|
||||
13 | True and False
|
||||
| ^^^^ E273
|
||||
13 | True and False
|
||||
| ^ E273
|
||||
14 | #: E271
|
||||
15 | a and b
|
||||
|
|
||||
@@ -68,10 +68,10 @@ E27.py:27:6: E273 [*] Tab after keyword
|
||||
|
|
||||
25 | this and False
|
||||
26 | #: E273
|
||||
27 | a and b
|
||||
| ^^^^ E273
|
||||
27 | a and b
|
||||
| ^^^ E273
|
||||
28 | #: E274
|
||||
29 | a and b
|
||||
29 | a and b
|
||||
|
|
||||
= help: Replace with single space
|
||||
|
||||
@@ -87,10 +87,10 @@ E27.py:27:6: E273 [*] Tab after keyword
|
||||
|
||||
E27.py:31:10: E273 [*] Tab after keyword
|
||||
|
|
||||
29 | a and b
|
||||
29 | a and b
|
||||
30 | #: E273 E274
|
||||
31 | this and False
|
||||
| ^^^^ E273
|
||||
31 | this and False
|
||||
| ^ E273
|
||||
32 | #: Okay
|
||||
33 | from u import (a, b)
|
||||
|
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
E27.py:29:2: E274 [*] Tab before keyword
|
||||
|
|
||||
27 | a and b
|
||||
27 | a and b
|
||||
28 | #: E274
|
||||
29 | a and b
|
||||
| ^^^^^^^^ E274
|
||||
29 | a and b
|
||||
| ^^^^^^^ E274
|
||||
30 | #: E273 E274
|
||||
31 | this and False
|
||||
31 | this and False
|
||||
|
|
||||
= help: Replace with single space
|
||||
|
||||
@@ -24,9 +25,9 @@ E27.py:29:2: E274 [*] Tab before keyword
|
||||
|
||||
E27.py:31:5: E274 [*] Tab before keyword
|
||||
|
|
||||
29 | a and b
|
||||
29 | a and b
|
||||
30 | #: E273 E274
|
||||
31 | this and False
|
||||
31 | this and False
|
||||
| ^^^^^^^^ E274
|
||||
32 | #: Okay
|
||||
33 | from u import (a, b)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
W19.py:1:1: W191 Indentation contains tabs
|
||||
|
|
||||
@@ -370,7 +371,7 @@ W19.py:157:1: W191 Indentation contains tabs
|
||||
156 | f"test{
|
||||
157 | tab_indented_should_be_flagged
|
||||
| ^^^^ W191
|
||||
158 | } <- this tab is fine"
|
||||
158 | } <- this tab is fine"
|
||||
|
|
||||
|
||||
W19.py:161:1: W191 Indentation contains tabs
|
||||
@@ -378,5 +379,5 @@ W19.py:161:1: W191 Indentation contains tabs
|
||||
160 | f"""test{
|
||||
161 | tab_indented_should_be_flagged
|
||||
| ^^^^ W191
|
||||
162 | } <- this tab is fine"""
|
||||
162 | } <- this tab is fine"""
|
||||
|
|
||||
|
||||
@@ -6,9 +6,8 @@ use ruff_python_ast::{
|
||||
use ruff_python_semantic::{SemanticModel, analyze::typing};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for access to the first or last element of `str.split()` or `str.rsplit()` without
|
||||
@@ -36,14 +35,10 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
/// url = "www.example.com"
|
||||
/// suffix = url.rsplit(".", maxsplit=1)[-1]
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe for `split()`/`rsplit()` calls that contain `**kwargs`, as
|
||||
/// adding a `maxsplit` keyword to such a call may lead to a duplicate keyword argument error.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingMaxsplitArg {
|
||||
index: SliceBoundary,
|
||||
actual_split_type: String,
|
||||
suggested_split_type: String,
|
||||
}
|
||||
|
||||
/// Represents the index of the slice used for this rule (which can only be 0 or -1)
|
||||
@@ -52,27 +47,25 @@ enum SliceBoundary {
|
||||
Last,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for MissingMaxsplitArg {
|
||||
impl Violation for MissingMaxsplitArg {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
actual_split_type: _,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
format!("Replace with `{suggested_split_type}(..., maxsplit=1)`.")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
let MissingMaxsplitArg {
|
||||
index,
|
||||
actual_split_type,
|
||||
suggested_split_type,
|
||||
} = self;
|
||||
|
||||
let suggested_split_type = match index {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
if actual_split_type == suggested_split_type {
|
||||
format!("Pass `maxsplit=1` into `str.{actual_split_type}()`")
|
||||
} else {
|
||||
format!("Use `str.{suggested_split_type}()` and pass `maxsplit=1`")
|
||||
format!(
|
||||
"Instead of `str.{actual_split_type}()`, call `str.{suggested_split_type}()` and pass `maxsplit=1`",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,8 +123,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
};
|
||||
|
||||
// Check the function is "split" or "rsplit"
|
||||
let actual_split_type = attr.as_str();
|
||||
if !matches!(actual_split_type, "split" | "rsplit") {
|
||||
let attr = attr.as_str();
|
||||
if !matches!(attr, "split" | "rsplit") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,48 +161,11 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
}
|
||||
}
|
||||
|
||||
let suggested_split_type = match slice_boundary {
|
||||
SliceBoundary::First => "split",
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
let maxsplit_argument_edit = fix::edits::add_argument(
|
||||
"maxsplit=1",
|
||||
arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
|
||||
// Only change `actual_split_type` if it doesn't match `suggested_split_type`
|
||||
let split_type_edit: Option<Edit> = if actual_split_type == suggested_split_type {
|
||||
None
|
||||
} else {
|
||||
Some(Edit::range_replacement(
|
||||
suggested_split_type.to_string(),
|
||||
attr.range(),
|
||||
))
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
checker.report_diagnostic(
|
||||
MissingMaxsplitArg {
|
||||
actual_split_type: actual_split_type.to_string(),
|
||||
suggested_split_type: suggested_split_type.to_string(),
|
||||
index: slice_boundary,
|
||||
actual_split_type: attr.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
);
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edits(
|
||||
maxsplit_argument_edit,
|
||||
split_type_edit,
|
||||
// If keyword.arg is `None` (i.e. if the function call contains `**kwargs`), mark the fix as unsafe
|
||||
if arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.any(|keyword| keyword.arg.is_none())
|
||||
{
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:14:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
12 | # Errors
|
||||
13 | ## Test split called directly on string literal
|
||||
@@ -10,19 +10,8 @@ missing_maxsplit_arg.py:14:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 |-"1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
14 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:15:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
13 | ## Test split called directly on string literal
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -31,19 +20,8 @@ missing_maxsplit_arg.py:15:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | # Errors
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 |-"1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
15 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:16:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -51,19 +29,8 @@ missing_maxsplit_arg.py:16:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | ## Test split called directly on string literal
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 |-"1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
16 |+"1,2,3".split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:17:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -72,19 +39,8 @@ missing_maxsplit_arg.py:17:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
18 |
|
||||
19 | ## Test split called on string variable
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 | "1,2,3".split(",")[0] # [missing-maxsplit-arg]
|
||||
15 15 | "1,2,3".split(",")[-1] # [missing-maxsplit-arg]
|
||||
16 16 | "1,2,3".rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
17 |-"1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
17 |+"1,2,3".rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:20:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -92,19 +48,8 @@ missing_maxsplit_arg.py:20:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
17 17 | "1,2,3".rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 |-SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
20 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:21:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
19 | ## Test split called on string variable
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -113,19 +58,8 @@ missing_maxsplit_arg.py:21:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
18 18 |
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 |-SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
21 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:22:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -133,19 +67,8 @@ missing_maxsplit_arg.py:22:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
19 19 | ## Test split called on string variable
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 |-SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
22 |+SEQ.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:23:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -154,19 +77,8 @@ missing_maxsplit_arg.py:23:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
24 |
|
||||
25 | ## Test split called on class attribute
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
20 20 | SEQ.split(",")[0] # [missing-maxsplit-arg]
|
||||
21 21 | SEQ.split(",")[-1] # [missing-maxsplit-arg]
|
||||
22 22 | SEQ.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
23 |-SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
23 |+SEQ.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:26:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -174,19 +86,8 @@ missing_maxsplit_arg.py:26:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
23 23 | SEQ.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 |-Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
26 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:27:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
25 | ## Test split called on class attribute
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -195,19 +96,8 @@ missing_maxsplit_arg.py:27:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
24 24 |
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 |-Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
27 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:28:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -215,19 +105,8 @@ missing_maxsplit_arg.py:28:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
25 25 | ## Test split called on class attribute
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 |-Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
28 |+Foo.class_str.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:29:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -236,19 +115,8 @@ missing_maxsplit_arg.py:29:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
30 |
|
||||
31 | ## Test split called on sliced string
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | Foo.class_str.split(",")[0] # [missing-maxsplit-arg]
|
||||
27 27 | Foo.class_str.split(",")[-1] # [missing-maxsplit-arg]
|
||||
28 28 | Foo.class_str.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
29 |-Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
29 |+Foo.class_str.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:32:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -256,19 +124,8 @@ missing_maxsplit_arg.py:32:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
29 29 | Foo.class_str.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 |-"1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
32 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:33:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
31 | ## Test split called on sliced string
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -277,19 +134,8 @@ missing_maxsplit_arg.py:33:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
30 30 |
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |-"1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 |+"1,2,3"[::-1][::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:34:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -298,19 +144,8 @@ missing_maxsplit_arg.py:34:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
31 31 | ## Test split called on sliced string
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |-SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:35:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -319,19 +154,8 @@ missing_maxsplit_arg.py:35:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
32 32 | "1,2,3"[::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 |-Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
35 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:36:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -340,19 +164,8 @@ missing_maxsplit_arg.py:36:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | "1,2,3"[::-1][::-1].split(",")[0] # [missing-maxsplit-arg]
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 |-"1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
36 |+"1,2,3"[::-1].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:37:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -360,19 +173,8 @@ missing_maxsplit_arg.py:37:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
34 34 | SEQ[:3].split(",")[0] # [missing-maxsplit-arg]
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |-SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 |+SEQ[:3].split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:38:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -381,19 +183,8 @@ missing_maxsplit_arg.py:38:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
39 |
|
||||
40 | ## Test sep given as named argument
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
35 35 | Foo.class_str[1:3].split(",")[-1] # [missing-maxsplit-arg]
|
||||
36 36 | "1,2,3"[::-1].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
37 37 | SEQ[:3].rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
38 |-Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
38 |+Foo.class_str[1:3].rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:41:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -401,19 +192,8 @@ missing_maxsplit_arg.py:41:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
38 38 | Foo.class_str[1:3].rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 |-"1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
41 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:42:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
40 | ## Test sep given as named argument
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -422,19 +202,8 @@ missing_maxsplit_arg.py:42:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
39 39 |
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 |-"1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
42 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:43:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -442,19 +211,8 @@ missing_maxsplit_arg.py:43:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
40 40 | ## Test sep given as named argument
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 |-"1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
43 |+"1,2,3".split(maxsplit=1, sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:44:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
@@ -463,19 +221,8 @@ missing_maxsplit_arg.py:44:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
45 |
|
||||
46 | ## Special cases
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
41 41 | "1,2,3".split(sep=",")[0] # [missing-maxsplit-arg]
|
||||
42 42 | "1,2,3".split(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
43 43 | "1,2,3".rsplit(sep=",")[0] # [missing-maxsplit-arg]
|
||||
44 |-"1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
44 |+"1,2,3".rsplit(maxsplit=1, sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:47:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -483,19 +230,8 @@ missing_maxsplit_arg.py:47:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
44 44 | "1,2,3".rsplit(sep=",")[-1] # [missing-maxsplit-arg]
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 |-"1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
47 |+"1,2,3".split("\n", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:48:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
46 | ## Special cases
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
@@ -503,19 +239,8 @@ missing_maxsplit_arg.py:48:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
45 45 |
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 |-"1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
48 |+"1,2,3".rsplit("split", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:49:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
@@ -524,19 +249,8 @@ missing_maxsplit_arg.py:49:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
50 |
|
||||
51 | ## Test class attribute named split
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
46 46 | ## Special cases
|
||||
47 47 | "1,2,3".split("\n")[0] # [missing-maxsplit-arg]
|
||||
48 48 | "1,2,3".split("split")[-1] # [missing-maxsplit-arg]
|
||||
49 |-"1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
49 |+"1,2,3".split("rsplit", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:52:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -544,19 +258,8 @@ missing_maxsplit_arg.py:52:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Safe fix
|
||||
49 49 | "1,2,3".rsplit("rsplit")[0] # [missing-maxsplit-arg]
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 |-Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
52 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:53:1: PLC0207 Instead of `str.split()`, call `str.rsplit()` and pass `maxsplit=1`
|
||||
|
|
||||
51 | ## Test class attribute named split
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -565,19 +268,8 @@ missing_maxsplit_arg.py:53:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.rsplit()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
50 50 |
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 |-Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
53 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:54:1: PLC0207 Instead of `str.rsplit()`, call `str.split()` and pass `maxsplit=1`
|
||||
|
|
||||
52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
@@ -585,19 +277,8 @@ missing_maxsplit_arg.py:54:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
|
|
||||
= help: Use `str.split()` and pass `maxsplit=1`
|
||||
|
||||
ℹ Safe fix
|
||||
51 51 | ## Test class attribute named split
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 |-Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
54 |+Bar.split.split(",", maxsplit=1)[0] # [missing-maxsplit-arg]
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:55:1: PLC0207 Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
|
||||
53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
@@ -606,37 +287,15 @@ missing_maxsplit_arg.py:55:1: PLC0207 [*] Replace with `rsplit(..., maxsplit=1)`
|
||||
56 |
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.rsplit()`
|
||||
|
||||
ℹ Safe fix
|
||||
52 52 | Bar.split.split(",")[0] # [missing-maxsplit-arg]
|
||||
53 53 | Bar.split.split(",")[-1] # [missing-maxsplit-arg]
|
||||
54 54 | Bar.split.rsplit(",")[0] # [missing-maxsplit-arg]
|
||||
55 |-Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
55 |+Bar.split.rsplit(",", maxsplit=1)[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:58:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
57 | ## Test unpacked dict literal kwargs
|
||||
58 | "1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
55 55 | Bar.split.rsplit(",")[-1] # [missing-maxsplit-arg]
|
||||
56 56 |
|
||||
57 57 | ## Test unpacked dict literal kwargs
|
||||
58 |-"1,2,3".split(**{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
58 |+"1,2,3".split(maxsplit=1, **{"sep": ","})[0] # [missing-maxsplit-arg]
|
||||
59 59 |
|
||||
60 60 |
|
||||
61 61 | # OK
|
||||
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:179:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
177 | # Errors
|
||||
178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
@@ -645,19 +304,8 @@ missing_maxsplit_arg.py:179:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
176 176 | ## TODO: These require the ability to resolve a dict variable name to a value
|
||||
177 177 | # Errors
|
||||
178 178 | kwargs_without_maxsplit = {"seq": ","}
|
||||
179 |-"1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
179 |+"1,2,3".split(maxsplit=1, **kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:182:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
180 | # OK
|
||||
181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
@@ -666,29 +314,11 @@ missing_maxsplit_arg.py:182:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
179 179 | "1,2,3".split(**kwargs_without_maxsplit)[0] # TODO: [missing-maxsplit-arg]
|
||||
180 180 | # OK
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 |-"1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
182 |+"1,2,3".split(",", maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 [*] Replace with `split(..., maxsplit=1)`.
|
||||
missing_maxsplit_arg.py:184:1: PLC0207 Pass `maxsplit=1` into `str.split()`
|
||||
|
|
||||
182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 | "1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0207
|
||||
|
|
||||
= help: Pass `maxsplit=1` into `str.split()`
|
||||
|
||||
ℹ Unsafe fix
|
||||
181 181 | kwargs_with_maxsplit = {"maxsplit": 1}
|
||||
182 182 | "1,2,3".split(",", **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
183 183 | kwargs_with_maxsplit = {"sep": ",", "maxsplit": 1}
|
||||
184 |-"1,2,3".split(**kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
184 |+"1,2,3".split(maxsplit=1, **kwargs_with_maxsplit)[0] # TODO: false positive
|
||||
|
||||
@@ -124,20 +124,10 @@ fn is_sequential(indices: &[usize]) -> bool {
|
||||
indices.iter().enumerate().all(|(idx, value)| idx == *value)
|
||||
}
|
||||
|
||||
static FORMAT_SPECIFIER: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?x)
|
||||
(?P<prefix>
|
||||
^|[^{]|(?:\{{2})+ # preceded by nothing, a non-brace, or an even number of braces
|
||||
)
|
||||
\{ # opening curly brace
|
||||
(?P<int>\d+) # followed by any integer
|
||||
(?P<fmt>.*?) # followed by any text
|
||||
} # followed by a closing brace
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
// An opening curly brace, followed by any integer, followed by any text,
|
||||
// followed by a closing brace.
|
||||
static FORMAT_SPECIFIER: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r"\{(?P<int>\d+)(?P<fmt>.*?)}").unwrap());
|
||||
|
||||
/// Remove the explicit positional indices from a format string.
|
||||
fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Arena<String>) {
|
||||
@@ -145,7 +135,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
|
||||
Expression::SimpleString(expr) => {
|
||||
expr.value = arena.alloc(
|
||||
FORMAT_SPECIFIER
|
||||
.replace_all(expr.value, "$prefix{$fmt}")
|
||||
.replace_all(expr.value, "{$fmt}")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
@@ -156,7 +146,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
|
||||
libcst_native::String::Simple(string) => {
|
||||
string.value = arena.alloc(
|
||||
FORMAT_SPECIFIER
|
||||
.replace_all(string.value, "$prefix{$fmt}")
|
||||
.replace_all(string.value, "{$fmt}")
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -481,7 +481,6 @@ UP030_0.py:59:1: UP030 [*] Use implicit references for positional format fields
|
||||
59 |+"{}_{}".format(2, 1, )
|
||||
60 60 |
|
||||
61 61 | "{1}_{0}".format(1, 2)
|
||||
62 62 |
|
||||
|
||||
UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
@@ -489,8 +488,6 @@ UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
60 |
|
||||
61 | "{1}_{0}".format(1, 2)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
62 |
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
@@ -500,42 +497,3 @@ UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
|
||||
60 60 |
|
||||
61 |-"{1}_{0}".format(1, 2)
|
||||
61 |+"{}_{}".format(2, 1)
|
||||
62 62 |
|
||||
63 63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 64 |
|
||||
|
||||
UP030_0.py:63:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
61 | "{1}_{0}".format(1, 2)
|
||||
62 |
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
64 |
|
||||
65 | "{{{0}}}".format(123)
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 |
|
||||
61 61 | "{1}_{0}".format(1, 2)
|
||||
62 62 |
|
||||
63 |-r"\d{{1,2}} {0}".format(42)
|
||||
63 |+r"\d{{1,2}} {}".format(42)
|
||||
64 64 |
|
||||
65 65 | "{{{0}}}".format(123)
|
||||
|
||||
UP030_0.py:65:1: UP030 [*] Use implicit references for positional format fields
|
||||
|
|
||||
63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 |
|
||||
65 | "{{{0}}}".format(123)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ UP030
|
||||
|
|
||||
= help: Remove explicit positional indices
|
||||
|
||||
ℹ Unsafe fix
|
||||
62 62 |
|
||||
63 63 | r"\d{{1,2}} {0}".format(42)
|
||||
64 64 |
|
||||
65 |-"{{{0}}}".format(123)
|
||||
65 |+"{{{}}}".format(123)
|
||||
|
||||
@@ -180,18 +180,18 @@ fn is_valid_argument_type(
|
||||
typing::is_float(binding, semantic),
|
||||
)
|
||||
})
|
||||
.unwrap_or((false, false))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
(false, false)
|
||||
};
|
||||
|
||||
match (method_name, constructor) {
|
||||
// Decimal.from_float: Only int or bool are safe (float is unsafe due to FloatOperation trap)
|
||||
// Decimal.from_float accepts int, bool, float
|
||||
(MethodName::FromFloat, Constructor::Decimal) => match resolved_type {
|
||||
ResolvedPythonType::Atom(PythonType::Number(
|
||||
NumberLike::Integer | NumberLike::Bool,
|
||||
NumberLike::Integer | NumberLike::Bool | NumberLike::Float,
|
||||
)) => true,
|
||||
ResolvedPythonType::Unknown => is_int,
|
||||
ResolvedPythonType::Unknown => is_int || is_float,
|
||||
_ => false,
|
||||
},
|
||||
// Fraction.from_float accepts int, bool, float
|
||||
@@ -286,8 +286,10 @@ fn handle_non_finite_float_special_case(
|
||||
let [float_arg] = arguments.args.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let normalized = as_non_finite_float_string_literal(float_arg)?;
|
||||
let replacement_text = format!(r#"{constructor_name}("{normalized}")"#);
|
||||
as_non_finite_float_string_literal(float_arg)?;
|
||||
|
||||
let replacement_arg = checker.locator().slice(float_arg).to_string();
|
||||
let replacement_text = format!("{constructor_name}({replacement_arg})");
|
||||
Some(Edit::range_replacement(replacement_text, call.range()))
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ FURB164.py:13:27: FURB164 [*] Verbose method `from_float` in `Decimal` construct
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
10 10 | _ = fractions.Fraction.from_float(4.2)
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
@@ -179,7 +179,7 @@ FURB164.py:14:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
11 11 | _ = Fraction.from_decimal(Decimal("4.2"))
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
@@ -200,7 +200,7 @@ FURB164.py:15:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
12 12 | _ = Fraction.from_decimal(Decimal("-4.2"))
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
@@ -221,7 +221,7 @@ FURB164.py:16:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
13 13 | _ = Fraction.from_decimal(Decimal.from_float(4.2))
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
@@ -242,7 +242,7 @@ FURB164.py:17:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Unsafe fix
|
||||
ℹ Safe fix
|
||||
14 14 | _ = Decimal.from_float(0.1)
|
||||
15 15 | _ = Decimal.from_float(-0.5)
|
||||
16 16 | _ = Decimal.from_float(5.0)
|
||||
@@ -310,7 +310,7 @@ FURB164.py:20:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
18 18 | _ = Decimal.from_float(float("inf"))
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 |-_ = Decimal.from_float(float("Infinity"))
|
||||
20 |+_ = Decimal("infinity")
|
||||
20 |+_ = Decimal("Infinity")
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
@@ -331,7 +331,7 @@ FURB164.py:21:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
19 19 | _ = Decimal.from_float(float("-inf"))
|
||||
20 20 | _ = Decimal.from_float(float("Infinity"))
|
||||
21 |-_ = Decimal.from_float(float("-Infinity"))
|
||||
21 |+_ = Decimal("-infinity")
|
||||
21 |+_ = Decimal("-Infinity")
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
@@ -373,7 +373,7 @@ FURB164.py:23:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
21 21 | _ = Decimal.from_float(float("-Infinity"))
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 |-_ = Decimal.from_float(float("-NaN "))
|
||||
23 |+_ = Decimal("-nan")
|
||||
23 |+_ = Decimal("-NaN ")
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
@@ -394,7 +394,7 @@ FURB164.py:24:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
22 22 | _ = Decimal.from_float(float("nan"))
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 |-_ = Decimal.from_float(float(" \n+nan \t"))
|
||||
24 |+_ = Decimal("+nan")
|
||||
24 |+_ = Decimal(" \n+nan \t")
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
@@ -415,7 +415,7 @@ FURB164.py:25:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
23 23 | _ = Decimal.from_float(float("-NaN "))
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 |-_ = Decimal.from_float(float(" iNf \n\t "))
|
||||
25 |+_ = Decimal("inf")
|
||||
25 |+_ = Decimal(" iNf \n\t ")
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
@@ -436,7 +436,7 @@ FURB164.py:26:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
24 24 | _ = Decimal.from_float(float(" \n+nan \t"))
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 |-_ = Decimal.from_float(float(" -inF\n \t"))
|
||||
26 |+_ = Decimal("-inf")
|
||||
26 |+_ = Decimal(" -inF\n \t")
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
@@ -456,7 +456,7 @@ FURB164.py:27:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
25 25 | _ = Decimal.from_float(float(" iNf \n\t "))
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 |-_ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
27 |+_ = Decimal("infinity")
|
||||
27 |+_ = Decimal(" InfinIty \n\t ")
|
||||
28 28 | _ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
@@ -477,7 +477,7 @@ FURB164.py:28:5: FURB164 [*] Verbose method `from_float` in `Decimal` constructi
|
||||
26 26 | _ = Decimal.from_float(float(" -inF\n \t"))
|
||||
27 27 | _ = Decimal.from_float(float(" InfinIty \n\t "))
|
||||
28 |-_ = Decimal.from_float(float(" -InfinIty\n \t"))
|
||||
28 |+_ = Decimal("-infinity")
|
||||
28 |+_ = Decimal(" -InfinIty\n \t")
|
||||
29 29 |
|
||||
30 30 | # Cases with keyword arguments - should produce unsafe fixes
|
||||
31 31 | _ = Fraction.from_decimal(dec=Decimal("4.2"))
|
||||
@@ -612,96 +612,3 @@ FURB164.py:45:5: FURB164 [*] Verbose method `from_float` in `Fraction` construct
|
||||
46 46 |
|
||||
47 47 | # OK - should not trigger the rule
|
||||
48 48 | _ = Fraction(0.1)
|
||||
|
||||
FURB164.py:60:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
61 | _ = Decimal.from_float(True)
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
57 57 | _ = decimal.Decimal(4.2)
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 |-_ = Decimal.from_float(1)
|
||||
60 |+_ = Decimal(1)
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
||||
FURB164.py:61:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
59 | # Cases with int and bool - should produce safe fixes
|
||||
60 | _ = Decimal.from_float(1)
|
||||
61 | _ = Decimal.from_float(True)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
62 |
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 |
|
||||
59 59 | # Cases with int and bool - should produce safe fixes
|
||||
60 60 | _ = Decimal.from_float(1)
|
||||
61 |-_ = Decimal.from_float(True)
|
||||
61 |+_ = Decimal(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
|
||||
FURB164.py:64:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
61 61 | _ = Decimal.from_float(True)
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 |-_ = Decimal.from_float(float("-nan"))
|
||||
64 |+_ = Decimal("-nan")
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:65:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
62 62 |
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 |-_ = Decimal.from_float(float("\x2dnan"))
|
||||
65 |+_ = Decimal("-nan")
|
||||
66 66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
|
||||
FURB164.py:66:5: FURB164 [*] Verbose method `from_float` in `Decimal` construction
|
||||
|
|
||||
64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 | _ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB164
|
||||
|
|
||||
= help: Replace with `Decimal` constructor
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | # Cases with non-finite floats - should produce safe fixes
|
||||
64 64 | _ = Decimal.from_float(float("-nan"))
|
||||
65 65 | _ = Decimal.from_float(float("\x2dnan"))
|
||||
66 |-_ = Decimal.from_float(float("\N{HYPHEN-MINUS}nan"))
|
||||
66 |+_ = Decimal("-nan")
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::checkers::ast::{Checker, LintContext};
|
||||
use crate::preview::is_unicode_to_unicode_confusables_enabled;
|
||||
use crate::rules::ruff::rules::Context;
|
||||
use crate::rules::ruff::rules::confusables::confusable;
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for ambiguous Unicode characters in strings.
|
||||
@@ -177,9 +178,12 @@ pub(crate) fn ambiguous_unicode_character_comment(
|
||||
context: &LintContext,
|
||||
locator: &Locator,
|
||||
range: TextRange,
|
||||
settings: &LinterSettings,
|
||||
) {
|
||||
let text = locator.slice(range);
|
||||
ambiguous_unicode_character(text, range, Context::Comment, context);
|
||||
for candidate in ambiguous_unicode_character(text, range, settings) {
|
||||
candidate.into_diagnostic(Context::Comment, settings, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// RUF001, RUF002
|
||||
@@ -200,19 +204,22 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
match part {
|
||||
ast::StringLikePart::String(string_literal) => {
|
||||
let text = checker.locator().slice(string_literal);
|
||||
ambiguous_unicode_character(
|
||||
text,
|
||||
string_literal.range(),
|
||||
context,
|
||||
checker.context(),
|
||||
);
|
||||
for candidate in
|
||||
ambiguous_unicode_character(text, string_literal.range(), checker.settings())
|
||||
{
|
||||
candidate.report_diagnostic(checker, context);
|
||||
}
|
||||
}
|
||||
ast::StringLikePart::Bytes(_) => {}
|
||||
ast::StringLikePart::FString(FString { elements, .. })
|
||||
| ast::StringLikePart::TString(TString { elements, .. }) => {
|
||||
for literal in elements.literals() {
|
||||
let text = checker.locator().slice(literal);
|
||||
ambiguous_unicode_character(text, literal.range(), context, checker.context());
|
||||
for candidate in
|
||||
ambiguous_unicode_character(text, literal.range(), checker.settings())
|
||||
{
|
||||
candidate.report_diagnostic(checker, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,12 +229,13 @@ pub(crate) fn ambiguous_unicode_character_string(checker: &Checker, string_like:
|
||||
fn ambiguous_unicode_character(
|
||||
text: &str,
|
||||
range: TextRange,
|
||||
context: Context,
|
||||
lint_context: &LintContext,
|
||||
) {
|
||||
settings: &LinterSettings,
|
||||
) -> Vec<Candidate> {
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
// Most of the time, we don't need to check for ambiguous unicode characters at all.
|
||||
if text.is_ascii() {
|
||||
return;
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// Iterate over the "words" in the text.
|
||||
@@ -239,7 +247,7 @@ fn ambiguous_unicode_character(
|
||||
if !word_candidates.is_empty() {
|
||||
if word_flags.is_candidate_word() {
|
||||
for candidate in word_candidates.drain(..) {
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
word_candidates.clear();
|
||||
@@ -250,23 +258,21 @@ fn ambiguous_unicode_character(
|
||||
// case, it's always included as a diagnostic.
|
||||
if !current_char.is_ascii() {
|
||||
if let Some(representant) = confusable(current_char as u32).filter(|representant| {
|
||||
is_unicode_to_unicode_confusables_enabled(lint_context.settings())
|
||||
|| representant.is_ascii()
|
||||
is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
|
||||
}) {
|
||||
let candidate = Candidate::new(
|
||||
TextSize::try_from(relative_offset).unwrap() + range.start(),
|
||||
current_char,
|
||||
representant,
|
||||
);
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
} else if current_char.is_ascii() {
|
||||
// The current word contains at least one ASCII character.
|
||||
word_flags |= WordFlags::ASCII;
|
||||
} else if let Some(representant) = confusable(current_char as u32).filter(|representant| {
|
||||
is_unicode_to_unicode_confusables_enabled(lint_context.settings())
|
||||
|| representant.is_ascii()
|
||||
is_unicode_to_unicode_confusables_enabled(settings) || representant.is_ascii()
|
||||
}) {
|
||||
// The current word contains an ambiguous unicode character.
|
||||
word_candidates.push(Candidate::new(
|
||||
@@ -284,11 +290,13 @@ fn ambiguous_unicode_character(
|
||||
if !word_candidates.is_empty() {
|
||||
if word_flags.is_candidate_word() {
|
||||
for candidate in word_candidates.drain(..) {
|
||||
candidate.into_diagnostic(context, lint_context);
|
||||
candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
word_candidates.clear();
|
||||
}
|
||||
|
||||
candidates
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -334,12 +342,13 @@ impl Candidate {
|
||||
}
|
||||
}
|
||||
|
||||
fn into_diagnostic(self, context: Context, lint_context: &LintContext) {
|
||||
if !lint_context
|
||||
.settings()
|
||||
.allowed_confusables
|
||||
.contains(&self.confusable)
|
||||
{
|
||||
fn into_diagnostic(
|
||||
self,
|
||||
context: Context,
|
||||
settings: &LinterSettings,
|
||||
lint_context: &LintContext,
|
||||
) {
|
||||
if !settings.allowed_confusables.contains(&self.confusable) {
|
||||
let char_range = TextRange::at(self.offset, self.confusable.text_len());
|
||||
match context {
|
||||
Context::String => lint_context.report_diagnostic_if_enabled(
|
||||
@@ -366,6 +375,39 @@ impl Candidate {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn report_diagnostic(self, checker: &Checker, context: Context) {
|
||||
if !checker
|
||||
.settings()
|
||||
.allowed_confusables
|
||||
.contains(&self.confusable)
|
||||
{
|
||||
let char_range = TextRange::at(self.offset, self.confusable.text_len());
|
||||
match context {
|
||||
Context::String => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterString {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
Context::Docstring => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterDocstring {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
Context::Comment => checker.report_diagnostic_if_enabled(
|
||||
AmbiguousUnicodeCharacterComment {
|
||||
confusable: self.confusable,
|
||||
representant: self.representant,
|
||||
},
|
||||
char_range,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NamedUnicode(char);
|
||||
|
||||
@@ -149,7 +149,8 @@ fn convert_call_to_conversion_flag(
|
||||
formatted_string_expression.whitespace_before_expression = space();
|
||||
}
|
||||
|
||||
formatted_string_expression.expression = if needs_paren_expr(arg) {
|
||||
formatted_string_expression.expression = if needs_paren(OperatorPrecedence::from_expr(arg))
|
||||
{
|
||||
call.args[0]
|
||||
.value
|
||||
.clone()
|
||||
@@ -177,16 +178,6 @@ fn needs_paren(precedence: OperatorPrecedence) -> bool {
|
||||
precedence <= OperatorPrecedence::Lambda
|
||||
}
|
||||
|
||||
fn needs_paren_expr(arg: &Expr) -> bool {
|
||||
// Generator expressions need to be parenthesized in f-string expressions
|
||||
if let Some(generator) = arg.as_generator_expr() {
|
||||
return !generator.parenthesized;
|
||||
}
|
||||
|
||||
// Check precedence for other expressions
|
||||
needs_paren(OperatorPrecedence::from_expr(arg))
|
||||
}
|
||||
|
||||
/// Represents the three built-in Python conversion functions that can be replaced
|
||||
/// with f-string conversion flags.
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
@@ -359,7 +359,6 @@ RUF010.py:52:4: RUF010 [*] Use explicit conversion flag
|
||||
52 |+f"{(x := 2)!r}"
|
||||
53 53 |
|
||||
54 54 | f"{str(object=3)}"
|
||||
55 55 |
|
||||
|
||||
RUF010.py:54:4: RUF010 [*] Use explicit conversion flag
|
||||
|
|
||||
@@ -367,8 +366,6 @@ RUF010.py:54:4: RUF010 [*] Use explicit conversion flag
|
||||
53 |
|
||||
54 | f"{str(object=3)}"
|
||||
| ^^^^^^^^^^^^^ RUF010
|
||||
55 |
|
||||
56 | f"{str(x for x in [])}"
|
||||
|
|
||||
= help: Replace with conversion flag
|
||||
|
||||
@@ -378,42 +375,3 @@ RUF010.py:54:4: RUF010 [*] Use explicit conversion flag
|
||||
53 53 |
|
||||
54 |-f"{str(object=3)}"
|
||||
54 |+f"{3!s}"
|
||||
55 55 |
|
||||
56 56 | f"{str(x for x in [])}"
|
||||
57 57 |
|
||||
|
||||
RUF010.py:56:4: RUF010 [*] Use explicit conversion flag
|
||||
|
|
||||
54 | f"{str(object=3)}"
|
||||
55 |
|
||||
56 | f"{str(x for x in [])}"
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF010
|
||||
57 |
|
||||
58 | f"{str((x for x in []))}"
|
||||
|
|
||||
= help: Replace with conversion flag
|
||||
|
||||
ℹ Safe fix
|
||||
53 53 |
|
||||
54 54 | f"{str(object=3)}"
|
||||
55 55 |
|
||||
56 |-f"{str(x for x in [])}"
|
||||
56 |+f"{(x for x in [])!s}"
|
||||
57 57 |
|
||||
58 58 | f"{str((x for x in []))}"
|
||||
|
||||
RUF010.py:58:4: RUF010 [*] Use explicit conversion flag
|
||||
|
|
||||
56 | f"{str(x for x in [])}"
|
||||
57 |
|
||||
58 | f"{str((x for x in []))}"
|
||||
| ^^^^^^^^^^^^^^^^^^^^ RUF010
|
||||
|
|
||||
= help: Replace with conversion flag
|
||||
|
||||
ℹ Safe fix
|
||||
55 55 |
|
||||
56 56 | f"{str(x for x in [])}"
|
||||
57 57 |
|
||||
58 |-f"{str((x for x in []))}"
|
||||
58 |+f"{(x for x in [])!s}"
|
||||
|
||||
@@ -22,7 +22,7 @@ RUF054.py:10:3: RUF054 Indented form feed
|
||||
RUF054.py:13:2: RUF054 Indented form feed
|
||||
|
|
||||
12 | def _():
|
||||
13 | pass
|
||||
13 | pass
|
||||
| ^ RUF054
|
||||
14 |
|
||||
15 | if False:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
@@ -163,7 +162,7 @@ impl SourceFileBuilder {
|
||||
/// A source file that is identified by its name. Optionally stores the source code and [`LineIndex`].
|
||||
///
|
||||
/// Cloning a [`SourceFile`] is cheap, because it only requires bumping a reference count.
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct SourceFile {
|
||||
inner: Arc<SourceFileInner>,
|
||||
@@ -242,13 +241,6 @@ impl PartialEq for SourceFileInner {
|
||||
|
||||
impl Eq for SourceFileInner {}
|
||||
|
||||
impl Hash for SourceFileInner {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.code.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// The line and column of an offset in a source file.
|
||||
///
|
||||
/// See [`LineIndex::line_column`] for more information.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.7"
|
||||
version = "0.12.5"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -66,9 +66,9 @@ enum CompletionTargetTokens<'t> {
|
||||
/// A token was found under the cursor, but it didn't
|
||||
/// match any of our anticipated token patterns.
|
||||
Generic { token: &'t Token },
|
||||
/// No token was found. We generally treat this like
|
||||
/// `Generic` (i.e., offer scope based completions).
|
||||
Unknown,
|
||||
/// No token was found, but we have the offset of the
|
||||
/// cursor.
|
||||
Unknown { offset: TextSize },
|
||||
}
|
||||
|
||||
impl<'t> CompletionTargetTokens<'t> {
|
||||
@@ -78,7 +78,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
static OBJECT_DOT_NON_EMPTY: [TokenKind; 2] = [TokenKind::Dot, TokenKind::Name];
|
||||
|
||||
let offset = match parsed.tokens().at_offset(offset) {
|
||||
TokenAt::None => return Some(CompletionTargetTokens::Unknown),
|
||||
TokenAt::None => return Some(CompletionTargetTokens::Unknown { offset }),
|
||||
TokenAt::Single(tok) => tok.end(),
|
||||
TokenAt::Between(_, tok) => tok.start(),
|
||||
};
|
||||
@@ -122,7 +122,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
return None;
|
||||
} else {
|
||||
let Some(last) = before.last() else {
|
||||
return Some(CompletionTargetTokens::Unknown);
|
||||
return Some(CompletionTargetTokens::Unknown { offset });
|
||||
};
|
||||
CompletionTargetTokens::Generic { token: last }
|
||||
},
|
||||
@@ -171,7 +171,7 @@ impl<'t> CompletionTargetTokens<'t> {
|
||||
node: covering_node.node(),
|
||||
})
|
||||
}
|
||||
CompletionTargetTokens::Unknown => {
|
||||
CompletionTargetTokens::Unknown { offset } => {
|
||||
let range = TextRange::empty(offset);
|
||||
let covering_node = covering_node(parsed.syntax().into(), range);
|
||||
Some(CompletionTargetAst::Scoped {
|
||||
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
|
||||
impl CursorTest {
|
||||
fn references(&self) -> String {
|
||||
let Some(mut reference_results) =
|
||||
let Some(reference_results) =
|
||||
goto_references(&self.db, self.cursor.file, self.cursor.offset, true)
|
||||
else {
|
||||
return "No references found".to_string();
|
||||
@@ -48,8 +48,6 @@ mod tests {
|
||||
return "No references found".to_string();
|
||||
}
|
||||
|
||||
reference_results.sort_by_key(ReferenceTarget::file);
|
||||
|
||||
self.render_diagnostics(reference_results.into_iter().enumerate().map(
|
||||
|(i, ref_item)| -> ReferenceResult {
|
||||
ReferenceResult {
|
||||
|
||||
@@ -199,14 +199,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:13
|
||||
@@ -228,14 +228,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:2:22
|
||||
@@ -344,14 +344,14 @@ mod tests {
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:18
|
||||
@@ -413,14 +413,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:2890:7
|
||||
--> stdlib/builtins.pyi:2888:7
|
||||
|
|
||||
2888 | """See PEP 585"""
|
||||
2889 |
|
||||
2890 | class dict(MutableMapping[_KT, _VT]):
|
||||
2886 | """See PEP 585"""
|
||||
2887 |
|
||||
2888 | class dict(MutableMapping[_KT, _VT]):
|
||||
| ^^^^
|
||||
2891 | """dict() -> new empty dictionary
|
||||
2892 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
2889 | """dict() -> new empty dictionary
|
||||
2890 | dict(mapping) -> new dictionary initialized from a mapping object's
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:5
|
||||
@@ -444,14 +444,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -537,14 +537,14 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:27
|
||||
@@ -568,13 +568,13 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:922:11
|
||||
--> stdlib/types.pyi:921:11
|
||||
|
|
||||
920 | if sys.version_info >= (3, 10):
|
||||
921 | @final
|
||||
922 | class NoneType:
|
||||
919 | if sys.version_info >= (3, 10):
|
||||
920 | @final
|
||||
921 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
923 | """The type of the None singleton."""
|
||||
922 | """The type of the None singleton."""
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -585,14 +585,14 @@ f(**kwargs<CURSOR>)
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/builtins.pyi:892:7
|
||||
--> stdlib/builtins.pyi:890:7
|
||||
|
|
||||
890 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
891 |
|
||||
892 | class str(Sequence[str]):
|
||||
888 | def __getitem__(self, key: int, /) -> str | int | None: ...
|
||||
889 |
|
||||
890 | class str(Sequence[str]):
|
||||
| ^^^
|
||||
893 | """str(object='') -> str
|
||||
894 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
891 | """str(object='') -> str
|
||||
892 | str(bytes_or_buffer[, encoding[, errors]]) -> str
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
//! types, and documentation. It supports multiple signatures for union types
|
||||
//! and overloads.
|
||||
|
||||
use crate::{
|
||||
Db, docstring::get_parameter_documentation, find_node::covering_node, stub_mapping::StubMapper,
|
||||
};
|
||||
use crate::{Db, docstring::get_parameter_documentation, find_node::covering_node};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::{CallSignatureDetails, call_signature_details};
|
||||
|
||||
// TODO: We may want to add special-case handling for calls to constructors
|
||||
// so the class docstring is used in place of (or inaddition to) any docstring
|
||||
// associated with the __new__ or __init__ call.
|
||||
// Limitations of the current implementation:
|
||||
|
||||
// TODO - If the target function is declared in a stub file but defined (implemented)
|
||||
// in a source file, the documentation will not reflect the a docstring that appears
|
||||
// only in the implementation. To do this, we'll need to map the function or
|
||||
// method in the stub to the implementation and extract the docstring from there.
|
||||
|
||||
/// Information about a function parameter
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -66,6 +66,10 @@ pub fn signature_help(db: &dyn Db, file: File, offset: TextSize) -> Option<Signa
|
||||
// Get the call expression at the given position.
|
||||
let (call_expr, current_arg_index) = get_call_expr(&parsed, offset)?;
|
||||
|
||||
if offset >= call_expr.end() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get signature details from the semantic analyzer.
|
||||
let signature_details: Vec<CallSignatureDetails<'_>> =
|
||||
call_signature_details(db, file, call_expr);
|
||||
@@ -97,19 +101,11 @@ fn get_call_expr(
|
||||
parsed: &ruff_db::parsed::ParsedModuleRef,
|
||||
offset: TextSize,
|
||||
) -> Option<(&ast::ExprCall, usize)> {
|
||||
let root_node: AnyNodeRef = parsed.syntax().into();
|
||||
|
||||
// Create a range from the offset for the covering_node function.
|
||||
// Use length 1 if it fits within the root node, otherwise use zero-length range.
|
||||
let one_char_range = TextRange::at(offset, TextSize::from(1));
|
||||
let range = if root_node.range().contains_range(one_char_range) {
|
||||
one_char_range
|
||||
} else {
|
||||
TextRange::at(offset, TextSize::from(0))
|
||||
};
|
||||
let range = TextRange::new(offset, offset);
|
||||
|
||||
// Find the covering node at the given position that is a function call.
|
||||
let covering_node = covering_node(root_node, range)
|
||||
let covering_node = covering_node(parsed.syntax().into(), range)
|
||||
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
|
||||
.ok()?;
|
||||
|
||||
@@ -185,32 +181,11 @@ fn create_signature_details_from_call_signature_details(
|
||||
|
||||
/// Determine appropriate documentation for a callable type based on its original type.
|
||||
fn get_callable_documentation(db: &dyn crate::Db, definition: Option<Definition>) -> String {
|
||||
// TODO: If the definition is located within a stub file and no docstring
|
||||
// is present, try to map the symbol to an implementation file and extract
|
||||
// the docstring from that location.
|
||||
if let Some(definition) = definition {
|
||||
// First try to get the docstring from the original definition
|
||||
let original_docstring = definition.docstring(db);
|
||||
|
||||
// If we got a docstring from the original definition, use it
|
||||
if let Some(docstring) = original_docstring {
|
||||
return docstring;
|
||||
}
|
||||
|
||||
// If the definition is located within a stub file and no docstring
|
||||
// is present, try to map the symbol to an implementation file and extract
|
||||
// the docstring from that location.
|
||||
let stub_mapper = StubMapper::new(db);
|
||||
let resolved_definition = ResolvedDefinition::Definition(definition);
|
||||
|
||||
// Try to find the corresponding implementation definition
|
||||
for mapped_definition in stub_mapper.map_definition(resolved_definition) {
|
||||
if let ResolvedDefinition::Definition(impl_definition) = mapped_definition {
|
||||
if let Some(impl_docstring) = impl_definition.docstring(db) {
|
||||
return impl_docstring;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to empty string if no docstring found anywhere
|
||||
String::new()
|
||||
definition.docstring(db).unwrap_or_default()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
@@ -708,95 +683,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_after_closing_paren() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def func1(v: str) -> str:
|
||||
return v
|
||||
|
||||
r = func1("")<CURSOR>
|
||||
print(r)
|
||||
"#,
|
||||
);
|
||||
|
||||
let result = test.signature_help();
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Signature help should return None after closing paren"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_after_nested_closing_paren() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def inner_func(x: str) -> str:
|
||||
return x.upper()
|
||||
|
||||
def outer_func(a: int, b: str) -> str:
|
||||
return f"{a}: {b}"
|
||||
|
||||
result = outer_func(42, inner_func("hello")<CURSOR>
|
||||
"#,
|
||||
);
|
||||
|
||||
// Should return signature help for the outer function call
|
||||
// even though cursor is after the closing paren of the inner call
|
||||
let result = test
|
||||
.signature_help()
|
||||
.expect("Should have signature help for outer function");
|
||||
assert_eq!(result.signatures.len(), 1);
|
||||
|
||||
let signature = &result.signatures[0];
|
||||
assert!(signature.label.contains("a: int") && signature.label.contains("b: str"));
|
||||
|
||||
// Should be on the second parameter (b: str) since we're after the inner call
|
||||
assert_eq!(signature.active_parameter, Some(1));
|
||||
assert_eq!(result.active_signature, Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signature_help_stub_to_implementation_mapping() {
|
||||
// Test that when a function is called from a stub file with no docstring,
|
||||
// the signature help includes the docstring from the corresponding implementation file
|
||||
let test = CursorTest::builder()
|
||||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import func
|
||||
result = func(<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.pyi",
|
||||
r#"
|
||||
def func() -> str: ...
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
def func() -> str:
|
||||
"""This function does something."""
|
||||
return ""
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
||||
let result = test.signature_help().expect("Should have signature help");
|
||||
assert_eq!(result.signatures.len(), 1);
|
||||
|
||||
let signature = &result.signatures[0];
|
||||
assert_eq!(signature.label, "() -> str");
|
||||
|
||||
let expected_docstring = "This function does something.";
|
||||
assert_eq!(
|
||||
signature.documentation,
|
||||
Some(expected_docstring.to_string())
|
||||
);
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn signature_help(&self) -> Option<SignatureHelpInfo> {
|
||||
crate::signature_help::signature_help(&self.db, self.cursor.file, self.cursor.offset)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::fmt::Formatter;
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::panic::{AssertUnwindSafe, RefUnwindSafe};
|
||||
use std::sync::Arc;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
@@ -87,7 +87,9 @@ impl ProjectDatabase {
|
||||
///
|
||||
/// [`set_check_mode`]: ProjectDatabase::set_check_mode
|
||||
pub fn check(&self) -> Vec<Diagnostic> {
|
||||
self.project().check(self, &mut DummyReporter)
|
||||
let mut reporter = DummyReporter;
|
||||
let reporter = AssertUnwindSafe(&mut reporter as &mut dyn ProgressReporter);
|
||||
self.project().check(self, reporter)
|
||||
}
|
||||
|
||||
/// Checks the files in the project and its dependencies, using the given reporter.
|
||||
@@ -96,6 +98,7 @@ impl ProjectDatabase {
|
||||
///
|
||||
/// [`set_check_mode`]: ProjectDatabase::set_check_mode
|
||||
pub fn check_with_reporter(&self, reporter: &mut dyn ProgressReporter) -> Vec<Diagnostic> {
|
||||
let reporter = AssertUnwindSafe(reporter);
|
||||
self.project().check(self, reporter)
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ impl Project {
|
||||
/// This is a salsa query to prevent re-computing queries if other, unrelated
|
||||
/// settings change. For example, we don't want that changing the terminal settings
|
||||
/// invalidates any type checking queries.
|
||||
#[salsa::tracked(returns(deref), heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(returns(deref), heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub fn rules(self, db: &dyn Db) -> Arc<RuleSelection> {
|
||||
self.settings(db).to_rules()
|
||||
}
|
||||
@@ -228,7 +228,7 @@ impl Project {
|
||||
pub(crate) fn check(
|
||||
self,
|
||||
db: &ProjectDatabase,
|
||||
reporter: &mut dyn ProgressReporter,
|
||||
mut reporter: AssertUnwindSafe<&mut dyn ProgressReporter>,
|
||||
) -> Vec<Diagnostic> {
|
||||
let project_span = tracing::debug_span!("Project::check");
|
||||
let _span = project_span.enter();
|
||||
@@ -511,7 +511,7 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked(returns(ref), heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(returns(ref), heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub(crate) fn check_file_impl(db: &dyn Db, file: File) -> Result<Box<[Diagnostic]>, Diagnostic> {
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ impl Override {
|
||||
}
|
||||
|
||||
/// Resolves the settings for a given file.
|
||||
#[salsa::tracked(returns(ref), heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(returns(ref), heap_size=get_size2::GetSize::get_heap_size)]
|
||||
pub(crate) fn file_settings(db: &dyn Db, file: File) -> FileSettings {
|
||||
let settings = db.project().settings(db);
|
||||
|
||||
@@ -155,7 +155,7 @@ pub(crate) fn file_settings(db: &dyn Db, file: File) -> FileSettings {
|
||||
/// This is to make Salsa happy because it requires that queries with only a single argument
|
||||
/// take a salsa-struct as argument, which isn't the case here. The `()` enables salsa's
|
||||
/// automatic interning for the arguments.
|
||||
#[salsa::tracked(heap_size=get_size2::heap_size)]
|
||||
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
|
||||
fn merge_overrides(db: &dyn Db, overrides: Vec<Arc<InnerOverrideOptions>>, _: ()) -> FileSettings {
|
||||
let mut overrides = overrides.into_iter().rev();
|
||||
let mut merged = (*overrides.next().unwrap()).clone();
|
||||
|
||||
@@ -13,7 +13,7 @@ reveal_type(x) # revealed: int | float
|
||||
|
||||
x = (1, 2)
|
||||
x += (3, 4)
|
||||
reveal_type(x) # revealed: tuple[Literal[1, 2, 3, 4], ...]
|
||||
reveal_type(x) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]]
|
||||
```
|
||||
|
||||
## Dunder methods
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
# `async` / `await`
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
async def retrieve() -> int:
|
||||
return 42
|
||||
|
||||
async def main():
|
||||
result = await retrieve()
|
||||
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
## Generic `async` functions
|
||||
|
||||
```py
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
async def persist(x: T) -> T:
|
||||
return x
|
||||
|
||||
async def f(x: int):
|
||||
result = await persist(x)
|
||||
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
## Use cases
|
||||
|
||||
### `Future`
|
||||
|
||||
```py
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
|
||||
def blocking_function() -> int:
|
||||
return 42
|
||||
|
||||
async def main():
|
||||
loop = asyncio.get_event_loop()
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
result = await loop.run_in_executor(pool, blocking_function)
|
||||
|
||||
# TODO: should be `int`
|
||||
reveal_type(result) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `asyncio.Task`
|
||||
|
||||
```py
|
||||
import asyncio
|
||||
|
||||
async def f() -> int:
|
||||
return 1
|
||||
|
||||
async def main():
|
||||
task = asyncio.create_task(f())
|
||||
|
||||
result = await task
|
||||
|
||||
# TODO: this should be `int`
|
||||
reveal_type(result) # revealed: Unknown
|
||||
```
|
||||
|
||||
### `asyncio.gather`
|
||||
|
||||
```py
|
||||
import asyncio
|
||||
|
||||
async def task(name: str) -> int:
|
||||
return len(name)
|
||||
|
||||
async def main():
|
||||
(a, b) = await asyncio.gather(
|
||||
task("A"),
|
||||
task("B"),
|
||||
)
|
||||
|
||||
# TODO: these should be `int`
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Under the hood
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12" # Use 3.12 to be able to use PEP 695 generics
|
||||
```
|
||||
|
||||
Let's look at the example from the beginning again:
|
||||
|
||||
```py
|
||||
async def retrieve() -> int:
|
||||
return 42
|
||||
```
|
||||
|
||||
When we look at the signature of this function, we see that it actually returns a `CoroutineType`:
|
||||
|
||||
```py
|
||||
reveal_type(retrieve) # revealed: def retrieve() -> CoroutineType[Any, Any, int]
|
||||
```
|
||||
|
||||
The expression `await retrieve()` desugars into a call to the `__await__` dunder method on the
|
||||
`CoroutineType` object, followed by a `yield from`. Let's first see the return type of `__await__`:
|
||||
|
||||
```py
|
||||
reveal_type(retrieve().__await__()) # revealed: Generator[Any, None, int]
|
||||
```
|
||||
|
||||
We can see that this returns a `Generator` that yields `Any`, and eventually returns `int`. For the
|
||||
final type of the `await` expression, we retrieve that third argument of the `Generator` type:
|
||||
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
def _():
|
||||
result = yield from retrieve().__await__()
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
49
crates/ty_python_semantic/resources/mdtest/binary/in.md
Normal file
49
crates/ty_python_semantic/resources/mdtest/binary/in.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Static binary operations using `in`
|
||||
|
||||
## Basic functionality
|
||||
|
||||
This demonstrates type inference support for `<str-literal> in <tuple>`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
static_assert("foo" in ("quux", "foo", "baz"))
|
||||
static_assert("foo" not in ("quux", "bar", "baz"))
|
||||
```
|
||||
|
||||
## With variables
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
x = ("quux", "foo", "baz")
|
||||
static_assert("foo" in x)
|
||||
|
||||
x = ("quux", "bar", "baz")
|
||||
static_assert("foo" not in x)
|
||||
```
|
||||
|
||||
## Statically unknown results in a `bool`
|
||||
|
||||
```py
|
||||
def _(a: str, b: str):
|
||||
reveal_type("foo" in (a, b)) # revealed: bool
|
||||
```
|
||||
|
||||
## Values being unknown doesn't mean the result is unknown
|
||||
|
||||
For example, when the types are completely disjoint:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
def _(a: int, b: int):
|
||||
static_assert("foo" not in (a, b))
|
||||
```
|
||||
|
||||
## Failure cases
|
||||
|
||||
```py
|
||||
# We don't support byte strings.
|
||||
reveal_type(b"foo" not in (b"quux", b"foo", b"baz")) # revealed: bool
|
||||
```
|
||||
@@ -3,14 +3,14 @@
|
||||
## Concatenation for heterogeneous tuples
|
||||
|
||||
```py
|
||||
reveal_type((1, 2) + (3, 4)) # revealed: tuple[Literal[1, 2, 3, 4], ...]
|
||||
reveal_type(() + (1, 2)) # revealed: tuple[Literal[1, 2], ...]
|
||||
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1, 2], ...]
|
||||
reveal_type((1, 2) + (3, 4)) # revealed: tuple[Literal[1], Literal[2], Literal[3], Literal[4]]
|
||||
reveal_type(() + (1, 2)) # revealed: tuple[Literal[1], Literal[2]]
|
||||
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1], Literal[2]]
|
||||
reveal_type(() + ()) # revealed: tuple[()]
|
||||
|
||||
def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||
reveal_type(x + y) # revealed: tuple[int | str | None | tuple[int], ...]
|
||||
reveal_type(y + x) # revealed: tuple[None | tuple[int] | int | str, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int, str, None, tuple[int]]
|
||||
reveal_type(y + x) # revealed: tuple[None, tuple[int], int, str]
|
||||
```
|
||||
|
||||
## Concatenation for homogeneous tuples
|
||||
@@ -19,10 +19,10 @@ def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
|
||||
def _(x: tuple[int, ...], y: tuple[str, ...]):
|
||||
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
reveal_type((1, 2) + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + (3, 4)) # revealed: tuple[int, ...]
|
||||
reveal_type((1, 2) + x + (3, 4)) # revealed: tuple[int, ...]
|
||||
reveal_type((1, 2) + y + (3, 4) + x) # revealed: tuple[int | str, ...]
|
||||
reveal_type((1, 2) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||
reveal_type(x + (3, 4)) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type((1, 2) + x + (3, 4)) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type((1, 2) + y + (3, 4) + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||
```
|
||||
|
||||
We get the same results even when we use a legacy type alias, even though this involves first
|
||||
@@ -41,8 +41,8 @@ StrTuple = tuple[str, ...]
|
||||
def _(one_two: OneTwo, x: IntTuple, y: StrTuple, three_four: ThreeFour):
|
||||
reveal_type(x + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + y) # revealed: tuple[int | str, ...]
|
||||
reveal_type(one_two + x) # revealed: tuple[int, ...]
|
||||
reveal_type(x + three_four) # revealed: tuple[int, ...]
|
||||
reveal_type(one_two + x + three_four) # revealed: tuple[int, ...]
|
||||
reveal_type(one_two + y + three_four + x) # revealed: tuple[int | str, ...]
|
||||
reveal_type(one_two + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...]]
|
||||
reveal_type(x + three_four) # revealed: tuple[*tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type(one_two + x + three_four) # revealed: tuple[Literal[1], Literal[2], *tuple[int, ...], Literal[3], Literal[4]]
|
||||
reveal_type(one_two + y + three_four + x) # revealed: tuple[Literal[1], Literal[2], *tuple[int | str, ...]]
|
||||
```
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
# Call expression
|
||||
|
||||
## A
|
||||
## Simple
|
||||
|
||||
```py
|
||||
reveal_type(1)
|
||||
def get_int() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
## B
|
||||
## Async
|
||||
|
||||
```py
|
||||
async def get_int_async() -> int:
|
||||
return 42
|
||||
|
||||
# TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]`
|
||||
reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
|
||||
```
|
||||
|
||||
## Generic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
@@ -14,5 +27,618 @@ python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
reveal_type(1)
|
||||
def get_int[T]() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
## Decorated
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
def decorator(func) -> Callable[[], int]:
|
||||
return foo
|
||||
|
||||
@decorator
|
||||
def bar() -> str:
|
||||
return "bar"
|
||||
|
||||
reveal_type(bar()) # revealed: int
|
||||
```
|
||||
|
||||
## Invalid callable
|
||||
|
||||
```py
|
||||
nonsense = 123
|
||||
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
|
||||
```
|
||||
|
||||
## Potentially unbound function
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
def foo() -> int:
|
||||
return 42
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
```
|
||||
|
||||
## Splatted arguments
|
||||
|
||||
### Unknown argument length
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: list[int]) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, ...]) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
||||
### Fixed-length tuple argument
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: tuple[int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [missing-argument]
|
||||
takes_two_positional_only(*args) # error: [missing-argument]
|
||||
takes_two_different(*args) # error: [missing-argument]
|
||||
takes_two_different_positional_only(*args) # error: [missing-argument]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
def _(args: tuple[int, int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, str]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Mixed tuple argument
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: int) -> None: ...
|
||||
def takes_two(x: int, y: int) -> None: ...
|
||||
def takes_two_positional_only(x: int, y: int, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: int, *args) -> None: ...
|
||||
def takes_at_least_two(x: int, y: int, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: tuple[int, *tuple[int, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[str, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
|
||||
def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[int, ...], int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: tuple[int, *tuple[str, ...], int]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### String argument
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def takes_zero() -> None: ...
|
||||
def takes_one(x: str) -> None: ...
|
||||
def takes_two(x: str, y: str) -> None: ...
|
||||
def takes_two_positional_only(x: str, y: str, /) -> None: ...
|
||||
def takes_two_different(x: int, y: str) -> None: ...
|
||||
def takes_two_different_positional_only(x: int, y: str, /) -> None: ...
|
||||
def takes_at_least_zero(*args) -> None: ...
|
||||
def takes_at_least_one(x: str, *args) -> None: ...
|
||||
def takes_at_least_two(x: str, y: str, *args) -> None: ...
|
||||
def takes_at_least_two_positional_only(x: str, y: str, /, *args) -> None: ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(args: Literal["a"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args) # error: [missing-argument]
|
||||
takes_two_positional_only(*args) # error: [missing-argument]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [missing-argument]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [missing-argument]
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
def _(args: Literal["ab"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: Literal["abc"]) -> None:
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args) # error: [too-many-positional-arguments]
|
||||
takes_two_positional_only(*args) # error: [too-many-positional-arguments]
|
||||
# error: [invalid-argument-type]
|
||||
# error: [too-many-positional-arguments]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [too-many-positional-arguments]
|
||||
takes_two_different_positional_only(*args)
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
def _(args: str) -> None:
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
||||
### Argument expansion regression
|
||||
|
||||
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
||||
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
||||
to retry both `match_parameters` _and_ `check_types` for each expansion. Currently we only retry
|
||||
`check_types`.
|
||||
|
||||
The issue is that argument expansion might produce a splatted value with a different arity than what
|
||||
we originally inferred for the unexpanded value, and that in turn can affect which parameters the
|
||||
splatted value is matched with.
|
||||
|
||||
The first example correctly produces an error. The `tuple[int, str]` union element has a precise
|
||||
arity of two, and so parameter matching chooses the first overload. The second element of the tuple
|
||||
does not match the second parameter type, which yielding an `invalid-argument-type` error.
|
||||
|
||||
The third example should produce the same error. However, because we have a union, we do not see the
|
||||
precise arity of each union element during parameter matching. Instead, we infer an arity of "zero
|
||||
or more" for the union as a whole, and use that less precise arity when matching parameters. We
|
||||
therefore consider the second overload to still be a potential candidate for the `tuple[int, str]`
|
||||
union element. During type checking, we have to force the arity of each union element to match the
|
||||
inferred arity of the union as a whole (turning `tuple[int, str]` into `tuple[int | str, ...]`).
|
||||
That less precise tuple type-checks successfully against the second overload, making us incorrectly
|
||||
think that `tuple[int, str]` is a valid splatted call.
|
||||
|
||||
If we update argument expansion to retry parameter matching with the precise arity of each union
|
||||
element, we will correctly rule out the second overload for `tuple[int, str]`, just like we do when
|
||||
splatting that tuple directly (instead of as part of a union).
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(x: int, y: int) -> None: ...
|
||||
@overload
|
||||
def f(x: int, y: str, z: int) -> None: ...
|
||||
def f(*args): ...
|
||||
|
||||
# Test all of the above with a number of different splatted argument types
|
||||
|
||||
def _(t: tuple[int, str]) -> None:
|
||||
f(*t) # error: [invalid-argument-type]
|
||||
|
||||
def _(t: tuple[int, str, int]) -> None:
|
||||
f(*t)
|
||||
|
||||
def _(t: tuple[int, str] | tuple[int, str, int]) -> None:
|
||||
# TODO: error: [invalid-argument-type]
|
||||
f(*t)
|
||||
```
|
||||
|
||||
## Wrong argument type
|
||||
|
||||
### Positional argument, positional-or-keyword parameter
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Positional argument, positional-only parameter
|
||||
|
||||
```py
|
||||
def f(x: int, /) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Positional argument, variadic parameter
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, positional-or-keyword parameter
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, keyword-only parameter
|
||||
|
||||
```py
|
||||
def f(*, x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Keyword argument, keywords parameter
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
|
||||
reveal_type(f(x="foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Correctly match keyword out-of-order
|
||||
|
||||
```py
|
||||
def f(x: int = 1, y: str = "foo") -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[2]`"
|
||||
# error: 20 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["bar"]`"
|
||||
reveal_type(f(y=2, x="bar")) # revealed: int
|
||||
```
|
||||
|
||||
## Too many positional arguments
|
||||
|
||||
### One too many
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 1"
|
||||
reveal_type(f("foo")) # revealed: int
|
||||
```
|
||||
|
||||
### Two too many
|
||||
|
||||
```py
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 2"
|
||||
reveal_type(f("foo", "bar")) # revealed: int
|
||||
```
|
||||
|
||||
### No too-many-positional if variadic is taken
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f(1, 2, 3)) # revealed: int
|
||||
```
|
||||
|
||||
### Multiple keyword arguments map to keyword variadic parameter
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f(foo=1, bar=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Missing arguments
|
||||
|
||||
### No defaults or variadic
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### With default
|
||||
|
||||
```py
|
||||
def f(x: int, y: str = "foo") -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Defaulted argument is not required
|
||||
|
||||
```py
|
||||
def f(x: int = 1) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### With variadic
|
||||
|
||||
```py
|
||||
def f(x: int, *y: str) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Variadic argument is not required
|
||||
|
||||
```py
|
||||
def f(*args: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Keywords argument is not required
|
||||
|
||||
```py
|
||||
def f(**kwargs: int) -> int:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
```py
|
||||
def f(x: int, y: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 13 [missing-argument] "No arguments provided for required parameters `x`, `y` of function `f`"
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Unknown argument
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 20 [unknown-argument] "Argument `y` does not match any known parameter of function `f`"
|
||||
reveal_type(f(x=1, y=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Parameter already assigned
|
||||
|
||||
```py
|
||||
def f(x: int) -> int:
|
||||
return 1
|
||||
|
||||
# error: 18 [parameter-already-assigned] "Multiple values provided for parameter `x` of function `f`"
|
||||
reveal_type(f(1, x=2)) # revealed: int
|
||||
```
|
||||
|
||||
## Special functions
|
||||
|
||||
Some functions require special handling in type inference. Here, we make sure that we still emit
|
||||
proper diagnostics in case of missing or superfluous arguments.
|
||||
|
||||
### `reveal_type`
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
|
||||
reveal_type()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
|
||||
reveal_type(1, 2)
|
||||
```
|
||||
|
||||
### `static_assert`
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert
|
||||
|
||||
# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
|
||||
static_assert()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"
|
||||
static_assert(True, 2, 3)
|
||||
```
|
||||
|
||||
### `len`
|
||||
|
||||
```py
|
||||
# error: [missing-argument] "No argument provided for required parameter `obj` of function `len`"
|
||||
len()
|
||||
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments to function `len`: expected 1, got 2"
|
||||
len([], 1)
|
||||
```
|
||||
|
||||
### Type property predicates
|
||||
|
||||
```py
|
||||
from ty_extensions import is_subtype_of
|
||||
|
||||
# error: [missing-argument]
|
||||
is_subtype_of()
|
||||
|
||||
# error: [missing-argument]
|
||||
is_subtype_of(int)
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
is_subtype_of(int, int, int)
|
||||
|
||||
# error: [too-many-positional-arguments]
|
||||
is_subtype_of(int, int, int, int)
|
||||
```
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
# `replace`
|
||||
|
||||
The `replace` function and the `replace` protocol were added in Python 3.13:
|
||||
<https://docs.python.org/3/whatsnew/3.13.html#copy>
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
from copy import replace
|
||||
from datetime import time
|
||||
|
||||
t = time(12, 0, 0)
|
||||
t = replace(t, minute=30)
|
||||
|
||||
reveal_type(t) # revealed: time
|
||||
```
|
||||
|
||||
## The `__replace__` protocol
|
||||
|
||||
### Dataclasses
|
||||
|
||||
Dataclasses support the `__replace__` protocol:
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from copy import replace
|
||||
|
||||
@dataclass
|
||||
class Point:
|
||||
x: int
|
||||
y: int
|
||||
|
||||
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = int, y: int = int) -> Point
|
||||
```
|
||||
|
||||
The `__replace__` method can either be called directly or through the `replace` function:
|
||||
|
||||
```py
|
||||
a = Point(1, 2)
|
||||
|
||||
b = a.__replace__(x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
|
||||
b = replace(a, x=3, y=4)
|
||||
reveal_type(b) # revealed: Point
|
||||
```
|
||||
|
||||
A call to `replace` does not require all keyword arguments:
|
||||
|
||||
```py
|
||||
c = a.__replace__(y=4)
|
||||
reveal_type(c) # revealed: Point
|
||||
|
||||
d = replace(a, y=4)
|
||||
reveal_type(d) # revealed: Point
|
||||
```
|
||||
|
||||
Invalid calls to `__replace__` or `replace` will raise an error:
|
||||
|
||||
```py
|
||||
e = a.__replace__(x="wrong") # error: [invalid-argument-type]
|
||||
|
||||
# TODO: this should ideally also be emit an error
|
||||
e = replace(a, x="wrong")
|
||||
```
|
||||
@@ -128,7 +128,7 @@ class AsyncIterable:
|
||||
return AsyncIterator()
|
||||
|
||||
async def _():
|
||||
# revealed: int
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in AsyncIterable()]
|
||||
```
|
||||
|
||||
@@ -147,7 +147,6 @@ class Iterable:
|
||||
return Iterator()
|
||||
|
||||
async def _():
|
||||
# error: [not-iterable] "Object of type `Iterable` is not async-iterable"
|
||||
# revealed: Unknown
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in Iterable()]
|
||||
```
|
||||
|
||||
@@ -27,7 +27,6 @@ If all of the comprehensions are `async`, on the other hand, the code was still
|
||||
|
||||
```py
|
||||
async def test():
|
||||
# error: [not-iterable] "Object of type `range` is not async-iterable"
|
||||
return [[x async for x in elements(n)] async for n in range(3)]
|
||||
```
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# `yield` and `yield from`
|
||||
|
||||
## Basic `yield` and `yield from`
|
||||
|
||||
The type of a `yield` expression is the "send" type of the generator function. The type of a
|
||||
`yield from` expression is the return type of the inner generator:
|
||||
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
def inner_generator() -> Generator[int, bytes, str]:
|
||||
yield 1
|
||||
yield 2
|
||||
x = yield 3
|
||||
|
||||
# TODO: this should be `bytes`
|
||||
reveal_type(x) # revealed: @Todo(yield expressions)
|
||||
|
||||
return "done"
|
||||
|
||||
def outer_generator():
|
||||
result = yield from inner_generator()
|
||||
reveal_type(result) # revealed: str
|
||||
```
|
||||
|
||||
## `yield from` with a custom iterable
|
||||
|
||||
`yield from` can also be used with custom iterable types. In that case, the type of the `yield from`
|
||||
expression can not be determined
|
||||
|
||||
```py
|
||||
from typing import Generator, TypeVar, Generic
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class OnceIterator(Generic[T]):
|
||||
def __init__(self, value: T):
|
||||
self.value = value
|
||||
self.returned = False
|
||||
|
||||
def __next__(self) -> T:
|
||||
if self.returned:
|
||||
raise StopIteration(42)
|
||||
|
||||
self.returned = True
|
||||
return self.value
|
||||
|
||||
class Once(Generic[T]):
|
||||
def __init__(self, value: T):
|
||||
self.value = value
|
||||
|
||||
def __iter__(self) -> OnceIterator[T]:
|
||||
return OnceIterator(self.value)
|
||||
|
||||
for x in Once("a"):
|
||||
reveal_type(x) # revealed: str
|
||||
|
||||
def generator() -> Generator:
|
||||
result = yield from Once("a")
|
||||
|
||||
# At runtime, the value of `result` will be the `.value` attribute of the `StopIteration`
|
||||
# error raised by `OnceIterator` to signal to the interpreter that the iterator has been
|
||||
# exhausted. Here that will always be 42, but this information cannot be captured in the
|
||||
# signature of `OnceIterator.__next__`, since exceptions lie outside the type signature.
|
||||
# We therefore just infer `Unknown` here.
|
||||
#
|
||||
# If the `StopIteration` error in `OnceIterator.__next__` had been simply `raise StopIteration`
|
||||
# (the more common case), then the `.value` attribute of the `StopIteration` instance
|
||||
# would default to `None`.
|
||||
reveal_type(result) # revealed: Unknown
|
||||
```
|
||||
|
||||
## `yield from` with a generator that return `types.GeneratorType`
|
||||
|
||||
`types.GeneratorType` is a nominal type that implements the `typing.Generator` protocol:
|
||||
|
||||
```py
|
||||
from types import GeneratorType
|
||||
|
||||
def inner_generator() -> GeneratorType[int, bytes, str]:
|
||||
yield 1
|
||||
yield 2
|
||||
x = yield 3
|
||||
|
||||
# TODO: this should be `bytes`
|
||||
reveal_type(x) # revealed: @Todo(yield expressions)
|
||||
|
||||
return "done"
|
||||
|
||||
def outer_generator():
|
||||
result = yield from inner_generator()
|
||||
reveal_type(result) # revealed: str
|
||||
```
|
||||
|
||||
## Error cases
|
||||
|
||||
### Non-iterable type
|
||||
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
def generator() -> Generator:
|
||||
yield from 42 # error: [not-iterable] "Object of type `Literal[42]` is not iterable"
|
||||
```
|
||||
|
||||
### Invalid `yield` type
|
||||
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
# TODO: This should be an error. Claims to yield `int`, but yields `str`.
|
||||
def invalid_generator() -> Generator[int, None, None]:
|
||||
yield "not an int" # This should be an `int`
|
||||
```
|
||||
|
||||
### Invalid return type
|
||||
|
||||
```py
|
||||
from typing import Generator
|
||||
|
||||
# TODO: should emit an error (does not return `str`)
|
||||
def invalid_generator1() -> Generator[int, None, str]:
|
||||
yield 1
|
||||
|
||||
# TODO: should emit an error (does not return `int`)
|
||||
def invalid_generator2() -> Generator[int, None, None]:
|
||||
yield 1
|
||||
|
||||
return "done"
|
||||
```
|
||||
@@ -433,8 +433,6 @@ def f(cond: bool) -> str:
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
### Synchronous
|
||||
|
||||
A function with a `yield` or `yield from` expression anywhere in its body is a
|
||||
[generator function](https://docs.python.org/3/glossary.html#term-generator). A generator function
|
||||
implicitly returns an instance of `types.GeneratorType` even if it does not contain any `return`
|
||||
@@ -463,8 +461,6 @@ def j() -> str: # error: [invalid-return-type]
|
||||
yield 42
|
||||
```
|
||||
|
||||
### Asynchronous
|
||||
|
||||
If it is an `async` function with a `yield` statement in its body, it is an
|
||||
[asynchronous generator function](https://docs.python.org/3/glossary.html#term-asynchronous-generator).
|
||||
An asynchronous generator function implicitly returns an instance of `types.AsyncGeneratorType` even
|
||||
|
||||
@@ -145,34 +145,27 @@ T = TypeVar("T")
|
||||
def takes_mixed_tuple_suffix(x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||
return x[-2]
|
||||
|
||||
# TODO: revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_mixed_tuple_prefix(x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||
return x[1]
|
||||
|
||||
def _(x: tuple[int, bytes, *tuple[str, ...], bool, int]):
|
||||
reveal_type(takes_mixed_tuple_suffix(x)) # revealed: bool
|
||||
reveal_type(takes_mixed_tuple_prefix(x)) # revealed: bytes
|
||||
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Literal[b"foo"]
|
||||
# TODO: revealed: Literal[b"foo"]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_fixed_tuple(x: tuple[T, int]) -> T:
|
||||
return x[0]
|
||||
|
||||
def _(x: tuple[str, int]):
|
||||
reveal_type(takes_fixed_tuple(x)) # revealed: str
|
||||
|
||||
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||
|
||||
def takes_homogeneous_tuple(x: tuple[T, ...]) -> T:
|
||||
return x[0]
|
||||
|
||||
def _(x: tuple[str, int], y: tuple[bool, ...], z: tuple[int, str, *tuple[range, ...], bytes]):
|
||||
reveal_type(takes_homogeneous_tuple(x)) # revealed: str | int
|
||||
reveal_type(takes_homogeneous_tuple(y)) # revealed: bool
|
||||
reveal_type(takes_homogeneous_tuple(z)) # revealed: int | str | range | bytes
|
||||
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Literal[42, 43]
|
||||
# TODO: revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||
# TODO: revealed: Literal[42, 43]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
||||
@@ -131,34 +131,27 @@ reveal_type(takes_in_protocol(ExplicitGenericSub[str]())) # revealed: str
|
||||
def takes_mixed_tuple_suffix[T](x: tuple[int, bytes, *tuple[str, ...], T, int]) -> T:
|
||||
return x[-2]
|
||||
|
||||
# TODO: revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_mixed_tuple_prefix[T](x: tuple[int, T, *tuple[str, ...], bool, int]) -> T:
|
||||
return x[1]
|
||||
|
||||
def _(x: tuple[int, bytes, *tuple[str, ...], bool, int]):
|
||||
reveal_type(takes_mixed_tuple_suffix(x)) # revealed: bool
|
||||
reveal_type(takes_mixed_tuple_prefix(x)) # revealed: bytes
|
||||
|
||||
reveal_type(takes_mixed_tuple_suffix((1, b"foo", "bar", "baz", True, 42))) # revealed: Literal[True]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Literal[b"foo"]
|
||||
# TODO: revealed: Literal[b"foo"]
|
||||
reveal_type(takes_mixed_tuple_prefix((1, b"foo", "bar", "baz", True, 42))) # revealed: Unknown
|
||||
|
||||
def takes_fixed_tuple[T](x: tuple[T, int]) -> T:
|
||||
return x[0]
|
||||
|
||||
def _(x: tuple[str, int]):
|
||||
reveal_type(takes_fixed_tuple(x)) # revealed: str
|
||||
|
||||
reveal_type(takes_fixed_tuple((True, 42))) # revealed: Literal[True]
|
||||
|
||||
def takes_homogeneous_tuple[T](x: tuple[T, ...]) -> T:
|
||||
return x[0]
|
||||
|
||||
def _(x: tuple[str, int], y: tuple[bool, ...], z: tuple[int, str, *tuple[range, ...], bytes]):
|
||||
reveal_type(takes_homogeneous_tuple(x)) # revealed: str | int
|
||||
reveal_type(takes_homogeneous_tuple(y)) # revealed: bool
|
||||
reveal_type(takes_homogeneous_tuple(z)) # revealed: int | str | range | bytes
|
||||
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Literal[42, 43]
|
||||
# TODO: revealed: Literal[42]
|
||||
reveal_type(takes_homogeneous_tuple((42,))) # revealed: Unknown
|
||||
# TODO: revealed: Literal[42, 43]
|
||||
reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Inferring a bound typevar
|
||||
|
||||
@@ -1,65 +1,60 @@
|
||||
# List all members
|
||||
|
||||
This test suite acts as a set of unit tests for our `ide_support::all_members` routine, which lists
|
||||
all members available on a given type. This routine is used for autocomplete suggestions.
|
||||
|
||||
## Basic functionality
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
The `ty_extensions.all_members` and `ty_extensions.has_member` functions expose a Python-level API
|
||||
that can be used to query which attributes `ide_support::all_members` understands as being available
|
||||
on a given object. For example, all member functions of `str` are available on `"a"`. The Python API
|
||||
`all_members` returns a tuple of all available members; `has_member` returns `Literal[True]` if a
|
||||
given member is present in that tuple, and `Literal[False]` if not:
|
||||
The `ty_extensions.all_members` function allows access to a tuple of accessible members/attributes
|
||||
on a given object. For example, all member functions of `str` are available on `"a"`:
|
||||
|
||||
```py
|
||||
from ty_extensions import static_assert, has_member
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
static_assert(has_member("a", "replace"))
|
||||
static_assert(has_member("a", "startswith"))
|
||||
static_assert(has_member("a", "isupper"))
|
||||
members_of_str = all_members("a")
|
||||
|
||||
static_assert("replace" in members_of_str)
|
||||
static_assert("startswith" in members_of_str)
|
||||
static_assert("isupper" in members_of_str)
|
||||
```
|
||||
|
||||
Similarly, special members such as `__add__` are also available:
|
||||
|
||||
```py
|
||||
static_assert(has_member("a", "__add__"))
|
||||
static_assert(has_member("a", "__gt__"))
|
||||
static_assert("__add__" in members_of_str)
|
||||
static_assert("__gt__" in members_of_str)
|
||||
```
|
||||
|
||||
Members of base classes are also included (these dunder methods are defined on `object`):
|
||||
|
||||
```py
|
||||
static_assert(has_member("a", "__doc__"))
|
||||
static_assert(has_member("a", "__repr__"))
|
||||
static_assert("__doc__" in members_of_str)
|
||||
static_assert("__repr__" in members_of_str)
|
||||
```
|
||||
|
||||
Non-existent members are not included:
|
||||
|
||||
```py
|
||||
static_assert(not has_member("a", "non_existent"))
|
||||
static_assert("non_existent" not in members_of_str)
|
||||
```
|
||||
|
||||
The full list of all members is relatively long, but `reveal_type` can be used in combination with
|
||||
`all_members` to see them all:
|
||||
Note: The full list of all members is relatively long, but `reveal_type` can theoretically be used
|
||||
to see them all:
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
from ty_extensions import all_members
|
||||
|
||||
reveal_type(all_members("a")) # error: [revealed-type]
|
||||
reveal_type(members_of_str) # error: [revealed-type]
|
||||
```
|
||||
|
||||
## Kinds of types
|
||||
|
||||
### Class instances
|
||||
|
||||
For instances of classes, class members and implicit instance members of all superclasses are
|
||||
understood as being available:
|
||||
For instances of classes, `all_members` returns class members and implicit instance members of all
|
||||
classes in the MRO:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class Base:
|
||||
base_class_attr: int = 1
|
||||
@@ -91,23 +86,25 @@ class C(Intermediate):
|
||||
def static_method() -> int:
|
||||
return 1
|
||||
|
||||
static_assert(has_member(C(), "base_class_attr"))
|
||||
static_assert(has_member(C(), "intermediate_attr"))
|
||||
static_assert(has_member(C(), "class_attr"))
|
||||
members_of_instance = all_members(C())
|
||||
|
||||
static_assert(has_member(C(), "base_instance_attr"))
|
||||
static_assert(has_member(C(), "intermediate_instance_attr"))
|
||||
static_assert(has_member(C(), "instance_attr"))
|
||||
static_assert("base_class_attr" in members_of_instance)
|
||||
static_assert("intermediate_attr" in members_of_instance)
|
||||
static_assert("class_attr" in members_of_instance)
|
||||
|
||||
static_assert(has_member(C(), "f_base"))
|
||||
static_assert(has_member(C(), "f_intermediate"))
|
||||
static_assert(has_member(C(), "f_c"))
|
||||
static_assert("base_instance_attr" in members_of_instance)
|
||||
static_assert("intermediate_instance_attr" in members_of_instance)
|
||||
static_assert("instance_attr" in members_of_instance)
|
||||
|
||||
static_assert(has_member(C(), "property_attr"))
|
||||
static_assert(has_member(C(), "class_method"))
|
||||
static_assert(has_member(C(), "static_method"))
|
||||
static_assert("f_base" in members_of_instance)
|
||||
static_assert("f_intermediate" in members_of_instance)
|
||||
static_assert("f_c" in members_of_instance)
|
||||
|
||||
static_assert(not has_member(C(), "non_existent"))
|
||||
static_assert("property_attr" in members_of_instance)
|
||||
static_assert("class_method" in members_of_instance)
|
||||
static_assert("static_method" in members_of_instance)
|
||||
|
||||
static_assert("non_existent" not in members_of_instance)
|
||||
```
|
||||
|
||||
### Class objects
|
||||
@@ -115,7 +112,7 @@ static_assert(not has_member(C(), "non_existent"))
|
||||
Class-level attributes can also be accessed through the class itself:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class Base:
|
||||
base_attr: int = 1
|
||||
@@ -126,16 +123,18 @@ class C(Base):
|
||||
def f(self):
|
||||
self.instance_attr = True
|
||||
|
||||
static_assert(has_member(C, "class_attr"))
|
||||
static_assert(has_member(C, "base_attr"))
|
||||
members_of_class = all_members(C)
|
||||
|
||||
static_assert(not has_member(C, "non_existent"))
|
||||
static_assert("class_attr" in members_of_class)
|
||||
static_assert("base_attr" in members_of_class)
|
||||
|
||||
static_assert("non_existent" not in members_of_class)
|
||||
```
|
||||
|
||||
But instance attributes can not be accessed this way:
|
||||
|
||||
```py
|
||||
static_assert(not has_member(C, "instance_attr"))
|
||||
static_assert("instance_attr" not in members_of_class)
|
||||
```
|
||||
|
||||
When a class has a metaclass, members of that metaclass (and bases of that metaclass) are also
|
||||
@@ -151,16 +150,16 @@ class Meta(MetaBase):
|
||||
class D(Base, metaclass=Meta):
|
||||
class_attr = 3
|
||||
|
||||
static_assert(has_member(D, "meta_base_attr"))
|
||||
static_assert(has_member(D, "meta_attr"))
|
||||
static_assert(has_member(D, "base_attr"))
|
||||
static_assert(has_member(D, "class_attr"))
|
||||
static_assert("meta_base_attr" in all_members(D))
|
||||
static_assert("meta_attr" in all_members(D))
|
||||
static_assert("base_attr" in all_members(D))
|
||||
static_assert("class_attr" in all_members(D))
|
||||
```
|
||||
|
||||
### Generic classes
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
@@ -168,53 +167,52 @@ T = TypeVar("T")
|
||||
class C(Generic[T]):
|
||||
base_attr: T
|
||||
|
||||
static_assert(has_member(C[int], "base_attr"))
|
||||
static_assert(has_member(C[int](), "base_attr"))
|
||||
static_assert("base_attr" in all_members(C[int]))
|
||||
static_assert("base_attr" in all_members(C[int]()))
|
||||
```
|
||||
|
||||
### Other instance-like types
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
from typing_extensions import LiteralString
|
||||
|
||||
static_assert(has_member(True, "__xor__"))
|
||||
static_assert(has_member(1, "bit_length"))
|
||||
static_assert(has_member("a", "startswith"))
|
||||
static_assert(has_member(b"a", "__buffer__"))
|
||||
static_assert(has_member(3.14, "is_integer"))
|
||||
static_assert("__xor__" in all_members(True))
|
||||
static_assert("bit_length" in all_members(1))
|
||||
static_assert("startswith" in all_members("a"))
|
||||
static_assert("__buffer__" in all_members(b"a"))
|
||||
static_assert("is_integer" in all_members(3.14))
|
||||
|
||||
def _(literal_string: LiteralString):
|
||||
static_assert(has_member(literal_string, "startswith"))
|
||||
static_assert("startswith" in all_members(literal_string))
|
||||
|
||||
static_assert(has_member(("some", "tuple", 1, 2), "count"))
|
||||
static_assert("count" in all_members(("some", "tuple", 1, 2)))
|
||||
|
||||
static_assert(has_member(len, "__doc__"))
|
||||
static_assert(has_member("a".startswith, "__doc__"))
|
||||
static_assert("__doc__" in all_members(len))
|
||||
static_assert("__doc__" in all_members("a".startswith))
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
from enum import Enum
|
||||
|
||||
class Answer(Enum):
|
||||
NO = 0
|
||||
YES = 1
|
||||
|
||||
static_assert(has_member(Answer, "NO"))
|
||||
static_assert(has_member(Answer, "YES"))
|
||||
static_assert(has_member(Answer, "__members__"))
|
||||
static_assert("NO" in all_members(Answer))
|
||||
static_assert("YES" in all_members(Answer))
|
||||
static_assert("__members__" in all_members(Answer))
|
||||
```
|
||||
|
||||
### Unions
|
||||
|
||||
For unions, `ide_support::all_members` only returns members that are available on all elements of
|
||||
the union.
|
||||
For unions, `all_members` will only return members that are available on all elements of the union.
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class A:
|
||||
on_both: int = 1
|
||||
@@ -225,20 +223,20 @@ class B:
|
||||
only_on_b: str = "b"
|
||||
|
||||
def f(union: A | B):
|
||||
static_assert(has_member(union, "on_both"))
|
||||
static_assert(not has_member(union, "only_on_a"))
|
||||
static_assert(not has_member(union, "only_on_b"))
|
||||
static_assert("on_both" in all_members(union))
|
||||
static_assert("only_on_a" not in all_members(union))
|
||||
static_assert("only_on_b" not in all_members(union))
|
||||
```
|
||||
|
||||
### Intersections
|
||||
|
||||
#### Only positive types
|
||||
|
||||
Conversely, for intersections, `ide_support::all_members` lists members that are available on any of
|
||||
the elements:
|
||||
Conversely, for intersections, `all_members` will list members that are available on any of the
|
||||
elements:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class A:
|
||||
on_both: int = 1
|
||||
@@ -251,9 +249,9 @@ class B:
|
||||
def f(intersection: object):
|
||||
if isinstance(intersection, A):
|
||||
if isinstance(intersection, B):
|
||||
static_assert(has_member(intersection, "on_both"))
|
||||
static_assert(has_member(intersection, "only_on_a"))
|
||||
static_assert(has_member(intersection, "only_on_b"))
|
||||
static_assert("on_both" in all_members(intersection))
|
||||
static_assert("only_on_a" in all_members(intersection))
|
||||
static_assert("only_on_b" in all_members(intersection))
|
||||
```
|
||||
|
||||
#### With negative types
|
||||
@@ -261,7 +259,7 @@ def f(intersection: object):
|
||||
It also works when negative types are introduced:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class A:
|
||||
on_all: int = 1
|
||||
@@ -286,27 +284,27 @@ def f(intersection: object):
|
||||
if isinstance(intersection, B):
|
||||
if not isinstance(intersection, C):
|
||||
reveal_type(intersection) # revealed: A & B & ~C
|
||||
static_assert(has_member(intersection, "on_all"))
|
||||
static_assert(has_member(intersection, "only_on_a"))
|
||||
static_assert(has_member(intersection, "only_on_b"))
|
||||
static_assert(not has_member(intersection, "only_on_c"))
|
||||
static_assert(has_member(intersection, "only_on_ab"))
|
||||
static_assert(has_member(intersection, "only_on_ac"))
|
||||
static_assert(has_member(intersection, "only_on_bc"))
|
||||
static_assert("on_all" in all_members(intersection))
|
||||
static_assert("only_on_a" in all_members(intersection))
|
||||
static_assert("only_on_b" in all_members(intersection))
|
||||
static_assert("only_on_c" not in all_members(intersection))
|
||||
static_assert("only_on_ab" in all_members(intersection))
|
||||
static_assert("only_on_ac" in all_members(intersection))
|
||||
static_assert("only_on_bc" in all_members(intersection))
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
### Basic support with sub-modules
|
||||
|
||||
`ide_support::all_members` can also list attributes on modules:
|
||||
`all_members` can also list attributes on modules:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
import math
|
||||
|
||||
static_assert(has_member(math, "pi"))
|
||||
static_assert(has_member(math, "cos"))
|
||||
static_assert("pi" in all_members(math))
|
||||
static_assert("cos" in all_members(math))
|
||||
```
|
||||
|
||||
This also works for submodules:
|
||||
@@ -314,18 +312,18 @@ This also works for submodules:
|
||||
```py
|
||||
import os
|
||||
|
||||
static_assert(has_member(os, "path"))
|
||||
static_assert("path" in all_members(os))
|
||||
|
||||
import os.path
|
||||
|
||||
static_assert(has_member(os.path, "join"))
|
||||
static_assert("join" in all_members(os.path))
|
||||
```
|
||||
|
||||
Special members available on all modules are also included:
|
||||
|
||||
```py
|
||||
static_assert(has_member(math, "__name__"))
|
||||
static_assert(has_member(math, "__doc__"))
|
||||
static_assert("__name__" in all_members(math))
|
||||
static_assert("__doc__" in all_members(math))
|
||||
```
|
||||
|
||||
### `__all__` is not respected for direct module access
|
||||
@@ -333,12 +331,12 @@ static_assert(has_member(math, "__doc__"))
|
||||
`foo.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
import bar
|
||||
|
||||
static_assert(has_member(bar, "lion"))
|
||||
static_assert(has_member(bar, "tiger"))
|
||||
static_assert("lion" in all_members(bar))
|
||||
static_assert("tiger" in all_members(bar))
|
||||
```
|
||||
|
||||
`bar.py`:
|
||||
@@ -350,17 +348,17 @@ lion = 1
|
||||
tiger = 1
|
||||
```
|
||||
|
||||
### `__all__` is respected for `*` imports
|
||||
### `__all__` is respected for glob imports
|
||||
|
||||
`foo.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
import bar
|
||||
|
||||
static_assert(has_member(bar, "lion"))
|
||||
static_assert(not has_member(bar, "tiger"))
|
||||
static_assert("lion" in all_members(bar))
|
||||
static_assert("tiger" not in all_members(bar))
|
||||
```
|
||||
|
||||
`bar.py`:
|
||||
@@ -402,12 +400,12 @@ def evaluate(x: Optional[int] = None) -> int: ...
|
||||
`play.py`:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
import module
|
||||
|
||||
static_assert(has_member(module, "evaluate"))
|
||||
static_assert(not has_member(module, "Optional"))
|
||||
static_assert("evaluate" in all_members(module))
|
||||
static_assert("Optional" not in all_members(module))
|
||||
```
|
||||
|
||||
## Conditionally available members
|
||||
@@ -423,9 +421,9 @@ python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
static_assert(not has_member(42, "bit_count"))
|
||||
static_assert("bit_count" not in all_members(42))
|
||||
```
|
||||
|
||||
### 3.10
|
||||
@@ -436,19 +434,19 @@ python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
static_assert(has_member(42, "bit_count"))
|
||||
static_assert("bit_count" in all_members(42))
|
||||
```
|
||||
|
||||
## Failure cases
|
||||
## Failures cases
|
||||
|
||||
### Dynamically added members
|
||||
|
||||
Dynamically added members cannot be accessed:
|
||||
Dynamically added members can not be accessed:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
class C:
|
||||
static_attr = 1
|
||||
@@ -462,8 +460,8 @@ class C:
|
||||
c = C()
|
||||
c.dynamic_attr = "a"
|
||||
|
||||
static_assert(has_member(c, "static_attr"))
|
||||
static_assert(not has_member(c, "dynamic_attr"))
|
||||
static_assert("static_attr" in all_members(c))
|
||||
static_assert("dynamic_attr" not in all_members(c))
|
||||
```
|
||||
|
||||
### Dataclasses
|
||||
@@ -471,24 +469,24 @@ static_assert(not has_member(c, "dynamic_attr"))
|
||||
So far, we do not include synthetic members of dataclasses.
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass(order=True)
|
||||
class Person:
|
||||
age: int
|
||||
name: str
|
||||
age: int
|
||||
|
||||
static_assert(has_member(Person, "name"))
|
||||
static_assert(has_member(Person, "age"))
|
||||
static_assert("name" in all_members(Person))
|
||||
static_assert("age" in all_members(Person))
|
||||
|
||||
# These are always available, since they are also defined on `object`:
|
||||
static_assert(has_member(Person, "__init__"))
|
||||
static_assert(has_member(Person, "__repr__"))
|
||||
static_assert(has_member(Person, "__eq__"))
|
||||
static_assert("__init__" in all_members(Person))
|
||||
static_assert("__repr__" in all_members(Person))
|
||||
static_assert("__eq__" in all_members(Person))
|
||||
|
||||
# TODO: this should ideally be available:
|
||||
static_assert(has_member(Person, "__lt__")) # error: [static-assert-error]
|
||||
static_assert("__lt__" in all_members(Person)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
### Attributes not available at runtime
|
||||
@@ -498,8 +496,8 @@ example, `__annotations__` does not exist on `int` at runtime, but it is availab
|
||||
on `object` in typeshed:
|
||||
|
||||
```py
|
||||
from ty_extensions import has_member, static_assert
|
||||
from ty_extensions import all_members, static_assert
|
||||
|
||||
# TODO: this should ideally not be available:
|
||||
static_assert(not has_member(3, "__annotations__")) # error: [static-assert-error]
|
||||
static_assert("__annotations__" not in all_members(3)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
@@ -122,14 +122,15 @@ class A:
|
||||
__slots__ = ()
|
||||
__slots__ += ("a", "b")
|
||||
|
||||
reveal_type(A.__slots__) # revealed: tuple[Literal["a", "b"], ...]
|
||||
reveal_type(A.__slots__) # revealed: tuple[Literal["a"], Literal["b"]]
|
||||
|
||||
class B:
|
||||
__slots__ = ("c", "d")
|
||||
|
||||
# TODO: ideally this would trigger `[instance-layout-conflict]`
|
||||
# (but it's also not high-priority)
|
||||
class C(A, B): ...
|
||||
class C( # error: [instance-layout-conflict]
|
||||
A,
|
||||
B,
|
||||
): ...
|
||||
```
|
||||
|
||||
## Explicitly annotated `__slots__`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user