Compare commits
4 Commits
david/data
...
gankra/got
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97af9d8466 | ||
|
|
5cc6762c23 | ||
|
|
1bee527cb2 | ||
|
|
d7b7b835e1 |
50
.github/workflows/ci.yaml
vendored
50
.github/workflows/ci.yaml
vendored
@@ -214,7 +214,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: |
|
||||
rustup component add clippy
|
||||
@@ -234,17 +234,17 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
@@ -292,17 +292,17 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -321,11 +321,11 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -348,7 +348,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
@@ -377,7 +377,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -400,7 +400,7 @@ jobs:
|
||||
with:
|
||||
file: "Cargo.toml"
|
||||
field: "workspace.package.rust-version"
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
env:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
@@ -408,11 +408,11 @@ jobs:
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -432,7 +432,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "fuzz -> target"
|
||||
- name: "Install Rust toolchain"
|
||||
@@ -494,7 +494,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup component add rustfmt
|
||||
# Run all code generation scripts, and verify that the current output is
|
||||
@@ -708,7 +708,7 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
architecture: x64
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Prep README.md"
|
||||
run: python scripts/transform_readme.py --target pypi
|
||||
- name: "Build wheels"
|
||||
@@ -732,7 +732,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
@@ -765,7 +765,7 @@ jobs:
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Add SSH key"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
|
||||
@@ -804,7 +804,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Run checks"
|
||||
@@ -874,7 +874,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
@@ -905,14 +905,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
@@ -938,14 +938,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
|
||||
uses: taiki-e/install-action@d12e869b89167df346dd0ff65da342d1fb1202fb # v2.53.2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||
|
||||
82
.github/workflows/mypy_primer.yaml
vendored
82
.github/workflows/mypy_primer.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
@@ -49,12 +49,46 @@ jobs:
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
DIFF_FILE: mypy_primer.diff
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
echo ${{ github.event.number }} > ../pr-number
|
||||
|
||||
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
|
||||
mkdir -p ~/.config/ty
|
||||
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
|
||||
|
||||
echo "new commit"
|
||||
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||
|
||||
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
|
||||
git checkout -b base_commit "$MERGE_BASE"
|
||||
echo "base commit"
|
||||
git rev-list --format=%s --max-count=1 base_commit
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx \
|
||||
--from="git+https://github.com/hauntsaninja/mypy_primer@e5f55447969d33ae3c7ccdb183e2a37101867270" \
|
||||
mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker ty \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||
--output concise \
|
||||
--debug > mypy_primer.diff || [ $? -eq 1 ]
|
||||
|
||||
# Output diff with ANSI color codes
|
||||
cat mypy_primer.diff
|
||||
|
||||
# Remove ANSI color codes before uploading
|
||||
sed -ie 's/\x1b\[[0-9;]*m//g' mypy_primer.diff
|
||||
|
||||
echo ${{ github.event.number }} > pr-number
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
@@ -67,41 +101,3 @@ jobs:
|
||||
with:
|
||||
name: pr-number
|
||||
path: pr-number
|
||||
|
||||
memory_usage:
|
||||
name: Run memory statistics
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
env:
|
||||
TY_MAX_PARALLELISM: 1 # for deterministic memory numbers
|
||||
TY_MEMORY_REPORT: mypy_primer
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/memory.txt
|
||||
DIFF_FILE: mypy_primer_memory.diff
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
|
||||
- name: Upload diff
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
path: mypy_primer_memory.diff
|
||||
|
||||
31
.github/workflows/mypy_primer_comment.yaml
vendored
31
.github/workflows/mypy_primer_comment.yaml
vendored
@@ -45,28 +45,15 @@ jobs:
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
|
||||
name: "Download mypy_primer memory results"
|
||||
id: download-mypy_primer_memory_diff
|
||||
if: steps.pr-number.outputs.pr-number
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
workflow: mypy_primer.yaml
|
||||
pr: ${{ steps.pr-number.outputs.pr-number }}
|
||||
path: pr/mypy_primer_memory_diff
|
||||
workflow_conclusion: completed
|
||||
if_no_artifact_found: ignore
|
||||
allow_forks: true
|
||||
|
||||
- name: Generate comment content
|
||||
id: generate-comment
|
||||
if: ${{ steps.download-mypy_primer_diff.outputs.found_artifact == 'true' && steps.download-mypy_primer_memory_diff.outputs.found_artifact == 'true' }}
|
||||
if: steps.download-mypy_primer_diff.outputs.found_artifact == 'true'
|
||||
run: |
|
||||
# Guard against malicious mypy_primer results that symlink to a secret
|
||||
# file on this runner
|
||||
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]] || [[ -L pr/mypy_primer_memory_diff/mypy_primer_memory.diff ]]
|
||||
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]]
|
||||
then
|
||||
echo "Error: mypy_primer.diff and mypy_primer_memory.diff cannot be a symlink"
|
||||
echo "Error: mypy_primer.diff cannot be a symlink"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -87,18 +74,6 @@ jobs:
|
||||
echo 'No ecosystem changes detected ✅' >> comment.txt
|
||||
fi
|
||||
|
||||
if [ -s "pr/mypy_primer_memory_diff/mypy_primer_memory.diff" ]; then
|
||||
echo '<details>' >> comment.txt
|
||||
echo '<summary>Memory usage changes were detected when running on open source projects</summary>' >> comment.txt
|
||||
echo '' >> comment.txt
|
||||
echo '```diff' >> comment.txt
|
||||
cat pr/mypy_primer_memory_diff/mypy_primer_memory.diff >> comment.txt
|
||||
echo '```' >> comment.txt
|
||||
echo '</details>' >> comment.txt
|
||||
else
|
||||
echo 'No memory usage changes detected ✅' >> comment.txt
|
||||
fi
|
||||
|
||||
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
|
||||
cat comment.txt >> "$GITHUB_OUTPUT"
|
||||
echo 'EOF' >> "$GITHUB_OUTPUT"
|
||||
|
||||
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ repos:
|
||||
- black==25.1.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.34.0
|
||||
rev: v1.33.1
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -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.2
|
||||
rev: v0.12.1
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -99,12 +99,12 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.11.0
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.33.2
|
||||
rev: 0.33.1
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
|
||||
|
||||
171
Cargo.lock
generated
171
Cargo.lock
generated
@@ -591,7 +591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -600,7 +600,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -629,24 +629,12 @@ name = "console"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"unicode-width 0.2.1",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1031,7 +1019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1493,14 +1481,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.18.0"
|
||||
version = "0.17.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd"
|
||||
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||
dependencies = [
|
||||
"console 0.16.0",
|
||||
"console",
|
||||
"number_prefix",
|
||||
"portable-atomic",
|
||||
"unicode-width 0.2.1",
|
||||
"unit-prefix",
|
||||
"vt100",
|
||||
"web-time",
|
||||
]
|
||||
@@ -1537,7 +1525,7 @@ version = "1.43.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"console",
|
||||
"globset",
|
||||
"once_cell",
|
||||
"pest",
|
||||
@@ -1604,7 +1592,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.1",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1668,7 +1656,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2037,11 +2025,12 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.1.0"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97"
|
||||
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
@@ -2050,7 +2039,7 @@ dependencies = [
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2088,6 +2077,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -2171,7 +2166,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3412,7 +3407,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3608,9 +3603,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.14.0"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
|
||||
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -3619,9 +3614,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.14.0"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
||||
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
@@ -3802,7 +3797,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4061,9 +4056,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.34"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
@@ -4082,9 +4077,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-indicatif"
|
||||
version = "0.3.11"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200"
|
||||
checksum = "8201ca430e0cd893ef978226fd3516c06d9c494181c8bf4e5b32e30ed4b40aa1"
|
||||
dependencies = [
|
||||
"indicatif",
|
||||
"tracing",
|
||||
@@ -4172,7 +4167,6 @@ dependencies = [
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"insta",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
@@ -4479,12 +4473,6 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unit-prefix"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817"
|
||||
|
||||
[[package]]
|
||||
name = "unscanny"
|
||||
version = "0.1.0"
|
||||
@@ -4817,7 +4805,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4891,7 +4879,7 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4900,16 +4888,7 @@ version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4918,30 +4897,14 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.53.0",
|
||||
"windows_aarch64_msvc 0.53.0",
|
||||
"windows_i686_gnu 0.53.0",
|
||||
"windows_i686_gnullvm 0.53.0",
|
||||
"windows_i686_msvc 0.53.0",
|
||||
"windows_x86_64_gnu 0.53.0",
|
||||
"windows_x86_64_gnullvm 0.53.0",
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4950,96 +4913,48 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.10"
|
||||
|
||||
@@ -98,7 +98,7 @@ ignore = { version = "0.4.22" }
|
||||
imara-diff = { version = "0.1.5" }
|
||||
imperative = { version = "1.0.4" }
|
||||
indexmap = { version = "2.6.0" }
|
||||
indicatif = { version = "0.18.0" }
|
||||
indicatif = { version = "0.17.8" }
|
||||
indoc = { version = "2.0.4" }
|
||||
insta = { version = "1.35.1" }
|
||||
insta-cmd = { version = "0.6.0" }
|
||||
@@ -167,7 +167,7 @@ tikv-jemallocator = { version = "0.6.0" }
|
||||
toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
tracing-flame = { version = "0.2.0" }
|
||||
tracing-indicatif = { version = "0.3.11" }
|
||||
tracing-indicatif = { version = "0.3.6" }
|
||||
tracing-log = { version = "0.2.0" }
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
|
||||
"env-filter",
|
||||
|
||||
@@ -506,7 +506,6 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Streamlit](https://github.com/streamlit/streamlit)
|
||||
- [The Algorithms](https://github.com/TheAlgorithms/Python)
|
||||
- [Vega-Altair](https://github.com/altair-viz/altair)
|
||||
- [Weblate](https://weblate.org/)
|
||||
- WordPress ([Openverse](https://github.com/WordPress/openverse))
|
||||
- [ZenML](https://github.com/zenml-io/zenml)
|
||||
- [Zulip](https://github.com/zulip/zulip)
|
||||
|
||||
@@ -498,8 +498,11 @@ fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
|
||||
let diagnostics = result.len();
|
||||
|
||||
assert!(
|
||||
diagnostics <= max_diagnostics,
|
||||
"Expected <={max_diagnostics} diagnostics but got {diagnostics}"
|
||||
diagnostics > 1 && diagnostics <= max_diagnostics,
|
||||
"Expected between {} and {} diagnostics but got {}",
|
||||
1,
|
||||
max_diagnostics,
|
||||
diagnostics
|
||||
);
|
||||
}
|
||||
|
||||
@@ -567,23 +570,6 @@ fn anyio(criterion: &mut Criterion) {
|
||||
bench_project(&benchmark, criterion);
|
||||
}
|
||||
|
||||
fn datetype(criterion: &mut Criterion) {
|
||||
let benchmark = ProjectBenchmark::new(
|
||||
RealWorldProject {
|
||||
name: "DateType",
|
||||
repository: "https://github.com/glyph/DateType",
|
||||
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
|
||||
paths: vec![SystemPath::new("src")],
|
||||
dependencies: vec![],
|
||||
max_dep_date: "2025-07-04",
|
||||
python_version: PythonVersion::PY313,
|
||||
},
|
||||
0,
|
||||
);
|
||||
|
||||
bench_project(&benchmark, criterion);
|
||||
}
|
||||
|
||||
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
|
||||
criterion_group!(
|
||||
micro,
|
||||
@@ -592,5 +578,5 @@ criterion_group!(
|
||||
benchmark_complex_constrained_attributes_1,
|
||||
benchmark_complex_constrained_attributes_2,
|
||||
);
|
||||
criterion_group!(project, anyio, attrs, hydra, datetype);
|
||||
criterion_group!(project, anyio, attrs, hydra);
|
||||
criterion_main!(check_file, micro, project);
|
||||
|
||||
@@ -242,19 +242,20 @@ fn large(bencher: Bencher, benchmark: &Benchmark) {
|
||||
run_single_threaded(bencher, benchmark);
|
||||
}
|
||||
|
||||
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
|
||||
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
// Currently disabled because the benchmark is too noisy (± 10%) to give useful feedback.
|
||||
// #[bench(args=[&*PYDANTIC], sample_size=3, sample_count=3)]
|
||||
// fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
|
||||
// let thread_pool = ThreadPoolBuilder::new().build().unwrap();
|
||||
|
||||
bencher
|
||||
.with_inputs(|| benchmark.setup_iteration())
|
||||
.bench_local_values(|db| {
|
||||
thread_pool.install(|| {
|
||||
check_project(&db, benchmark.max_diagnostics);
|
||||
db
|
||||
})
|
||||
});
|
||||
}
|
||||
// bencher
|
||||
// .with_inputs(|| benchmark.setup_iteration())
|
||||
// .bench_local_values(|db| {
|
||||
// thread_pool.install(|| {
|
||||
// check_project(&db, benchmark.max_diagnostics);
|
||||
// db
|
||||
// })
|
||||
// });
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
ThreadPoolBuilder::new()
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
for _ in []:
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception,):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (Exception,):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception, ValueError):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (Exception, ValueError):
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
continue
|
||||
try:
|
||||
pass
|
||||
except (ValueError,):
|
||||
continue
|
||||
|
||||
@@ -185,45 +185,38 @@ for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Shouldn't trigger the warning when there is a return statement.
|
||||
def foo():
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
return section_items
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
return section_items
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return statement after.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
|
||||
# Should trigger the warning for duplicate access, even if is a return in another branch.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items)
|
||||
|
||||
# Should trigger, since only one branch has a return statement.
|
||||
def foo():
|
||||
from itertools import groupby
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
for _section, section_items in groupby(items, key=lambda p: p[1]):
|
||||
if _section == "greens":
|
||||
collect_shop_items(shopper, section_items)
|
||||
return
|
||||
elif _section == "frozen items":
|
||||
collect_shop_items(shopper, section_items)
|
||||
collect_shop_items(shopper, section_items) # B031
|
||||
|
||||
# Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
# NOTE: This should always be at the end of the file.
|
||||
|
||||
@@ -26,9 +26,8 @@ abc(**{'a': b}, **{'a': c}) # PIE804
|
||||
abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||
|
||||
# Some values need to be parenthesized.
|
||||
def foo():
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/18036
|
||||
# The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
|
||||
@@ -27,9 +27,8 @@ with contextlib.ExitStack() as stack:
|
||||
close_files = stack.pop_all().close
|
||||
|
||||
# OK
|
||||
async def foo():
|
||||
with contextlib.AsyncExitStack() as exit_stack:
|
||||
f = await exit_stack.enter_async_context(open("filename"))
|
||||
with contextlib.AsyncExitStack() as exit_stack:
|
||||
f = await exit_stack.enter_async_context(open("filename"))
|
||||
|
||||
# OK (false negative)
|
||||
with contextlib.ExitStack():
|
||||
@@ -276,10 +275,9 @@ class ExampleClassTests(TestCase):
|
||||
cls.enterClassContext(open("filename"))
|
||||
|
||||
# OK
|
||||
async def foo():
|
||||
class ExampleAsyncTests(IsolatedAsyncioTestCase):
|
||||
async def test_something(self):
|
||||
await self.enterAsyncContext(open("filename"))
|
||||
class ExampleAsyncTests(IsolatedAsyncioTestCase):
|
||||
async def test_something(self):
|
||||
await self.enterAsyncContext(open("filename"))
|
||||
|
||||
# OK
|
||||
class ExampleTests(TestCase):
|
||||
|
||||
@@ -1,99 +1,98 @@
|
||||
def foo():
|
||||
# Errors
|
||||
a = "hello"
|
||||
# Errors
|
||||
a = "hello"
|
||||
|
||||
# SIM116
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return "baz"
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
# SIM116
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return "baz"
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return (1, 2, 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif a == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return ("hello'", 'hi"', 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# OK
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return baz()
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# OK
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif b == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if func_name == "create":
|
||||
return "A"
|
||||
elif func_name == "modify":
|
||||
return "M"
|
||||
elif func_name == "remove":
|
||||
return "D"
|
||||
elif func_name == "move":
|
||||
return "MV"
|
||||
|
||||
# OK
|
||||
def no_return_in_else(platform):
|
||||
if platform == "linux":
|
||||
return "auditwheel repair -w {dest_dir} {wheel}"
|
||||
elif platform == "macos":
|
||||
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
||||
elif platform == "windows":
|
||||
return ""
|
||||
else:
|
||||
return 42
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == 1:
|
||||
return (1, 2, 3)
|
||||
elif a == 2:
|
||||
return (4, 5, 6)
|
||||
elif a == 3:
|
||||
return (7, 8, 9)
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return (1, 2, 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# SIM116
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif a == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if a == "hello 'sir'":
|
||||
return ("hello'", 'hi"', 3)
|
||||
elif a == 'goodbye "mam"':
|
||||
return (4, 5, 6)
|
||||
elif a == """Fairwell 'mister'""":
|
||||
return (7, 8, 9)
|
||||
else:
|
||||
return (10, 11, 12)
|
||||
|
||||
# OK
|
||||
if a == "foo":
|
||||
return "bar"
|
||||
elif a == "bar":
|
||||
return baz()
|
||||
elif a == "boo":
|
||||
return "ooh"
|
||||
else:
|
||||
return 42
|
||||
|
||||
# OK
|
||||
if a == b"one":
|
||||
return 1
|
||||
elif b == b"two":
|
||||
return 2
|
||||
elif a == b"three":
|
||||
return 3
|
||||
|
||||
# SIM116
|
||||
if func_name == "create":
|
||||
return "A"
|
||||
elif func_name == "modify":
|
||||
return "M"
|
||||
elif func_name == "remove":
|
||||
return "D"
|
||||
elif func_name == "move":
|
||||
return "MV"
|
||||
|
||||
# OK
|
||||
def no_return_in_else(platform):
|
||||
if platform == "linux":
|
||||
return "auditwheel repair -w {dest_dir} {wheel}"
|
||||
elif platform == "macos":
|
||||
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
|
||||
elif platform == "windows":
|
||||
return ""
|
||||
else:
|
||||
msg = f"Unknown platform: {platform!r}"
|
||||
raise ValueError(msg)
|
||||
msg = f"Unknown platform: {platform!r}"
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -50,23 +50,3 @@ class Baz:
|
||||
class Nested:
|
||||
a: TypeAlias = 'Baz' # OK
|
||||
type A = 'Baz' # TC008
|
||||
|
||||
# O should have parenthesis added
|
||||
o: TypeAlias = """int
|
||||
| None"""
|
||||
type O = """int
|
||||
| None"""
|
||||
|
||||
# P, Q, and R should not have parenthesis added
|
||||
p: TypeAlias = ("""int
|
||||
| None""")
|
||||
type P = ("""int
|
||||
| None""")
|
||||
|
||||
q: TypeAlias = """(int
|
||||
| None)"""
|
||||
type Q = """(int
|
||||
| None)"""
|
||||
|
||||
r: TypeAlias = """int | None"""
|
||||
type R = """int | None"""
|
||||
@@ -1,4 +1,4 @@
|
||||
import os.path, pathlib
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
from os.path import getatime
|
||||
|
||||
@@ -10,26 +10,3 @@ os.path.getatime(Path("filename"))
|
||||
getatime("filename")
|
||||
getatime(b"filename")
|
||||
getatime(Path("filename"))
|
||||
|
||||
|
||||
file = __file__
|
||||
|
||||
os.path.getatime(file)
|
||||
os.path.getatime(filename="filename")
|
||||
os.path.getatime(filename=Path("filename"))
|
||||
|
||||
os.path.getatime( # comment 1
|
||||
# comment 2
|
||||
"filename" # comment 3
|
||||
# comment 4
|
||||
, # comment 5
|
||||
# comment 6
|
||||
) # comment 7
|
||||
|
||||
os.path.getatime("file" + "name")
|
||||
|
||||
getatime(Path("filename").resolve())
|
||||
|
||||
os.path.getatime(pathlib.Path("filename"))
|
||||
|
||||
getatime(Path("dir") / "file.txt")
|
||||
|
||||
@@ -81,5 +81,4 @@ match(foo):
|
||||
# https://github.com/astral-sh/ruff/issues/12094
|
||||
pass;
|
||||
|
||||
def foo():
|
||||
yield, x
|
||||
yield, x
|
||||
|
||||
@@ -1062,6 +1062,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::OsPathSplitext,
|
||||
Rule::BuiltinOpen,
|
||||
Rule::PyPath,
|
||||
Rule::OsPathGetatime,
|
||||
Rule::OsPathGetmtime,
|
||||
Rule::OsPathGetctime,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
Rule::OsSymlink,
|
||||
@@ -1071,15 +1074,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::OsPathGetsize) {
|
||||
flake8_use_pathlib::rules::os_path_getsize(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetatime) {
|
||||
flake8_use_pathlib::rules::os_path_getatime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetctime) {
|
||||
flake8_use_pathlib::rules::os_path_getctime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::OsPathGetmtime) {
|
||||
flake8_use_pathlib::rules::os_path_getmtime(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
}
|
||||
|
||||
@@ -54,20 +54,6 @@ pub(crate) const fn is_fix_manual_list_comprehension_enabled(settings: &LinterSe
|
||||
pub(crate) const fn is_fix_os_path_getsize_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
// https://github.com/astral-sh/ruff/pull/18922
|
||||
pub(crate) const fn is_fix_os_path_getmtime_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18922
|
||||
pub(crate) const fn is_fix_os_path_getatime_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18922
|
||||
pub(crate) const fn is_fix_os_path_getctime_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/11436
|
||||
// https://github.com/astral-sh/ruff/pull/11168
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
|
||||
---
|
||||
S112.py:4:5: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
S112.py:3:1: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
|
|
||||
2 | try:
|
||||
3 | pass
|
||||
4 | / except Exception:
|
||||
5 | | continue
|
||||
| |________________^ S112
|
||||
6 |
|
||||
7 | try:
|
||||
1 | try:
|
||||
2 | pass
|
||||
3 | / except Exception:
|
||||
4 | | continue
|
||||
| |____________^ S112
|
||||
5 |
|
||||
6 | try:
|
||||
|
|
||||
|
||||
S112.py:9:5: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
S112.py:8:1: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
|
|
||||
7 | try:
|
||||
8 | pass
|
||||
9 | / except:
|
||||
10 | | continue
|
||||
| |________________^ S112
|
||||
11 |
|
||||
12 | try:
|
||||
6 | try:
|
||||
7 | pass
|
||||
8 | / except:
|
||||
9 | | continue
|
||||
| |____________^ S112
|
||||
10 |
|
||||
11 | try:
|
||||
|
|
||||
|
||||
S112.py:14:5: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
S112.py:13:1: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
|
|
||||
12 | try:
|
||||
13 | pass
|
||||
14 | / except (Exception,):
|
||||
15 | | continue
|
||||
| |________________^ S112
|
||||
16 |
|
||||
17 | try:
|
||||
11 | try:
|
||||
12 | pass
|
||||
13 | / except (Exception,):
|
||||
14 | | continue
|
||||
| |____________^ S112
|
||||
15 |
|
||||
16 | try:
|
||||
|
|
||||
|
||||
S112.py:19:5: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
S112.py:18:1: S112 `try`-`except`-`continue` detected, consider logging the exception
|
||||
|
|
||||
17 | try:
|
||||
18 | pass
|
||||
19 | / except (Exception, ValueError):
|
||||
20 | | continue
|
||||
| |________________^ S112
|
||||
21 |
|
||||
22 | try:
|
||||
16 | try:
|
||||
17 | pass
|
||||
18 | / except (Exception, ValueError):
|
||||
19 | | continue
|
||||
| |____________^ S112
|
||||
20 |
|
||||
21 | try:
|
||||
|
|
||||
|
||||
@@ -195,31 +195,31 @@ B031.py:144:33: B031 Using the generator returned from `itertools.groupby()` mor
|
||||
146 | for group in groupby(items, key=lambda p: p[1]):
|
||||
|
|
||||
|
||||
B031.py:203:41: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
B031.py:200:37: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
201 | if _section == "greens":
|
||||
202 | collect_shop_items(shopper, section_items)
|
||||
203 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
204 | return
|
||||
|
|
||||
|
||||
B031.py:215:41: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
213 | elif _section == "frozen items":
|
||||
214 | collect_shop_items(shopper, section_items)
|
||||
215 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
216 |
|
||||
217 | # Should trigger, since only one branch has a return statement.
|
||||
|
|
||||
|
||||
B031.py:226:37: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
224 | elif _section == "frozen items":
|
||||
225 | collect_shop_items(shopper, section_items)
|
||||
226 | collect_shop_items(shopper, section_items) # B031
|
||||
198 | if _section == "greens":
|
||||
199 | collect_shop_items(shopper, section_items)
|
||||
200 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
227 |
|
||||
228 | # Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
201 | return
|
||||
|
|
||||
|
||||
B031.py:210:37: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
208 | elif _section == "frozen items":
|
||||
209 | collect_shop_items(shopper, section_items)
|
||||
210 | collect_shop_items(shopper, section_items)
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
211 |
|
||||
212 | # Should trigger, since only one branch has a return statement.
|
||||
|
|
||||
|
||||
B031.py:219:33: B031 Using the generator returned from `itertools.groupby()` more than once will do nothing on the second usage
|
||||
|
|
||||
217 | elif _section == "frozen items":
|
||||
218 | collect_shop_items(shopper, section_items)
|
||||
219 | collect_shop_items(shopper, section_items) # B031
|
||||
| ^^^^^^^^^^^^^ B031
|
||||
220 |
|
||||
221 | # Let's redefine the `groupby` function to make sure we pick up the correct one.
|
||||
|
|
||||
|
||||
@@ -190,73 +190,72 @@ PIE804.py:26:22: PIE804 [*] Unnecessary `dict` kwargs
|
||||
26 |+abc(a=1, **{'a': c}, b=c) # PIE804
|
||||
27 27 |
|
||||
28 28 | # Some values need to be parenthesized.
|
||||
29 29 | def foo():
|
||||
29 29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
|
||||
PIE804.py:30:16: PIE804 [*] Unnecessary `dict` kwargs
|
||||
PIE804.py:29:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
28 | # Some values need to be parenthesized.
|
||||
29 | def foo():
|
||||
30 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PIE804
|
||||
31 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ PIE804
|
||||
30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | abc(a=1, **{'a': c}, **{'b': c}) # PIE804
|
||||
27 27 |
|
||||
28 28 | # Some values need to be parenthesized.
|
||||
29 |-abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
29 |+abc(foo=1, bar=(bar := 1)) # PIE804
|
||||
30 30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
31 31 |
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
|
||||
PIE804.py:30:12: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
28 | # Some values need to be parenthesized.
|
||||
29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
30 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PIE804
|
||||
31 |
|
||||
32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
ℹ Safe fix
|
||||
27 27 |
|
||||
28 28 | # Some values need to be parenthesized.
|
||||
29 29 | def foo():
|
||||
30 |- abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
30 |+ abc(foo=1, bar=(bar := 1)) # PIE804
|
||||
31 31 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
32 32 |
|
||||
33 33 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
29 29 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
30 |-abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
30 |+abc(foo=1, bar=(yield 1)) # PIE804
|
||||
31 31 |
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
33 33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
|
||||
PIE804.py:31:16: PIE804 [*] Unnecessary `dict` kwargs
|
||||
PIE804.py:35:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
29 | def foo():
|
||||
30 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
31 | abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PIE804
|
||||
32 |
|
||||
33 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
ℹ Safe fix
|
||||
28 28 | # Some values need to be parenthesized.
|
||||
29 29 | def foo():
|
||||
30 30 | abc(foo=1, **{'bar': (bar := 1)}) # PIE804
|
||||
31 |- abc(foo=1, **{'bar': (yield 1)}) # PIE804
|
||||
31 |+ abc(foo=1, bar=(yield 1)) # PIE804
|
||||
32 32 |
|
||||
33 33 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
34 34 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
|
||||
PIE804.py:36:5: PIE804 [*] Unnecessary `dict` kwargs
|
||||
|
|
||||
34 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
35 | foo(
|
||||
36 | / **{
|
||||
37 | | # Comment 1
|
||||
38 | | "x": 1.0,
|
||||
39 | | # Comment 2
|
||||
40 | | "y": 2.0,
|
||||
41 | | }
|
||||
33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
34 | foo(
|
||||
35 | / **{
|
||||
36 | | # Comment 1
|
||||
37 | | "x": 1.0,
|
||||
38 | | # Comment 2
|
||||
39 | | "y": 2.0,
|
||||
40 | | }
|
||||
| |_____^ PIE804
|
||||
42 | )
|
||||
41 | )
|
||||
|
|
||||
= help: Remove unnecessary kwargs
|
||||
|
||||
ℹ Unsafe fix
|
||||
33 33 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
34 34 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
35 35 | foo(
|
||||
36 |- **{
|
||||
37 |- # Comment 1
|
||||
38 |- "x": 1.0,
|
||||
39 |- # Comment 2
|
||||
40 |- "y": 2.0,
|
||||
41 |- }
|
||||
36 |+ x=1.0, y=2.0
|
||||
42 37 | )
|
||||
32 32 | # https://github.com/astral-sh/ruff/issues/18036
|
||||
33 33 | # The autofix for this is unsafe due to the comments inside the dictionary.
|
||||
34 34 | foo(
|
||||
35 |- **{
|
||||
36 |- # Comment 1
|
||||
37 |- "x": 1.0,
|
||||
38 |- # Comment 2
|
||||
39 |- "y": 2.0,
|
||||
40 |- }
|
||||
35 |+ x=1.0, y=2.0
|
||||
41 36 | )
|
||||
|
||||
@@ -21,9 +21,7 @@ use crate::registry::Rule;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```pyi
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.platform == "xunil"[::-1]:
|
||||
/// if sys.platform.startswith("linux"):
|
||||
/// # Linux specific definitions
|
||||
/// ...
|
||||
/// else:
|
||||
@@ -33,8 +31,6 @@ use crate::registry::Rule;
|
||||
///
|
||||
/// Instead, use a simple string comparison, such as `==` or `!=`:
|
||||
/// ```pyi
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.platform == "linux":
|
||||
/// # Linux specific definitions
|
||||
/// ...
|
||||
@@ -69,15 +65,11 @@ impl Violation for UnrecognizedPlatformCheck {
|
||||
///
|
||||
/// ## Example
|
||||
/// ```pyi
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.platform == "linus": ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```pyi
|
||||
/// import sys
|
||||
///
|
||||
/// if sys.platform == "linux": ...
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -18,22 +18,17 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def find_phrase(x):
|
||||
/// if x == 1:
|
||||
/// return "Hello"
|
||||
/// elif x == 2:
|
||||
/// return "Goodbye"
|
||||
/// elif x == 3:
|
||||
/// return "Good morning"
|
||||
/// else:
|
||||
/// return "Goodnight"
|
||||
/// if x == 1:
|
||||
/// return "Hello"
|
||||
/// elif x == 2:
|
||||
/// return "Goodbye"
|
||||
/// else:
|
||||
/// return "Goodnight"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def find_phrase(x):
|
||||
/// phrases = {1: "Hello", 2: "Goodye", 3: "Good morning"}
|
||||
/// return phrases.get(x, "Goodnight")
|
||||
/// return {1: "Hello", 2: "Goodbye"}.get(x, "Goodnight")
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct IfElseBlockInsteadOfDictLookup;
|
||||
|
||||
@@ -50,285 +50,285 @@ SIM115.py:12:5: SIM115 Use a context manager for opening files
|
||||
14 | f.close()
|
||||
|
|
||||
|
||||
SIM115.py:40:9: SIM115 Use a context manager for opening files
|
||||
SIM115.py:39:9: SIM115 Use a context manager for opening files
|
||||
|
|
||||
38 | # SIM115
|
||||
39 | with contextlib.ExitStack():
|
||||
40 | f = open("filename")
|
||||
37 | # SIM115
|
||||
38 | with contextlib.ExitStack():
|
||||
39 | f = open("filename")
|
||||
| ^^^^ SIM115
|
||||
41 |
|
||||
42 | # OK
|
||||
40 |
|
||||
41 | # OK
|
||||
|
|
||||
|
||||
SIM115.py:80:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
78 | import fileinput
|
||||
79 |
|
||||
80 | f = tempfile.NamedTemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
81 | f = tempfile.TemporaryFile()
|
||||
82 | f = tempfile.SpooledTemporaryFile()
|
||||
|
|
||||
|
||||
SIM115.py:81:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
79 | import fileinput
|
||||
80 |
|
||||
81 | f = tempfile.NamedTemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
82 | f = tempfile.TemporaryFile()
|
||||
83 | f = tempfile.SpooledTemporaryFile()
|
||||
80 | f = tempfile.NamedTemporaryFile()
|
||||
81 | f = tempfile.TemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
82 | f = tempfile.SpooledTemporaryFile()
|
||||
83 | f = tarfile.open("foo.tar")
|
||||
|
|
||||
|
||||
SIM115.py:82:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
81 | f = tempfile.NamedTemporaryFile()
|
||||
82 | f = tempfile.TemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
83 | f = tempfile.SpooledTemporaryFile()
|
||||
84 | f = tarfile.open("foo.tar")
|
||||
80 | f = tempfile.NamedTemporaryFile()
|
||||
81 | f = tempfile.TemporaryFile()
|
||||
82 | f = tempfile.SpooledTemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
83 | f = tarfile.open("foo.tar")
|
||||
84 | f = TarFile("foo.tar").open()
|
||||
|
|
||||
|
||||
SIM115.py:83:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
81 | f = tempfile.NamedTemporaryFile()
|
||||
82 | f = tempfile.TemporaryFile()
|
||||
83 | f = tempfile.SpooledTemporaryFile()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
84 | f = tarfile.open("foo.tar")
|
||||
85 | f = TarFile("foo.tar").open()
|
||||
81 | f = tempfile.TemporaryFile()
|
||||
82 | f = tempfile.SpooledTemporaryFile()
|
||||
83 | f = tarfile.open("foo.tar")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
84 | f = TarFile("foo.tar").open()
|
||||
85 | f = tarfile.TarFile("foo.tar").open()
|
||||
|
|
||||
|
||||
SIM115.py:84:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
82 | f = tempfile.TemporaryFile()
|
||||
83 | f = tempfile.SpooledTemporaryFile()
|
||||
84 | f = tarfile.open("foo.tar")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
85 | f = TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile("foo.tar").open()
|
||||
82 | f = tempfile.SpooledTemporaryFile()
|
||||
83 | f = tarfile.open("foo.tar")
|
||||
84 | f = TarFile("foo.tar").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
85 | f = tarfile.TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile().open()
|
||||
|
|
||||
|
||||
SIM115.py:85:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
83 | f = tempfile.SpooledTemporaryFile()
|
||||
84 | f = tarfile.open("foo.tar")
|
||||
85 | f = TarFile("foo.tar").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
86 | f = tarfile.TarFile("foo.tar").open()
|
||||
87 | f = tarfile.TarFile().open()
|
||||
83 | f = tarfile.open("foo.tar")
|
||||
84 | f = TarFile("foo.tar").open()
|
||||
85 | f = tarfile.TarFile("foo.tar").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
86 | f = tarfile.TarFile().open()
|
||||
87 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:86:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
84 | f = tarfile.open("foo.tar")
|
||||
85 | f = TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile("foo.tar").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
87 | f = tarfile.TarFile().open()
|
||||
88 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
84 | f = TarFile("foo.tar").open()
|
||||
85 | f = tarfile.TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile().open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
87 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
88 | f = io.open("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:87:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
85 | f = TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile("foo.tar").open()
|
||||
87 | f = tarfile.TarFile().open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
88 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
89 | f = io.open("foo.txt")
|
||||
85 | f = tarfile.TarFile("foo.tar").open()
|
||||
86 | f = tarfile.TarFile().open()
|
||||
87 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
88 | f = io.open("foo.txt")
|
||||
89 | f = io.open_code("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:88:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
86 | f = tarfile.TarFile("foo.tar").open()
|
||||
87 | f = tarfile.TarFile().open()
|
||||
88 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
89 | f = io.open("foo.txt")
|
||||
90 | f = io.open_code("foo.txt")
|
||||
86 | f = tarfile.TarFile().open()
|
||||
87 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
88 | f = io.open("foo.txt")
|
||||
| ^^^^^^^ SIM115
|
||||
89 | f = io.open_code("foo.txt")
|
||||
90 | f = codecs.open("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:89:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
87 | f = tarfile.TarFile().open()
|
||||
88 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
89 | f = io.open("foo.txt")
|
||||
| ^^^^^^^ SIM115
|
||||
90 | f = io.open_code("foo.txt")
|
||||
91 | f = codecs.open("foo.txt")
|
||||
87 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
88 | f = io.open("foo.txt")
|
||||
89 | f = io.open_code("foo.txt")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
90 | f = codecs.open("foo.txt")
|
||||
91 | f = bz2.open("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:90:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
88 | f = zipfile.ZipFile("foo.zip").open("foo.txt")
|
||||
89 | f = io.open("foo.txt")
|
||||
90 | f = io.open_code("foo.txt")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
91 | f = codecs.open("foo.txt")
|
||||
92 | f = bz2.open("foo.txt")
|
||||
88 | f = io.open("foo.txt")
|
||||
89 | f = io.open_code("foo.txt")
|
||||
90 | f = codecs.open("foo.txt")
|
||||
| ^^^^^^^^^^^ SIM115
|
||||
91 | f = bz2.open("foo.txt")
|
||||
92 | f = gzip.open("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:91:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
89 | f = io.open("foo.txt")
|
||||
90 | f = io.open_code("foo.txt")
|
||||
91 | f = codecs.open("foo.txt")
|
||||
| ^^^^^^^^^^^ SIM115
|
||||
92 | f = bz2.open("foo.txt")
|
||||
93 | f = gzip.open("foo.txt")
|
||||
89 | f = io.open_code("foo.txt")
|
||||
90 | f = codecs.open("foo.txt")
|
||||
91 | f = bz2.open("foo.txt")
|
||||
| ^^^^^^^^ SIM115
|
||||
92 | f = gzip.open("foo.txt")
|
||||
93 | f = dbm.open("foo.db")
|
||||
|
|
||||
|
||||
SIM115.py:92:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
90 | f = io.open_code("foo.txt")
|
||||
91 | f = codecs.open("foo.txt")
|
||||
92 | f = bz2.open("foo.txt")
|
||||
| ^^^^^^^^ SIM115
|
||||
93 | f = gzip.open("foo.txt")
|
||||
94 | f = dbm.open("foo.db")
|
||||
90 | f = codecs.open("foo.txt")
|
||||
91 | f = bz2.open("foo.txt")
|
||||
92 | f = gzip.open("foo.txt")
|
||||
| ^^^^^^^^^ SIM115
|
||||
93 | f = dbm.open("foo.db")
|
||||
94 | f = dbm.gnu.open("foo.db")
|
||||
|
|
||||
|
||||
SIM115.py:93:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
91 | f = codecs.open("foo.txt")
|
||||
92 | f = bz2.open("foo.txt")
|
||||
93 | f = gzip.open("foo.txt")
|
||||
| ^^^^^^^^^ SIM115
|
||||
94 | f = dbm.open("foo.db")
|
||||
95 | f = dbm.gnu.open("foo.db")
|
||||
91 | f = bz2.open("foo.txt")
|
||||
92 | f = gzip.open("foo.txt")
|
||||
93 | f = dbm.open("foo.db")
|
||||
| ^^^^^^^^ SIM115
|
||||
94 | f = dbm.gnu.open("foo.db")
|
||||
95 | f = dbm.ndbm.open("foo.db")
|
||||
|
|
||||
|
||||
SIM115.py:94:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
92 | f = bz2.open("foo.txt")
|
||||
93 | f = gzip.open("foo.txt")
|
||||
94 | f = dbm.open("foo.db")
|
||||
| ^^^^^^^^ SIM115
|
||||
95 | f = dbm.gnu.open("foo.db")
|
||||
96 | f = dbm.ndbm.open("foo.db")
|
||||
92 | f = gzip.open("foo.txt")
|
||||
93 | f = dbm.open("foo.db")
|
||||
94 | f = dbm.gnu.open("foo.db")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
95 | f = dbm.ndbm.open("foo.db")
|
||||
96 | f = dbm.dumb.open("foo.db")
|
||||
|
|
||||
|
||||
SIM115.py:95:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
93 | f = gzip.open("foo.txt")
|
||||
94 | f = dbm.open("foo.db")
|
||||
95 | f = dbm.gnu.open("foo.db")
|
||||
| ^^^^^^^^^^^^ SIM115
|
||||
96 | f = dbm.ndbm.open("foo.db")
|
||||
97 | f = dbm.dumb.open("foo.db")
|
||||
93 | f = dbm.open("foo.db")
|
||||
94 | f = dbm.gnu.open("foo.db")
|
||||
95 | f = dbm.ndbm.open("foo.db")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
96 | f = dbm.dumb.open("foo.db")
|
||||
97 | f = lzma.open("foo.xz")
|
||||
|
|
||||
|
||||
SIM115.py:96:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
94 | f = dbm.open("foo.db")
|
||||
95 | f = dbm.gnu.open("foo.db")
|
||||
96 | f = dbm.ndbm.open("foo.db")
|
||||
94 | f = dbm.gnu.open("foo.db")
|
||||
95 | f = dbm.ndbm.open("foo.db")
|
||||
96 | f = dbm.dumb.open("foo.db")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
97 | f = dbm.dumb.open("foo.db")
|
||||
98 | f = lzma.open("foo.xz")
|
||||
97 | f = lzma.open("foo.xz")
|
||||
98 | f = lzma.LZMAFile("foo.xz")
|
||||
|
|
||||
|
||||
SIM115.py:97:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
95 | f = dbm.gnu.open("foo.db")
|
||||
96 | f = dbm.ndbm.open("foo.db")
|
||||
97 | f = dbm.dumb.open("foo.db")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
98 | f = lzma.open("foo.xz")
|
||||
99 | f = lzma.LZMAFile("foo.xz")
|
||||
95 | f = dbm.ndbm.open("foo.db")
|
||||
96 | f = dbm.dumb.open("foo.db")
|
||||
97 | f = lzma.open("foo.xz")
|
||||
| ^^^^^^^^^ SIM115
|
||||
98 | f = lzma.LZMAFile("foo.xz")
|
||||
99 | f = shelve.open("foo.db")
|
||||
|
|
||||
|
||||
SIM115.py:98:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
96 | f = dbm.ndbm.open("foo.db")
|
||||
97 | f = dbm.dumb.open("foo.db")
|
||||
98 | f = lzma.open("foo.xz")
|
||||
| ^^^^^^^^^ SIM115
|
||||
99 | f = lzma.LZMAFile("foo.xz")
|
||||
100 | f = shelve.open("foo.db")
|
||||
96 | f = dbm.dumb.open("foo.db")
|
||||
97 | f = lzma.open("foo.xz")
|
||||
98 | f = lzma.LZMAFile("foo.xz")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
99 | f = shelve.open("foo.db")
|
||||
100 | f = tokenize.open("foo.py")
|
||||
|
|
||||
|
||||
SIM115.py:99:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
97 | f = dbm.dumb.open("foo.db")
|
||||
98 | f = lzma.open("foo.xz")
|
||||
99 | f = lzma.LZMAFile("foo.xz")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
100 | f = shelve.open("foo.db")
|
||||
101 | f = tokenize.open("foo.py")
|
||||
97 | f = lzma.open("foo.xz")
|
||||
98 | f = lzma.LZMAFile("foo.xz")
|
||||
99 | f = shelve.open("foo.db")
|
||||
| ^^^^^^^^^^^ SIM115
|
||||
100 | f = tokenize.open("foo.py")
|
||||
101 | f = wave.open("foo.wav")
|
||||
|
|
||||
|
||||
SIM115.py:100:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
98 | f = lzma.open("foo.xz")
|
||||
99 | f = lzma.LZMAFile("foo.xz")
|
||||
100 | f = shelve.open("foo.db")
|
||||
| ^^^^^^^^^^^ SIM115
|
||||
101 | f = tokenize.open("foo.py")
|
||||
102 | f = wave.open("foo.wav")
|
||||
98 | f = lzma.LZMAFile("foo.xz")
|
||||
99 | f = shelve.open("foo.db")
|
||||
100 | f = tokenize.open("foo.py")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
101 | f = wave.open("foo.wav")
|
||||
102 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
|
|
||||
|
||||
SIM115.py:101:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
99 | f = lzma.LZMAFile("foo.xz")
|
||||
100 | f = shelve.open("foo.db")
|
||||
101 | f = tokenize.open("foo.py")
|
||||
| ^^^^^^^^^^^^^ SIM115
|
||||
102 | f = wave.open("foo.wav")
|
||||
103 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
99 | f = shelve.open("foo.db")
|
||||
100 | f = tokenize.open("foo.py")
|
||||
101 | f = wave.open("foo.wav")
|
||||
| ^^^^^^^^^ SIM115
|
||||
102 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
103 | f = fileinput.input("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:102:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
100 | f = shelve.open("foo.db")
|
||||
101 | f = tokenize.open("foo.py")
|
||||
102 | f = wave.open("foo.wav")
|
||||
| ^^^^^^^^^ SIM115
|
||||
103 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
104 | f = fileinput.input("foo.txt")
|
||||
100 | f = tokenize.open("foo.py")
|
||||
101 | f = wave.open("foo.wav")
|
||||
102 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
103 | f = fileinput.input("foo.txt")
|
||||
104 | f = fileinput.FileInput("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:103:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
101 | f = tokenize.open("foo.py")
|
||||
102 | f = wave.open("foo.wav")
|
||||
103 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
104 | f = fileinput.input("foo.txt")
|
||||
105 | f = fileinput.FileInput("foo.txt")
|
||||
101 | f = wave.open("foo.wav")
|
||||
102 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
103 | f = fileinput.input("foo.txt")
|
||||
| ^^^^^^^^^^^^^^^ SIM115
|
||||
104 | f = fileinput.FileInput("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:104:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
102 | f = wave.open("foo.wav")
|
||||
103 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
104 | f = fileinput.input("foo.txt")
|
||||
| ^^^^^^^^^^^^^^^ SIM115
|
||||
105 | f = fileinput.FileInput("foo.txt")
|
||||
|
|
||||
|
||||
SIM115.py:105:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
103 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
104 | f = fileinput.input("foo.txt")
|
||||
105 | f = fileinput.FileInput("foo.txt")
|
||||
102 | f = tarfile.TarFile.taropen("foo.tar")
|
||||
103 | f = fileinput.input("foo.txt")
|
||||
104 | f = fileinput.FileInput("foo.txt")
|
||||
| ^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
106 |
|
||||
107 | with contextlib.suppress(Exception):
|
||||
105 |
|
||||
106 | with contextlib.suppress(Exception):
|
||||
|
|
||||
|
||||
SIM115.py:241:9: SIM115 Use a context manager for opening files
|
||||
SIM115.py:240:9: SIM115 Use a context manager for opening files
|
||||
|
|
||||
239 | def aliased():
|
||||
240 | from shelve import open as open_shelf
|
||||
241 | x = open_shelf("foo.dbm")
|
||||
238 | def aliased():
|
||||
239 | from shelve import open as open_shelf
|
||||
240 | x = open_shelf("foo.dbm")
|
||||
| ^^^^^^^^^^ SIM115
|
||||
242 | x.close()
|
||||
241 | x.close()
|
||||
|
|
||||
|
||||
SIM115.py:245:9: SIM115 Use a context manager for opening files
|
||||
SIM115.py:244:9: SIM115 Use a context manager for opening files
|
||||
|
|
||||
244 | from tarfile import TarFile as TF
|
||||
245 | f = TF("foo").open()
|
||||
243 | from tarfile import TarFile as TF
|
||||
244 | f = TF("foo").open()
|
||||
| ^^^^^^^^^^^^^^ SIM115
|
||||
246 | f.close()
|
||||
245 | f.close()
|
||||
|
|
||||
|
||||
SIM115.py:258:5: SIM115 Use a context manager for opening files
|
||||
SIM115.py:257:5: SIM115 Use a context manager for opening files
|
||||
|
|
||||
257 | # SIM115
|
||||
258 | f = dbm.sqlite3.open("foo.db")
|
||||
256 | # SIM115
|
||||
257 | f = dbm.sqlite3.open("foo.db")
|
||||
| ^^^^^^^^^^^^^^^^ SIM115
|
||||
259 | f.close()
|
||||
258 | f.close()
|
||||
|
|
||||
|
||||
@@ -1,110 +1,110 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM116.py:6:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:5:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
5 | # SIM116
|
||||
6 | / if a == "foo":
|
||||
7 | | return "bar"
|
||||
8 | | elif a == "bar":
|
||||
9 | | return "baz"
|
||||
10 | | elif a == "boo":
|
||||
11 | | return "ooh"
|
||||
12 | | else:
|
||||
13 | | return 42
|
||||
| |_________________^ SIM116
|
||||
14 |
|
||||
15 | # SIM116
|
||||
4 | # SIM116
|
||||
5 | / if a == "foo":
|
||||
6 | | return "bar"
|
||||
7 | | elif a == "bar":
|
||||
8 | | return "baz"
|
||||
9 | | elif a == "boo":
|
||||
10 | | return "ooh"
|
||||
11 | | else:
|
||||
12 | | return 42
|
||||
| |_____________^ SIM116
|
||||
13 |
|
||||
14 | # SIM116
|
||||
|
|
||||
|
||||
SIM116.py:16:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:15:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
15 | # SIM116
|
||||
16 | / if a == 1:
|
||||
17 | | return (1, 2, 3)
|
||||
18 | | elif a == 2:
|
||||
19 | | return (4, 5, 6)
|
||||
20 | | elif a == 3:
|
||||
21 | | return (7, 8, 9)
|
||||
22 | | else:
|
||||
23 | | return (10, 11, 12)
|
||||
| |___________________________^ SIM116
|
||||
24 |
|
||||
25 | # SIM116
|
||||
14 | # SIM116
|
||||
15 | / if a == 1:
|
||||
16 | | return (1, 2, 3)
|
||||
17 | | elif a == 2:
|
||||
18 | | return (4, 5, 6)
|
||||
19 | | elif a == 3:
|
||||
20 | | return (7, 8, 9)
|
||||
21 | | else:
|
||||
22 | | return (10, 11, 12)
|
||||
| |_______________________^ SIM116
|
||||
23 |
|
||||
24 | # SIM116
|
||||
|
|
||||
|
||||
SIM116.py:26:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:25:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
25 | # SIM116
|
||||
26 | / if a == 1:
|
||||
27 | | return (1, 2, 3)
|
||||
28 | | elif a == 2:
|
||||
29 | | return (4, 5, 6)
|
||||
30 | | elif a == 3:
|
||||
31 | | return (7, 8, 9)
|
||||
| |________________________^ SIM116
|
||||
32 |
|
||||
33 | # SIM116
|
||||
24 | # SIM116
|
||||
25 | / if a == 1:
|
||||
26 | | return (1, 2, 3)
|
||||
27 | | elif a == 2:
|
||||
28 | | return (4, 5, 6)
|
||||
29 | | elif a == 3:
|
||||
30 | | return (7, 8, 9)
|
||||
| |____________________^ SIM116
|
||||
31 |
|
||||
32 | # SIM116
|
||||
|
|
||||
|
||||
SIM116.py:34:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:33:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
33 | # SIM116
|
||||
34 | / if a == "hello 'sir'":
|
||||
35 | | return (1, 2, 3)
|
||||
36 | | elif a == 'goodbye "mam"':
|
||||
37 | | return (4, 5, 6)
|
||||
38 | | elif a == """Fairwell 'mister'""":
|
||||
39 | | return (7, 8, 9)
|
||||
40 | | else:
|
||||
41 | | return (10, 11, 12)
|
||||
| |___________________________^ SIM116
|
||||
42 |
|
||||
43 | # SIM116
|
||||
32 | # SIM116
|
||||
33 | / if a == "hello 'sir'":
|
||||
34 | | return (1, 2, 3)
|
||||
35 | | elif a == 'goodbye "mam"':
|
||||
36 | | return (4, 5, 6)
|
||||
37 | | elif a == """Fairwell 'mister'""":
|
||||
38 | | return (7, 8, 9)
|
||||
39 | | else:
|
||||
40 | | return (10, 11, 12)
|
||||
| |_______________________^ SIM116
|
||||
41 |
|
||||
42 | # SIM116
|
||||
|
|
||||
|
||||
SIM116.py:44:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:43:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
43 | # SIM116
|
||||
44 | / if a == b"one":
|
||||
45 | | return 1
|
||||
46 | | elif a == b"two":
|
||||
47 | | return 2
|
||||
48 | | elif a == b"three":
|
||||
49 | | return 3
|
||||
| |________________^ SIM116
|
||||
50 |
|
||||
51 | # SIM116
|
||||
42 | # SIM116
|
||||
43 | / if a == b"one":
|
||||
44 | | return 1
|
||||
45 | | elif a == b"two":
|
||||
46 | | return 2
|
||||
47 | | elif a == b"three":
|
||||
48 | | return 3
|
||||
| |____________^ SIM116
|
||||
49 |
|
||||
50 | # SIM116
|
||||
|
|
||||
|
||||
SIM116.py:52:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:51:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
51 | # SIM116
|
||||
52 | / if a == "hello 'sir'":
|
||||
53 | | return ("hello'", 'hi"', 3)
|
||||
54 | | elif a == 'goodbye "mam"':
|
||||
55 | | return (4, 5, 6)
|
||||
56 | | elif a == """Fairwell 'mister'""":
|
||||
57 | | return (7, 8, 9)
|
||||
58 | | else:
|
||||
59 | | return (10, 11, 12)
|
||||
| |___________________________^ SIM116
|
||||
60 |
|
||||
61 | # OK
|
||||
50 | # SIM116
|
||||
51 | / if a == "hello 'sir'":
|
||||
52 | | return ("hello'", 'hi"', 3)
|
||||
53 | | elif a == 'goodbye "mam"':
|
||||
54 | | return (4, 5, 6)
|
||||
55 | | elif a == """Fairwell 'mister'""":
|
||||
56 | | return (7, 8, 9)
|
||||
57 | | else:
|
||||
58 | | return (10, 11, 12)
|
||||
| |_______________________^ SIM116
|
||||
59 |
|
||||
60 | # OK
|
||||
|
|
||||
|
||||
SIM116.py:80:5: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
SIM116.py:79:1: SIM116 Use a dictionary instead of consecutive `if` statements
|
||||
|
|
||||
79 | # SIM116
|
||||
80 | / if func_name == "create":
|
||||
81 | | return "A"
|
||||
82 | | elif func_name == "modify":
|
||||
83 | | return "M"
|
||||
84 | | elif func_name == "remove":
|
||||
85 | | return "D"
|
||||
86 | | elif func_name == "move":
|
||||
87 | | return "MV"
|
||||
| |___________________^ SIM116
|
||||
88 |
|
||||
89 | # OK
|
||||
78 | # SIM116
|
||||
79 | / if func_name == "create":
|
||||
80 | | return "A"
|
||||
81 | | elif func_name == "modify":
|
||||
82 | | return "M"
|
||||
83 | | elif func_name == "remove":
|
||||
84 | | return "D"
|
||||
85 | | elif func_name == "move":
|
||||
86 | | return "MV"
|
||||
| |_______________^ SIM116
|
||||
87 |
|
||||
88 | # OK
|
||||
|
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::registry::Rule;
|
||||
use crate::rules::flake8_type_checking::helpers::quote_type_expression;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks if [PEP 613] explicit type aliases contain references to
|
||||
@@ -88,15 +87,11 @@ impl Violation for UnquotedTypeAlias {
|
||||
/// ## Example
|
||||
/// Given:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// OptInt: TypeAlias = "int | None"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import TypeAlias
|
||||
///
|
||||
/// OptInt: TypeAlias = int | None
|
||||
/// ```
|
||||
///
|
||||
@@ -292,30 +287,7 @@ pub(crate) fn quoted_type_alias(
|
||||
|
||||
let range = annotation_expr.range();
|
||||
let mut diagnostic = checker.report_diagnostic(QuotedTypeAlias, range);
|
||||
let fix_string = annotation_expr.value.to_string();
|
||||
let fix_string = if (fix_string.contains('\n') || fix_string.contains('\r'))
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis outside string ("""...""")
|
||||
annotation_expr.into(),
|
||||
checker.semantic().current_statement().into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.is_none()
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis inside string """(...)"""
|
||||
expr.into(),
|
||||
annotation_expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
format!("({fix_string})")
|
||||
} else {
|
||||
fix_string
|
||||
};
|
||||
let edit = Edit::range_replacement(fix_string, range);
|
||||
let edit = Edit::range_replacement(annotation_expr.value.to_string(), range);
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
///
|
||||
/// from . import local_module
|
||||
/// import local_module
|
||||
///
|
||||
///
|
||||
/// def func(sized: local_module.Container) -> int:
|
||||
@@ -58,7 +58,7 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// from typing import TYPE_CHECKING
|
||||
///
|
||||
/// if TYPE_CHECKING:
|
||||
/// from . import local_module
|
||||
/// import local_module
|
||||
///
|
||||
///
|
||||
/// def func(sized: local_module.Container) -> int:
|
||||
|
||||
@@ -409,8 +409,6 @@ TC008.py:52:18: TC008 [*] Remove quotes from type alias
|
||||
51 | a: TypeAlias = 'Baz' # OK
|
||||
52 | type A = 'Baz' # TC008
|
||||
| ^^^^^ TC008
|
||||
53 |
|
||||
54 | # O should have parenthesis added
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
@@ -420,187 +418,3 @@ TC008.py:52:18: TC008 [*] Remove quotes from type alias
|
||||
51 51 | a: TypeAlias = 'Baz' # OK
|
||||
52 |- type A = 'Baz' # TC008
|
||||
52 |+ type A = Baz # TC008
|
||||
53 53 |
|
||||
54 54 | # O should have parenthesis added
|
||||
55 55 | o: TypeAlias = """int
|
||||
|
||||
TC008.py:55:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
54 | # O should have parenthesis added
|
||||
55 | o: TypeAlias = """int
|
||||
| ________________^
|
||||
56 | | | None"""
|
||||
| |_________^ TC008
|
||||
57 | type O = """int
|
||||
58 | | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
52 52 | type A = 'Baz' # TC008
|
||||
53 53 |
|
||||
54 54 | # O should have parenthesis added
|
||||
55 |-o: TypeAlias = """int
|
||||
56 |-| None"""
|
||||
55 |+o: TypeAlias = (int
|
||||
56 |+| None)
|
||||
57 57 | type O = """int
|
||||
58 58 | | None"""
|
||||
59 59 |
|
||||
|
||||
TC008.py:57:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
55 | o: TypeAlias = """int
|
||||
56 | | None"""
|
||||
57 | type O = """int
|
||||
| __________^
|
||||
58 | | | None"""
|
||||
| |_________^ TC008
|
||||
59 |
|
||||
60 | # P, Q, and R should not have parenthesis added
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
54 54 | # O should have parenthesis added
|
||||
55 55 | o: TypeAlias = """int
|
||||
56 56 | | None"""
|
||||
57 |-type O = """int
|
||||
58 |-| None"""
|
||||
57 |+type O = (int
|
||||
58 |+| None)
|
||||
59 59 |
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 61 | p: TypeAlias = ("""int
|
||||
|
||||
TC008.py:61:17: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
60 | # P, Q, and R should not have parenthesis added
|
||||
61 | p: TypeAlias = ("""int
|
||||
| _________________^
|
||||
62 | | | None""")
|
||||
| |_________^ TC008
|
||||
63 | type P = ("""int
|
||||
64 | | None""")
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
58 58 | | None"""
|
||||
59 59 |
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 |-p: TypeAlias = ("""int
|
||||
62 |-| None""")
|
||||
61 |+p: TypeAlias = (int
|
||||
62 |+| None)
|
||||
63 63 | type P = ("""int
|
||||
64 64 | | None""")
|
||||
65 65 |
|
||||
|
||||
TC008.py:63:11: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
61 | p: TypeAlias = ("""int
|
||||
62 | | None""")
|
||||
63 | type P = ("""int
|
||||
| ___________^
|
||||
64 | | | None""")
|
||||
| |_________^ TC008
|
||||
65 |
|
||||
66 | q: TypeAlias = """(int
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
60 60 | # P, Q, and R should not have parenthesis added
|
||||
61 61 | p: TypeAlias = ("""int
|
||||
62 62 | | None""")
|
||||
63 |-type P = ("""int
|
||||
64 |-| None""")
|
||||
63 |+type P = (int
|
||||
64 |+| None)
|
||||
65 65 |
|
||||
66 66 | q: TypeAlias = """(int
|
||||
67 67 | | None)"""
|
||||
|
||||
TC008.py:66:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
64 | | None""")
|
||||
65 |
|
||||
66 | q: TypeAlias = """(int
|
||||
| ________________^
|
||||
67 | | | None)"""
|
||||
| |__________^ TC008
|
||||
68 | type Q = """(int
|
||||
69 | | None)"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
63 63 | type P = ("""int
|
||||
64 64 | | None""")
|
||||
65 65 |
|
||||
66 |-q: TypeAlias = """(int
|
||||
67 |-| None)"""
|
||||
66 |+q: TypeAlias = (int
|
||||
67 |+| None)
|
||||
68 68 | type Q = """(int
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
|
||||
TC008.py:68:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
66 | q: TypeAlias = """(int
|
||||
67 | | None)"""
|
||||
68 | type Q = """(int
|
||||
| __________^
|
||||
69 | | | None)"""
|
||||
| |__________^ TC008
|
||||
70 |
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
65 65 |
|
||||
66 66 | q: TypeAlias = """(int
|
||||
67 67 | | None)"""
|
||||
68 |-type Q = """(int
|
||||
69 |-| None)"""
|
||||
68 |+type Q = (int
|
||||
69 |+| None)
|
||||
70 70 |
|
||||
71 71 | r: TypeAlias = """int | None"""
|
||||
72 72 | type R = """int | None"""
|
||||
|
||||
TC008.py:71:16: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
69 | | None)"""
|
||||
70 |
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
| ^^^^^^^^^^^^^^^^ TC008
|
||||
72 | type R = """int | None"""
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
68 68 | type Q = """(int
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
71 |-r: TypeAlias = """int | None"""
|
||||
71 |+r: TypeAlias = int | None
|
||||
72 72 | type R = """int | None"""
|
||||
|
||||
TC008.py:72:10: TC008 [*] Remove quotes from type alias
|
||||
|
|
||||
71 | r: TypeAlias = """int | None"""
|
||||
72 | type R = """int | None"""
|
||||
| ^^^^^^^^^^^^^^^^ TC008
|
||||
|
|
||||
= help: Remove quotes
|
||||
|
||||
ℹ Safe fix
|
||||
69 69 | | None)"""
|
||||
70 70 |
|
||||
71 71 | r: TypeAlias = """int | None"""
|
||||
72 |-type R = """int | None"""
|
||||
72 |+type R = int | None
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::{Applicability, Edit, Fix, Violation};
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
pub(crate) fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
expr.as_call_expr().is_some_and(|expr_call| {
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&expr_call.func)
|
||||
.is_some_and(|name| matches!(name.segments(), ["pathlib", "Path"]))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_os_path_get_calls(
|
||||
checker: &Checker,
|
||||
call: &ExprCall,
|
||||
fn_name: &str,
|
||||
attr: &str,
|
||||
fix_enabled: bool,
|
||||
violation: impl Violation,
|
||||
) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&call.func)
|
||||
.is_none_or(|qualified_name| qualified_name.segments() != ["os", "path", fn_name])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_code = checker.locator().slice(arg.range());
|
||||
let range = call.range();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(violation, call.func.range());
|
||||
|
||||
if fix_enabled {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let replacement = if is_path_call(checker, arg) {
|
||||
format!("{arg_code}.stat().{attr}")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).stat().{attr}")
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Rules from [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/).
|
||||
mod helpers;
|
||||
pub(crate) mod rules;
|
||||
pub(crate) mod violations;
|
||||
|
||||
@@ -82,9 +81,6 @@ mod tests {
|
||||
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202.py"))]
|
||||
#[test_case(Rule::OsPathGetsize, Path::new("PTH202_2.py"))]
|
||||
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
|
||||
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
|
||||
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
|
||||
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -19,20 +19,12 @@ use ruff_text_size::Ranged;
|
||||
/// ## Example
|
||||
///
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// path = Path()
|
||||
///
|
||||
/// path.with_suffix("py")
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// from pathlib import Path
|
||||
///
|
||||
/// path = Path()
|
||||
///
|
||||
/// path.with_suffix(".py")
|
||||
/// ```
|
||||
///
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getatime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getatime`.
|
||||
@@ -35,9 +32,6 @@ use ruff_python_ast::ExprCall;
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
|
||||
@@ -49,25 +43,8 @@ use ruff_python_ast::ExprCall;
|
||||
pub(crate) struct OsPathGetatime;
|
||||
|
||||
impl Violation for OsPathGetatime {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.getatime` should be replaced by `Path.stat().st_atime`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path.stat(...).st_atime`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH203
|
||||
pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
checker,
|
||||
call,
|
||||
"getatime",
|
||||
"st_atime",
|
||||
is_fix_os_path_getatime_enabled(checker.settings()),
|
||||
OsPathGetatime,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getctime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getctime`.
|
||||
@@ -35,9 +32,6 @@ use ruff_python_ast::ExprCall;
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
|
||||
@@ -49,26 +43,8 @@ use ruff_python_ast::ExprCall;
|
||||
pub(crate) struct OsPathGetctime;
|
||||
|
||||
impl Violation for OsPathGetctime {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.getctime` should be replaced by `Path.stat().st_ctime`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path.stat(...).st_ctime`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH205
|
||||
pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
checker,
|
||||
call,
|
||||
"getctime",
|
||||
"st_ctime",
|
||||
is_fix_os_path_getctime_enabled(checker.settings()),
|
||||
OsPathGetctime,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getmtime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::Violation;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getmtime`.
|
||||
@@ -35,9 +32,6 @@ use ruff_python_ast::ExprCall;
|
||||
/// it can be less performant than the lower-level alternatives that work directly with strings,
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
|
||||
@@ -49,26 +43,8 @@ use ruff_python_ast::ExprCall;
|
||||
pub(crate) struct OsPathGetmtime;
|
||||
|
||||
impl Violation for OsPathGetmtime {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"`os.path.getmtime` should be replaced by `Path.stat().st_mtime`".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Replace with `Path.stat(...).st_mtime`".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// PTH204
|
||||
pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
checker,
|
||||
call,
|
||||
"getmtime",
|
||||
"st_mtime",
|
||||
is_fix_os_path_getmtime_enabled(checker.settings()),
|
||||
OsPathGetmtime,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_os_path_getsize_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_path_get_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use crate::{Applicability, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getsize`.
|
||||
@@ -63,12 +65,63 @@ impl Violation for OsPathGetsize {
|
||||
|
||||
/// PTH202
|
||||
pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall) {
|
||||
check_os_path_get_calls(
|
||||
checker,
|
||||
call,
|
||||
"getsize",
|
||||
"st_size",
|
||||
is_fix_os_path_getsize_enabled(checker.settings()),
|
||||
OsPathGetsize,
|
||||
);
|
||||
if !matches!(
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&call.func)
|
||||
.as_ref()
|
||||
.map(QualifiedName::segments),
|
||||
Some(["os", "path", "getsize"])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("filename", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_code = checker.locator().slice(arg.range());
|
||||
let range = call.range();
|
||||
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(OsPathGetsize, range);
|
||||
|
||||
if is_fix_os_path_getsize_enabled(checker.settings()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let replacement = if is_path_call(checker, arg) {
|
||||
format!("{arg_code}.stat().st_size")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).stat().st_size")
|
||||
};
|
||||
|
||||
Ok(
|
||||
Fix::safe_edits(Edit::range_replacement(replacement, range), [import_edit])
|
||||
.with_applicability(applicability),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn is_path_call(checker: &Checker, expr: &Expr) -> bool {
|
||||
expr.as_call_expr().is_some_and(|expr_call| {
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(&expr_call.func)
|
||||
.is_some_and(|name| matches!(name.segments(), ["pathlib", "Path"]))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::rules::flake8_use_pathlib::rules::Glob;
|
||||
use crate::rules::flake8_use_pathlib::rules::{
|
||||
Glob, OsPathGetatime, OsPathGetctime, OsPathGetmtime,
|
||||
};
|
||||
use crate::rules::flake8_use_pathlib::violations::{
|
||||
BuiltinOpen, Joiner, OsChmod, OsGetcwd, OsListdir, OsMakedirs, OsMkdir, OsPathAbspath,
|
||||
OsPathBasename, OsPathDirname, OsPathExists, OsPathExpanduser, OsPathIsabs, OsPathIsdir,
|
||||
@@ -192,6 +194,12 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) {
|
||||
["os", "path", "samefile"] => checker.report_diagnostic_if_enabled(OsPathSamefile, range),
|
||||
// PTH122
|
||||
["os", "path", "splitext"] => checker.report_diagnostic_if_enabled(OsPathSplitext, range),
|
||||
// PTH203
|
||||
["os", "path", "getatime"] => checker.report_diagnostic_if_enabled(OsPathGetatime, range),
|
||||
// PTH204
|
||||
["os", "path", "getmtime"] => checker.report_diagnostic_if_enabled(OsPathGetmtime, range),
|
||||
// PTH205
|
||||
["os", "path", "getctime"] => checker.report_diagnostic_if_enabled(OsPathGetctime, range),
|
||||
// PTH211
|
||||
["os", "symlink"] => {
|
||||
// `dir_fd` is not supported by pathlib, so check if there are non-default values.
|
||||
|
||||
@@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
PTH202.py:10:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
10 | os.path.getsize("filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
|
|
||||
@@ -14,7 +14,7 @@ PTH202.py:11:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
10 | os.path.getsize("filename")
|
||||
11 | os.path.getsize(b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
13 | os.path.getsize(__file__)
|
||||
|
|
||||
@@ -25,7 +25,7 @@ PTH202.py:12:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
10 | os.path.getsize("filename")
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
13 | os.path.getsize(__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -35,7 +35,7 @@ PTH202.py:13:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
13 | os.path.getsize(__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
14 |
|
||||
15 | os.path.getsize(filename)
|
||||
|
|
||||
@@ -46,7 +46,7 @@ PTH202.py:15:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
13 | os.path.getsize(__file__)
|
||||
14 |
|
||||
15 | os.path.getsize(filename)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
16 | os.path.getsize(filename1)
|
||||
17 | os.path.getsize(filename2)
|
||||
|
|
||||
@@ -56,7 +56,7 @@ PTH202.py:16:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
15 | os.path.getsize(filename)
|
||||
16 | os.path.getsize(filename1)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
17 | os.path.getsize(filename2)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -66,7 +66,7 @@ PTH202.py:17:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
15 | os.path.getsize(filename)
|
||||
16 | os.path.getsize(filename1)
|
||||
17 | os.path.getsize(filename2)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
18 |
|
||||
19 | os.path.getsize(filename="filename")
|
||||
|
|
||||
@@ -77,7 +77,7 @@ PTH202.py:19:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
17 | os.path.getsize(filename2)
|
||||
18 |
|
||||
19 | os.path.getsize(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
|
|
||||
@@ -87,7 +87,7 @@ PTH202.py:20:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
19 | os.path.getsize(filename="filename")
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
@@ -98,7 +98,7 @@ PTH202.py:21:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
19 | os.path.getsize(filename="filename")
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -108,7 +108,7 @@ PTH202.py:22:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
23 |
|
||||
24 | getsize("filename")
|
||||
|
|
||||
@@ -119,7 +119,7 @@ PTH202.py:24:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
23 |
|
||||
24 | getsize("filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
|
|
||||
@@ -129,7 +129,7 @@ PTH202.py:25:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
24 | getsize("filename")
|
||||
25 | getsize(b"filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
26 | getsize(Path("filename"))
|
||||
27 | getsize(__file__)
|
||||
|
|
||||
@@ -140,7 +140,7 @@ PTH202.py:26:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
24 | getsize("filename")
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
27 | getsize(__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -150,7 +150,7 @@ PTH202.py:27:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
27 | getsize(__file__)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^ PTH202
|
||||
28 |
|
||||
29 | getsize(filename="filename")
|
||||
|
|
||||
@@ -161,7 +161,7 @@ PTH202.py:29:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
27 | getsize(__file__)
|
||||
28 |
|
||||
29 | getsize(filename="filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
|
|
||||
@@ -171,7 +171,7 @@ PTH202.py:30:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
29 | getsize(filename="filename")
|
||||
30 | getsize(filename=b"filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
31 | getsize(filename=Path("filename"))
|
||||
32 | getsize(filename=__file__)
|
||||
|
|
||||
@@ -182,7 +182,7 @@ PTH202.py:31:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
29 | getsize(filename="filename")
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
32 | getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -192,7 +192,7 @@ PTH202.py:32:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
32 | getsize(filename=__file__)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
33 |
|
||||
34 | getsize(filename)
|
||||
|
|
||||
@@ -203,7 +203,7 @@ PTH202.py:34:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
32 | getsize(filename=__file__)
|
||||
33 |
|
||||
34 | getsize(filename)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^ PTH202
|
||||
35 | getsize(filename1)
|
||||
36 | getsize(filename2)
|
||||
|
|
||||
@@ -213,7 +213,7 @@ PTH202.py:35:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
|
||||
34 | getsize(filename)
|
||||
35 | getsize(filename1)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH202
|
||||
36 | getsize(filename2)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -223,70 +223,89 @@ PTH202.py:36:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
34 | getsize(filename)
|
||||
35 | getsize(filename1)
|
||||
36 | getsize(filename2)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:39:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
39 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
40 | "filename", # comment
|
||||
41 | )
|
||||
39 | / os.path.getsize(
|
||||
40 | | "filename", # comment
|
||||
41 | | )
|
||||
| |_^ PTH202
|
||||
42 |
|
||||
43 | os.path.getsize(
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:43:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
41 | )
|
||||
41 | )
|
||||
42 |
|
||||
43 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
44 | # comment
|
||||
45 | "filename"
|
||||
43 | / os.path.getsize(
|
||||
44 | | # comment
|
||||
45 | | "filename"
|
||||
46 | | ,
|
||||
47 | | # comment
|
||||
48 | | )
|
||||
| |_^ PTH202
|
||||
49 |
|
||||
50 | os.path.getsize(
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:50:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
48 | )
|
||||
48 | )
|
||||
49 |
|
||||
50 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
51 | # comment
|
||||
52 | b"filename"
|
||||
50 | / os.path.getsize(
|
||||
51 | | # comment
|
||||
52 | | b"filename"
|
||||
53 | | # comment
|
||||
54 | | )
|
||||
| |_^ PTH202
|
||||
55 |
|
||||
56 | os.path.getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:56:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
54 | )
|
||||
54 | )
|
||||
55 |
|
||||
56 | os.path.getsize( # comment
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
57 | Path(__file__)
|
||||
58 | # comment
|
||||
56 | / os.path.getsize( # comment
|
||||
57 | | Path(__file__)
|
||||
58 | | # comment
|
||||
59 | | ) # comment
|
||||
| |_^ PTH202
|
||||
60 |
|
||||
61 | getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:61:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
59 | ) # comment
|
||||
59 | ) # comment
|
||||
60 |
|
||||
61 | getsize( # comment
|
||||
| ^^^^^^^ PTH202
|
||||
62 | "filename")
|
||||
61 | / getsize( # comment
|
||||
62 | | "filename")
|
||||
| |_______________^ PTH202
|
||||
63 |
|
||||
64 | getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
PTH202.py:64:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
62 | "filename")
|
||||
62 | "filename")
|
||||
63 |
|
||||
64 | getsize( # comment
|
||||
| ^^^^^^^ PTH202
|
||||
65 | b"filename",
|
||||
66 | #comment
|
||||
64 | / getsize( # comment
|
||||
65 | | b"filename",
|
||||
66 | | #comment
|
||||
67 | | )
|
||||
| |_^ PTH202
|
||||
68 |
|
||||
69 | os.path.getsize("file" + "name")
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -295,7 +314,7 @@ PTH202.py:69:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
67 | )
|
||||
68 |
|
||||
69 | os.path.getsize("file" + "name")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
70 |
|
||||
71 | getsize \
|
||||
|
|
||||
@@ -303,12 +322,17 @@ PTH202.py:69:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
|
||||
PTH202.py:71:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
69 | os.path.getsize("file" + "name")
|
||||
69 | os.path.getsize("file" + "name")
|
||||
70 |
|
||||
71 | getsize \
|
||||
| ^^^^^^^ PTH202
|
||||
72 | \
|
||||
73 | \
|
||||
71 | / getsize \
|
||||
72 | | \
|
||||
73 | | \
|
||||
74 | | ( # comment
|
||||
75 | | "filename",
|
||||
76 | | )
|
||||
| |_____^ PTH202
|
||||
77 |
|
||||
78 | getsize(Path("filename").resolve())
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -317,7 +341,7 @@ PTH202.py:78:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
76 | )
|
||||
77 |
|
||||
78 | getsize(Path("filename").resolve())
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
79 |
|
||||
80 | import pathlib
|
||||
|
|
||||
@@ -328,6 +352,6 @@ PTH202.py:82:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_s
|
||||
80 | import pathlib
|
||||
81 |
|
||||
82 | os.path.getsize(pathlib.Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -6,7 +6,7 @@ PTH202_2.py:3:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_
|
||||
1 | import os
|
||||
2 |
|
||||
3 | os.path.getsize(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
@@ -16,7 +16,7 @@ PTH202_2.py:4:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_
|
||||
|
|
||||
3 | os.path.getsize(filename="filename")
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -26,6 +26,6 @@ PTH202_2.py:5:1: PTH202 `os.path.getsize` should be replaced by `Path.stat().st_
|
||||
3 | os.path.getsize(filename="filename")
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -10,7 +10,6 @@ PTH203.py:5:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_a
|
||||
6 | os.path.getatime(b"filename")
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:6:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
@@ -19,7 +18,6 @@ PTH203.py:6:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_a
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:7:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
@@ -28,7 +26,6 @@ PTH203.py:7:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_a
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:10:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
@@ -37,7 +34,6 @@ PTH203.py:10:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_
|
||||
11 | getatime(b"filename")
|
||||
12 | getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:11:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
@@ -46,7 +42,6 @@ PTH203.py:11:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_
|
||||
| ^^^^^^^^ PTH203
|
||||
12 | getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:12:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
@@ -55,88 +50,3 @@ PTH203.py:12:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_
|
||||
12 | getatime(Path("filename"))
|
||||
| ^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:17:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
15 | file = __file__
|
||||
16 |
|
||||
17 | os.path.getatime(file)
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
18 | os.path.getatime(filename="filename")
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:18:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
17 | os.path.getatime(file)
|
||||
18 | os.path.getatime(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:19:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
17 | os.path.getatime(file)
|
||||
18 | os.path.getatime(filename="filename")
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
20 |
|
||||
21 | os.path.getatime( # comment 1
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:21:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
20 |
|
||||
21 | os.path.getatime( # comment 1
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
22 | # comment 2
|
||||
23 | "filename" # comment 3
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:29:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
27 | ) # comment 7
|
||||
28 |
|
||||
29 | os.path.getatime("file" + "name")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
30 |
|
||||
31 | getatime(Path("filename").resolve())
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:31:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
29 | os.path.getatime("file" + "name")
|
||||
30 |
|
||||
31 | getatime(Path("filename").resolve())
|
||||
| ^^^^^^^^ PTH203
|
||||
32 |
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:33:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
31 | getatime(Path("filename").resolve())
|
||||
32 |
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
34 |
|
||||
35 | getatime(Path("dir") / "file.txt")
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
PTH203.py:35:1: PTH203 `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
34 |
|
||||
35 | getatime(Path("dir") / "file.txt")
|
||||
| ^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PTH204.py:6:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -8,7 +9,6 @@ PTH204.py:6:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_m
|
||||
7 | os.path.getmtime(b"filename")
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
PTH204.py:7:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -17,7 +17,6 @@ PTH204.py:7:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_m
|
||||
| ^^^^^^^^^^^^^^^^ PTH204
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
PTH204.py:8:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -26,7 +25,6 @@ PTH204.py:8:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_m
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH204
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
PTH204.py:11:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -35,7 +33,6 @@ PTH204.py:11:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_
|
||||
12 | getmtime(b"filename")
|
||||
13 | getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
PTH204.py:12:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -44,7 +41,6 @@ PTH204.py:12:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_
|
||||
| ^^^^^^^^ PTH204
|
||||
13 | getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
PTH204.py:13:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
@@ -53,4 +49,3 @@ PTH204.py:13:1: PTH204 `os.path.getmtime` should be replaced by `Path.stat().st_
|
||||
13 | getmtime(Path("filename"))
|
||||
| ^^^^^^^^ PTH204
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
@@ -8,7 +8,6 @@ PTH205.py:6:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_c
|
||||
7 | os.path.getctime(b"filename")
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
PTH205.py:7:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
@@ -17,7 +16,6 @@ PTH205.py:7:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_c
|
||||
| ^^^^^^^^^^^^^^^^ PTH205
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
PTH205.py:8:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
@@ -28,7 +26,6 @@ PTH205.py:8:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_c
|
||||
9 |
|
||||
10 | getctime("filename")
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
PTH205.py:10:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
@@ -39,7 +36,6 @@ PTH205.py:10:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_
|
||||
11 | getctime(b"filename")
|
||||
12 | getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
PTH205.py:11:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
@@ -48,7 +44,6 @@ PTH205.py:11:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_
|
||||
| ^^^^^^^^ PTH205
|
||||
12 | getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
PTH205.py:12:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
@@ -57,4 +52,3 @@ PTH205.py:12:1: PTH205 `os.path.getctime` should be replaced by `Path.stat().st_
|
||||
12 | getctime(Path("filename"))
|
||||
| ^^^^^^^^ PTH205
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
@@ -4,7 +4,7 @@ source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
PTH202.py:10:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
10 | os.path.getsize("filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
|
|
||||
@@ -24,7 +24,7 @@ PTH202.py:11:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
10 | os.path.getsize("filename")
|
||||
11 | os.path.getsize(b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
13 | os.path.getsize(__file__)
|
||||
|
|
||||
@@ -45,7 +45,7 @@ PTH202.py:12:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
10 | os.path.getsize("filename")
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
13 | os.path.getsize(__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -65,7 +65,7 @@ PTH202.py:13:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
11 | os.path.getsize(b"filename")
|
||||
12 | os.path.getsize(Path("filename"))
|
||||
13 | os.path.getsize(__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
14 |
|
||||
15 | os.path.getsize(filename)
|
||||
|
|
||||
@@ -86,7 +86,7 @@ PTH202.py:15:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
13 | os.path.getsize(__file__)
|
||||
14 |
|
||||
15 | os.path.getsize(filename)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
16 | os.path.getsize(filename1)
|
||||
17 | os.path.getsize(filename2)
|
||||
|
|
||||
@@ -106,7 +106,7 @@ PTH202.py:16:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
15 | os.path.getsize(filename)
|
||||
16 | os.path.getsize(filename1)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
17 | os.path.getsize(filename2)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -126,7 +126,7 @@ PTH202.py:17:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
15 | os.path.getsize(filename)
|
||||
16 | os.path.getsize(filename1)
|
||||
17 | os.path.getsize(filename2)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
18 |
|
||||
19 | os.path.getsize(filename="filename")
|
||||
|
|
||||
@@ -147,7 +147,7 @@ PTH202.py:19:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
17 | os.path.getsize(filename2)
|
||||
18 |
|
||||
19 | os.path.getsize(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
|
|
||||
@@ -167,7 +167,7 @@ PTH202.py:20:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
19 | os.path.getsize(filename="filename")
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
@@ -188,7 +188,7 @@ PTH202.py:21:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
19 | os.path.getsize(filename="filename")
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -208,7 +208,7 @@ PTH202.py:22:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
20 | os.path.getsize(filename=b"filename")
|
||||
21 | os.path.getsize(filename=Path("filename"))
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
23 |
|
||||
24 | getsize("filename")
|
||||
|
|
||||
@@ -229,7 +229,7 @@ PTH202.py:24:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
22 | os.path.getsize(filename=__file__)
|
||||
23 |
|
||||
24 | getsize("filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
|
|
||||
@@ -249,7 +249,7 @@ PTH202.py:25:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
24 | getsize("filename")
|
||||
25 | getsize(b"filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
26 | getsize(Path("filename"))
|
||||
27 | getsize(__file__)
|
||||
|
|
||||
@@ -270,7 +270,7 @@ PTH202.py:26:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
24 | getsize("filename")
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
27 | getsize(__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -290,7 +290,7 @@ PTH202.py:27:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
25 | getsize(b"filename")
|
||||
26 | getsize(Path("filename"))
|
||||
27 | getsize(__file__)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^ PTH202
|
||||
28 |
|
||||
29 | getsize(filename="filename")
|
||||
|
|
||||
@@ -311,7 +311,7 @@ PTH202.py:29:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
27 | getsize(__file__)
|
||||
28 |
|
||||
29 | getsize(filename="filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
|
|
||||
@@ -331,7 +331,7 @@ PTH202.py:30:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
29 | getsize(filename="filename")
|
||||
30 | getsize(filename=b"filename")
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
31 | getsize(filename=Path("filename"))
|
||||
32 | getsize(filename=__file__)
|
||||
|
|
||||
@@ -352,7 +352,7 @@ PTH202.py:31:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
29 | getsize(filename="filename")
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
32 | getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -372,7 +372,7 @@ PTH202.py:32:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
30 | getsize(filename=b"filename")
|
||||
31 | getsize(filename=Path("filename"))
|
||||
32 | getsize(filename=__file__)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
33 |
|
||||
34 | getsize(filename)
|
||||
|
|
||||
@@ -393,7 +393,7 @@ PTH202.py:34:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
32 | getsize(filename=__file__)
|
||||
33 |
|
||||
34 | getsize(filename)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^ PTH202
|
||||
35 | getsize(filename1)
|
||||
36 | getsize(filename2)
|
||||
|
|
||||
@@ -413,7 +413,7 @@ PTH202.py:35:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
|
||||
34 | getsize(filename)
|
||||
35 | getsize(filename1)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH202
|
||||
36 | getsize(filename2)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -433,7 +433,7 @@ PTH202.py:36:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
34 | getsize(filename)
|
||||
35 | getsize(filename1)
|
||||
36 | getsize(filename2)
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -449,10 +449,12 @@ PTH202.py:36:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:39:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
39 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
40 | "filename", # comment
|
||||
41 | )
|
||||
39 | / os.path.getsize(
|
||||
40 | | "filename", # comment
|
||||
41 | | )
|
||||
| |_^ PTH202
|
||||
42 |
|
||||
43 | os.path.getsize(
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -470,12 +472,17 @@ PTH202.py:39:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:43:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
41 | )
|
||||
41 | )
|
||||
42 |
|
||||
43 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
44 | # comment
|
||||
45 | "filename"
|
||||
43 | / os.path.getsize(
|
||||
44 | | # comment
|
||||
45 | | "filename"
|
||||
46 | | ,
|
||||
47 | | # comment
|
||||
48 | | )
|
||||
| |_^ PTH202
|
||||
49 |
|
||||
50 | os.path.getsize(
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -496,12 +503,16 @@ PTH202.py:43:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:50:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
48 | )
|
||||
48 | )
|
||||
49 |
|
||||
50 | os.path.getsize(
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
51 | # comment
|
||||
52 | b"filename"
|
||||
50 | / os.path.getsize(
|
||||
51 | | # comment
|
||||
52 | | b"filename"
|
||||
53 | | # comment
|
||||
54 | | )
|
||||
| |_^ PTH202
|
||||
55 |
|
||||
56 | os.path.getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -521,12 +532,15 @@ PTH202.py:50:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:56:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
54 | )
|
||||
54 | )
|
||||
55 |
|
||||
56 | os.path.getsize( # comment
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
57 | Path(__file__)
|
||||
58 | # comment
|
||||
56 | / os.path.getsize( # comment
|
||||
57 | | Path(__file__)
|
||||
58 | | # comment
|
||||
59 | | ) # comment
|
||||
| |_^ PTH202
|
||||
60 |
|
||||
61 | getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -545,11 +559,13 @@ PTH202.py:56:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:61:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
59 | ) # comment
|
||||
59 | ) # comment
|
||||
60 |
|
||||
61 | getsize( # comment
|
||||
| ^^^^^^^ PTH202
|
||||
62 | "filename")
|
||||
61 | / getsize( # comment
|
||||
62 | | "filename")
|
||||
| |_______________^ PTH202
|
||||
63 |
|
||||
64 | getsize( # comment
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -566,12 +582,15 @@ PTH202.py:61:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:64:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
62 | "filename")
|
||||
62 | "filename")
|
||||
63 |
|
||||
64 | getsize( # comment
|
||||
| ^^^^^^^ PTH202
|
||||
65 | b"filename",
|
||||
66 | #comment
|
||||
64 | / getsize( # comment
|
||||
65 | | b"filename",
|
||||
66 | | #comment
|
||||
67 | | )
|
||||
| |_^ PTH202
|
||||
68 |
|
||||
69 | os.path.getsize("file" + "name")
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -593,7 +612,7 @@ PTH202.py:69:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
67 | )
|
||||
68 |
|
||||
69 | os.path.getsize("file" + "name")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
70 |
|
||||
71 | getsize \
|
||||
|
|
||||
@@ -611,12 +630,17 @@ PTH202.py:69:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
|
||||
PTH202.py:71:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().st_size`
|
||||
|
|
||||
69 | os.path.getsize("file" + "name")
|
||||
69 | os.path.getsize("file" + "name")
|
||||
70 |
|
||||
71 | getsize \
|
||||
| ^^^^^^^ PTH202
|
||||
72 | \
|
||||
73 | \
|
||||
71 | / getsize \
|
||||
72 | | \
|
||||
73 | | \
|
||||
74 | | ( # comment
|
||||
75 | | "filename",
|
||||
76 | | )
|
||||
| |_____^ PTH202
|
||||
77 |
|
||||
78 | getsize(Path("filename").resolve())
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
@@ -640,7 +664,7 @@ PTH202.py:78:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
76 | )
|
||||
77 |
|
||||
78 | getsize(Path("filename").resolve())
|
||||
| ^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
79 |
|
||||
80 | import pathlib
|
||||
|
|
||||
@@ -661,7 +685,7 @@ PTH202.py:82:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat().
|
||||
80 | import pathlib
|
||||
81 |
|
||||
82 | os.path.getsize(pathlib.Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ PTH202_2.py:3:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat()
|
||||
1 | import os
|
||||
2 |
|
||||
3 | os.path.getsize(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
@@ -25,7 +25,7 @@ PTH202_2.py:4:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat()
|
||||
|
|
||||
3 | os.path.getsize(filename="filename")
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
@@ -44,7 +44,7 @@ PTH202_2.py:5:1: PTH202 [*] `os.path.getsize` should be replaced by `Path.stat()
|
||||
3 | os.path.getsize(filename="filename")
|
||||
4 | os.path.getsize(filename=b"filename")
|
||||
5 | os.path.getsize(filename=__file__)
|
||||
| ^^^^^^^^^^^^^^^ PTH202
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PTH202
|
||||
|
|
||||
= help: Replace with `Path(...).stat().st_size`
|
||||
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH203.py:5:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
3 | from os.path import getatime
|
||||
4 |
|
||||
5 | os.path.getatime("filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
6 | os.path.getatime(b"filename")
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from pathlib import Path
|
||||
3 3 | from os.path import getatime
|
||||
4 4 |
|
||||
5 |-os.path.getatime("filename")
|
||||
5 |+Path("filename").stat().st_atime
|
||||
6 6 | os.path.getatime(b"filename")
|
||||
7 7 | os.path.getatime(Path("filename"))
|
||||
8 8 |
|
||||
|
||||
PTH203.py:6:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
5 | os.path.getatime("filename")
|
||||
6 | os.path.getatime(b"filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | from os.path import getatime
|
||||
4 4 |
|
||||
5 5 | os.path.getatime("filename")
|
||||
6 |-os.path.getatime(b"filename")
|
||||
6 |+Path(b"filename").stat().st_atime
|
||||
7 7 | os.path.getatime(Path("filename"))
|
||||
8 8 |
|
||||
9 9 |
|
||||
|
||||
PTH203.py:7:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
5 | os.path.getatime("filename")
|
||||
6 | os.path.getatime(b"filename")
|
||||
7 | os.path.getatime(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 | os.path.getatime("filename")
|
||||
6 6 | os.path.getatime(b"filename")
|
||||
7 |-os.path.getatime(Path("filename"))
|
||||
7 |+Path("filename").stat().st_atime
|
||||
8 8 |
|
||||
9 9 |
|
||||
10 10 | getatime("filename")
|
||||
|
||||
PTH203.py:10:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
10 | getatime("filename")
|
||||
| ^^^^^^^^ PTH203
|
||||
11 | getatime(b"filename")
|
||||
12 | getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | os.path.getatime(Path("filename"))
|
||||
8 8 |
|
||||
9 9 |
|
||||
10 |-getatime("filename")
|
||||
10 |+Path("filename").stat().st_atime
|
||||
11 11 | getatime(b"filename")
|
||||
12 12 | getatime(Path("filename"))
|
||||
13 13 |
|
||||
|
||||
PTH203.py:11:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
10 | getatime("filename")
|
||||
11 | getatime(b"filename")
|
||||
| ^^^^^^^^ PTH203
|
||||
12 | getatime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 |
|
||||
9 9 |
|
||||
10 10 | getatime("filename")
|
||||
11 |-getatime(b"filename")
|
||||
11 |+Path(b"filename").stat().st_atime
|
||||
12 12 | getatime(Path("filename"))
|
||||
13 13 |
|
||||
14 14 |
|
||||
|
||||
PTH203.py:12:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
10 | getatime("filename")
|
||||
11 | getatime(b"filename")
|
||||
12 | getatime(Path("filename"))
|
||||
| ^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | getatime("filename")
|
||||
11 11 | getatime(b"filename")
|
||||
12 |-getatime(Path("filename"))
|
||||
12 |+Path("filename").stat().st_atime
|
||||
13 13 |
|
||||
14 14 |
|
||||
15 15 | file = __file__
|
||||
|
||||
PTH203.py:17:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
15 | file = __file__
|
||||
16 |
|
||||
17 | os.path.getatime(file)
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
18 | os.path.getatime(filename="filename")
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
14 14 |
|
||||
15 15 | file = __file__
|
||||
16 16 |
|
||||
17 |-os.path.getatime(file)
|
||||
17 |+Path(file).stat().st_atime
|
||||
18 18 | os.path.getatime(filename="filename")
|
||||
19 19 | os.path.getatime(filename=Path("filename"))
|
||||
20 20 |
|
||||
|
||||
PTH203.py:18:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
17 | os.path.getatime(file)
|
||||
18 | os.path.getatime(filename="filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
15 15 | file = __file__
|
||||
16 16 |
|
||||
17 17 | os.path.getatime(file)
|
||||
18 |-os.path.getatime(filename="filename")
|
||||
18 |+Path("filename").stat().st_atime
|
||||
19 19 | os.path.getatime(filename=Path("filename"))
|
||||
20 20 |
|
||||
21 21 | os.path.getatime( # comment 1
|
||||
|
||||
PTH203.py:19:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
17 | os.path.getatime(file)
|
||||
18 | os.path.getatime(filename="filename")
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
20 |
|
||||
21 | os.path.getatime( # comment 1
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 |
|
||||
17 17 | os.path.getatime(file)
|
||||
18 18 | os.path.getatime(filename="filename")
|
||||
19 |-os.path.getatime(filename=Path("filename"))
|
||||
19 |+Path("filename").stat().st_atime
|
||||
20 20 |
|
||||
21 21 | os.path.getatime( # comment 1
|
||||
22 22 | # comment 2
|
||||
|
||||
PTH203.py:21:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
19 | os.path.getatime(filename=Path("filename"))
|
||||
20 |
|
||||
21 | os.path.getatime( # comment 1
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
22 | # comment 2
|
||||
23 | "filename" # comment 3
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Unsafe fix
|
||||
18 18 | os.path.getatime(filename="filename")
|
||||
19 19 | os.path.getatime(filename=Path("filename"))
|
||||
20 20 |
|
||||
21 |-os.path.getatime( # comment 1
|
||||
22 |- # comment 2
|
||||
23 |- "filename" # comment 3
|
||||
24 |- # comment 4
|
||||
25 |- , # comment 5
|
||||
26 |- # comment 6
|
||||
27 |-) # comment 7
|
||||
21 |+Path("filename").stat().st_atime # comment 7
|
||||
28 22 |
|
||||
29 23 | os.path.getatime("file" + "name")
|
||||
30 24 |
|
||||
|
||||
PTH203.py:29:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
27 | ) # comment 7
|
||||
28 |
|
||||
29 | os.path.getatime("file" + "name")
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
30 |
|
||||
31 | getatime(Path("filename").resolve())
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
26 26 | # comment 6
|
||||
27 27 | ) # comment 7
|
||||
28 28 |
|
||||
29 |-os.path.getatime("file" + "name")
|
||||
29 |+Path("file" + "name").stat().st_atime
|
||||
30 30 |
|
||||
31 31 | getatime(Path("filename").resolve())
|
||||
32 32 |
|
||||
|
||||
PTH203.py:31:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
29 | os.path.getatime("file" + "name")
|
||||
30 |
|
||||
31 | getatime(Path("filename").resolve())
|
||||
| ^^^^^^^^ PTH203
|
||||
32 |
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
28 28 |
|
||||
29 29 | os.path.getatime("file" + "name")
|
||||
30 30 |
|
||||
31 |-getatime(Path("filename").resolve())
|
||||
31 |+Path(Path("filename").resolve()).stat().st_atime
|
||||
32 32 |
|
||||
33 33 | os.path.getatime(pathlib.Path("filename"))
|
||||
34 34 |
|
||||
|
||||
PTH203.py:33:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
31 | getatime(Path("filename").resolve())
|
||||
32 |
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH203
|
||||
34 |
|
||||
35 | getatime(Path("dir") / "file.txt")
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
30 30 |
|
||||
31 31 | getatime(Path("filename").resolve())
|
||||
32 32 |
|
||||
33 |-os.path.getatime(pathlib.Path("filename"))
|
||||
33 |+pathlib.Path("filename").stat().st_atime
|
||||
34 34 |
|
||||
35 35 | getatime(Path("dir") / "file.txt")
|
||||
|
||||
PTH203.py:35:1: PTH203 [*] `os.path.getatime` should be replaced by `Path.stat().st_atime`
|
||||
|
|
||||
33 | os.path.getatime(pathlib.Path("filename"))
|
||||
34 |
|
||||
35 | getatime(Path("dir") / "file.txt")
|
||||
| ^^^^^^^^ PTH203
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_atime`
|
||||
|
||||
ℹ Safe fix
|
||||
32 32 |
|
||||
33 33 | os.path.getatime(pathlib.Path("filename"))
|
||||
34 34 |
|
||||
35 |-getatime(Path("dir") / "file.txt")
|
||||
35 |+Path(Path("dir") / "file.txt").stat().st_atime
|
||||
@@ -1,110 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH204.py:6:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
6 | os.path.getmtime("filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH204
|
||||
7 | os.path.getmtime(b"filename")
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | from os.path import getmtime
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 |-os.path.getmtime("filename")
|
||||
6 |+Path("filename").stat().st_mtime
|
||||
7 7 | os.path.getmtime(b"filename")
|
||||
8 8 | os.path.getmtime(Path("filename"))
|
||||
9 9 |
|
||||
|
||||
PTH204.py:7:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
6 | os.path.getmtime("filename")
|
||||
7 | os.path.getmtime(b"filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH204
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 6 | os.path.getmtime("filename")
|
||||
7 |-os.path.getmtime(b"filename")
|
||||
7 |+Path(b"filename").stat().st_mtime
|
||||
8 8 | os.path.getmtime(Path("filename"))
|
||||
9 9 |
|
||||
10 10 |
|
||||
|
||||
PTH204.py:8:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
6 | os.path.getmtime("filename")
|
||||
7 | os.path.getmtime(b"filename")
|
||||
8 | os.path.getmtime(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH204
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | os.path.getmtime("filename")
|
||||
7 7 | os.path.getmtime(b"filename")
|
||||
8 |-os.path.getmtime(Path("filename"))
|
||||
8 |+Path("filename").stat().st_mtime
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 11 | getmtime("filename")
|
||||
|
||||
PTH204.py:11:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
11 | getmtime("filename")
|
||||
| ^^^^^^^^ PTH204
|
||||
12 | getmtime(b"filename")
|
||||
13 | getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | os.path.getmtime(Path("filename"))
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 |-getmtime("filename")
|
||||
11 |+Path("filename").stat().st_mtime
|
||||
12 12 | getmtime(b"filename")
|
||||
13 13 | getmtime(Path("filename"))
|
||||
|
||||
PTH204.py:12:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
11 | getmtime("filename")
|
||||
12 | getmtime(b"filename")
|
||||
| ^^^^^^^^ PTH204
|
||||
13 | getmtime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 |
|
||||
11 11 | getmtime("filename")
|
||||
12 |-getmtime(b"filename")
|
||||
12 |+Path(b"filename").stat().st_mtime
|
||||
13 13 | getmtime(Path("filename"))
|
||||
|
||||
PTH204.py:13:1: PTH204 [*] `os.path.getmtime` should be replaced by `Path.stat().st_mtime`
|
||||
|
|
||||
11 | getmtime("filename")
|
||||
12 | getmtime(b"filename")
|
||||
13 | getmtime(Path("filename"))
|
||||
| ^^^^^^^^ PTH204
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_mtime`
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | getmtime("filename")
|
||||
12 12 | getmtime(b"filename")
|
||||
13 |-getmtime(Path("filename"))
|
||||
13 |+Path("filename").stat().st_mtime
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH205.py:6:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
6 | os.path.getctime("filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH205
|
||||
7 | os.path.getctime(b"filename")
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 | from os.path import getctime
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 |-os.path.getctime("filename")
|
||||
6 |+Path("filename").stat().st_ctime
|
||||
7 7 | os.path.getctime(b"filename")
|
||||
8 8 | os.path.getctime(Path("filename"))
|
||||
9 9 |
|
||||
|
||||
PTH205.py:7:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
6 | os.path.getctime("filename")
|
||||
7 | os.path.getctime(b"filename")
|
||||
| ^^^^^^^^^^^^^^^^ PTH205
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 |
|
||||
6 6 | os.path.getctime("filename")
|
||||
7 |-os.path.getctime(b"filename")
|
||||
7 |+Path(b"filename").stat().st_ctime
|
||||
8 8 | os.path.getctime(Path("filename"))
|
||||
9 9 |
|
||||
10 10 | getctime("filename")
|
||||
|
||||
PTH205.py:8:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
6 | os.path.getctime("filename")
|
||||
7 | os.path.getctime(b"filename")
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
| ^^^^^^^^^^^^^^^^ PTH205
|
||||
9 |
|
||||
10 | getctime("filename")
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 |
|
||||
6 6 | os.path.getctime("filename")
|
||||
7 7 | os.path.getctime(b"filename")
|
||||
8 |-os.path.getctime(Path("filename"))
|
||||
8 |+Path("filename").stat().st_ctime
|
||||
9 9 |
|
||||
10 10 | getctime("filename")
|
||||
11 11 | getctime(b"filename")
|
||||
|
||||
PTH205.py:10:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
8 | os.path.getctime(Path("filename"))
|
||||
9 |
|
||||
10 | getctime("filename")
|
||||
| ^^^^^^^^ PTH205
|
||||
11 | getctime(b"filename")
|
||||
12 | getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | os.path.getctime(b"filename")
|
||||
8 8 | os.path.getctime(Path("filename"))
|
||||
9 9 |
|
||||
10 |-getctime("filename")
|
||||
10 |+Path("filename").stat().st_ctime
|
||||
11 11 | getctime(b"filename")
|
||||
12 12 | getctime(Path("filename"))
|
||||
|
||||
PTH205.py:11:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
10 | getctime("filename")
|
||||
11 | getctime(b"filename")
|
||||
| ^^^^^^^^ PTH205
|
||||
12 | getctime(Path("filename"))
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | os.path.getctime(Path("filename"))
|
||||
9 9 |
|
||||
10 10 | getctime("filename")
|
||||
11 |-getctime(b"filename")
|
||||
11 |+Path(b"filename").stat().st_ctime
|
||||
12 12 | getctime(Path("filename"))
|
||||
|
||||
PTH205.py:12:1: PTH205 [*] `os.path.getctime` should be replaced by `Path.stat().st_ctime`
|
||||
|
|
||||
10 | getctime("filename")
|
||||
11 | getctime(b"filename")
|
||||
12 | getctime(Path("filename"))
|
||||
| ^^^^^^^^ PTH205
|
||||
|
|
||||
= help: Replace with `Path.stat(...).st_ctime`
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 |
|
||||
10 10 | getctime("filename")
|
||||
11 11 | getctime(b"filename")
|
||||
12 |-getctime(Path("filename"))
|
||||
12 |+Path("filename").stat().st_ctime
|
||||
@@ -24,14 +24,13 @@ use crate::checkers::ast::Checker;
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// with open("file", "rwx") as f:
|
||||
/// content = f.read()
|
||||
/// return f.read()
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
///
|
||||
/// ```python
|
||||
/// with open("file", "r") as f:
|
||||
/// content = f.read()
|
||||
/// return f.read()
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
|
||||
@@ -15,23 +15,21 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def bar():
|
||||
/// for x in foo:
|
||||
/// yield x
|
||||
/// for x in foo:
|
||||
/// yield x
|
||||
///
|
||||
/// global y
|
||||
/// for y in foo:
|
||||
/// yield y
|
||||
/// global y
|
||||
/// for y in foo:
|
||||
/// yield y
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def bar():
|
||||
/// yield from foo
|
||||
/// yield from foo
|
||||
///
|
||||
/// for _element in foo:
|
||||
/// y = _element
|
||||
/// yield y
|
||||
/// for _element in foo:
|
||||
/// y = _element
|
||||
/// yield y
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
|
||||
@@ -91,7 +91,7 @@ impl SequenceType {
|
||||
.map(Ranged::start)
|
||||
.unwrap_or(pattern.end()),
|
||||
)];
|
||||
let after_last_pattern = &source[TextRange::new(
|
||||
let after_last_patttern = &source[TextRange::new(
|
||||
pattern.start(),
|
||||
pattern
|
||||
.patterns
|
||||
@@ -100,7 +100,7 @@ impl SequenceType {
|
||||
.unwrap_or(pattern.end()),
|
||||
)];
|
||||
|
||||
if before_first_pattern.starts_with('[') && !after_last_pattern.ends_with(',') {
|
||||
if before_first_pattern.starts_with('[') && !after_last_patttern.ends_with(',') {
|
||||
SequenceType::List
|
||||
} else if before_first_pattern.starts_with('(') {
|
||||
// If the pattern is empty, it must be a parenthesized tuple with no members. (This
|
||||
|
||||
@@ -26,11 +26,8 @@ use self::traits::{NotificationHandler, RequestHandler};
|
||||
|
||||
use super::{Result, schedule::BackgroundSchedule};
|
||||
|
||||
/// Defines the `document_url` method for implementers of [`Notification`] and [`Request`], given
|
||||
/// the request or notification parameter type.
|
||||
///
|
||||
/// This would only work if the parameter type has a `text_document` field with a `uri` field
|
||||
/// that is of type [`lsp_types::Url`].
|
||||
/// Defines the `document_url` method for implementers of [`traits::Notification`] and [`traits::Request`],
|
||||
/// given the parameter type used by the implementer.
|
||||
macro_rules! define_document_url {
|
||||
($params:ident: &$p:ty) => {
|
||||
fn document_url($params: &$p) -> std::borrow::Cow<lsp_types::Url> {
|
||||
|
||||
@@ -1,31 +1,4 @@
|
||||
//! Traits for handling requests and notifications from the LSP client.
|
||||
//!
|
||||
//! This module defines the trait abstractions used by the language server to handle incoming
|
||||
//! requests and notifications from clients. It provides a type-safe way to implement LSP handlers
|
||||
//! with different execution models (synchronous or asynchronous) and automatic retry capabilities.
|
||||
//!
|
||||
//! All request and notification handlers must implement the base traits [`RequestHandler`] and
|
||||
//! [`NotificationHandler`], respectively, which associate them with specific LSP request or
|
||||
//! notification types. These base traits are then extended by more specific traits that define
|
||||
//! the execution model of the handler.
|
||||
//!
|
||||
//! The [`SyncRequestHandler`] and [`SyncNotificationHandler`] traits are for handlers that
|
||||
//! executes synchronously on the main loop, providing mutable access to the [`Session`] that
|
||||
//! contains the current state of the server. This is useful for handlers that need to modify
|
||||
//! the server state such as when the content of a file changes.
|
||||
//!
|
||||
//! The [`BackgroundDocumentRequestHandler`] and [`BackgroundDocumentNotificationHandler`] traits
|
||||
//! are for handlers that operate on a single document and can be executed on a background thread.
|
||||
//! These handlers will have access to a snapshot of the document at the time of the request or
|
||||
//! notification, allowing them to perform operations without blocking the main loop.
|
||||
//!
|
||||
//! The [`SyncNotificationHandler`] is the most common trait that would be used because most
|
||||
//! notifications are specific to a single document and require updating the server state.
|
||||
//! Similarly, the [`BackgroundDocumentRequestHandler`] is the most common request handler that
|
||||
//! would be used as most requests are document-specific and can be executed in the background.
|
||||
//!
|
||||
//! See the `./requests` and `./notifications` directories for concrete implementations of these
|
||||
//! traits in action.
|
||||
//! A stateful LSP implementation that calls into the Ruff API.
|
||||
|
||||
use crate::session::{Client, DocumentSnapshot, Session};
|
||||
|
||||
@@ -39,10 +12,9 @@ pub(super) trait RequestHandler {
|
||||
}
|
||||
|
||||
/// A request handler that needs mutable access to the session.
|
||||
///
|
||||
/// This will block the main message receiver loop, meaning that no incoming requests or
|
||||
/// notifications will be handled while `run` is executing. Try to avoid doing any I/O or
|
||||
/// long-running computations.
|
||||
/// This will block the main message receiver loop, meaning that no
|
||||
/// incoming requests or notifications will be handled while `run` is
|
||||
/// executing. Try to avoid doing any I/O or long-running computations.
|
||||
pub(super) trait SyncRequestHandler: RequestHandler {
|
||||
fn run(
|
||||
session: &mut Session,
|
||||
@@ -52,14 +24,10 @@ pub(super) trait SyncRequestHandler: RequestHandler {
|
||||
}
|
||||
|
||||
/// A request handler that can be run on a background thread.
|
||||
///
|
||||
/// This handler is specific to requests that operate on a single document.
|
||||
pub(super) trait BackgroundDocumentRequestHandler: RequestHandler {
|
||||
/// Returns the URL of the document that this request handler operates on.
|
||||
///
|
||||
/// This method can be implemented automatically using the [`define_document_url`] macro.
|
||||
///
|
||||
/// [`define_document_url`]: super::define_document_url
|
||||
/// `document_url` can be implemented automatically with
|
||||
/// `define_document_url!(params: &<YourParameterType>)` in the trait
|
||||
/// implementation.
|
||||
fn document_url(
|
||||
params: &<<Self as RequestHandler>::RequestType as Request>::Params,
|
||||
) -> std::borrow::Cow<lsp_types::Url>;
|
||||
@@ -79,10 +47,9 @@ pub(super) trait NotificationHandler {
|
||||
}
|
||||
|
||||
/// A notification handler that needs mutable access to the session.
|
||||
///
|
||||
/// This will block the main message receiver loop, meaning that no incoming requests or
|
||||
/// notifications will be handled while `run` is executing. Try to avoid doing any I/O or
|
||||
/// long-running computations.
|
||||
/// This will block the main message receiver loop, meaning that no
|
||||
/// incoming requests or notifications will be handled while `run` is
|
||||
/// executing. Try to avoid doing any I/O or long-running computations.
|
||||
pub(super) trait SyncNotificationHandler: NotificationHandler {
|
||||
fn run(
|
||||
session: &mut Session,
|
||||
@@ -93,11 +60,9 @@ pub(super) trait SyncNotificationHandler: NotificationHandler {
|
||||
|
||||
/// A notification handler that can be run on a background thread.
|
||||
pub(super) trait BackgroundDocumentNotificationHandler: NotificationHandler {
|
||||
/// Returns the URL of the document that this notification handler operates on.
|
||||
///
|
||||
/// This method can be implemented automatically using the [`define_document_url`] macro.
|
||||
///
|
||||
/// [`define_document_url`]: super::define_document_url
|
||||
/// `document_url` can be implemented automatically with
|
||||
/// `define_document_url!(params: &<YourParameterType>)` in the trait
|
||||
/// implementation.
|
||||
fn document_url(
|
||||
params: &<<Self as NotificationHandler>::NotificationType as LSPNotification>::Params,
|
||||
) -> std::borrow::Cow<lsp_types::Url>;
|
||||
|
||||
1
crates/ty/docs/configuration.md
generated
1
crates/ty/docs/configuration.md
generated
@@ -297,7 +297,6 @@ possibly-unresolved-reference = "ignore"
|
||||
A list of file and directory patterns to exclude from type checking.
|
||||
|
||||
Patterns follow a syntax similar to `.gitignore`:
|
||||
|
||||
- `./src/` matches only a directory
|
||||
- `./src` matches both files and directories
|
||||
- `src` matches files or directories named `src`
|
||||
|
||||
@@ -296,11 +296,6 @@ impl MainLoop {
|
||||
tracing::warn!("No python files found under the given path(s)");
|
||||
}
|
||||
|
||||
// TODO: We should have an official flag to silence workspace diagnostics.
|
||||
if std::env::var("TY_MEMORY_REPORT").as_deref() == Ok("mypy_primer") {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let mut stdout = stdout().lock();
|
||||
|
||||
if result.is_empty() {
|
||||
|
||||
@@ -11,7 +11,6 @@ repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bitflags = { workspace = true }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_python_parser = { workspace = true }
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::find_node::covering_node;
|
||||
use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue};
|
||||
use crate::{Db, HasNavigationTargets, NavigationTarget, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
use ty_python_semantic::{HasDefinition, HasType, SemanticModel};
|
||||
|
||||
pub fn goto_type_definition(
|
||||
db: &dyn Db,
|
||||
@@ -29,6 +30,35 @@ pub fn goto_type_definition(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn goto_definition(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let definitions = goto_target.definitions(&model)?;
|
||||
|
||||
tracing::debug!("Definitions of covering node is found");
|
||||
|
||||
let targets = definitions.into_iter().map(|definition| {
|
||||
let full_range = definition.full_range(db, &module);
|
||||
|
||||
NavigationTarget {
|
||||
file: full_range.file(),
|
||||
focus_range: definition.focus_range(db, &module).range(),
|
||||
full_range: full_range.range(),
|
||||
}
|
||||
});
|
||||
|
||||
Some(RangedValue {
|
||||
range: FileRange::new(file, goto_target.range()),
|
||||
value: NavigationTargets::unique(targets),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum GotoTarget<'a> {
|
||||
Expression(ast::ExprRef<'a>),
|
||||
@@ -154,6 +184,16 @@ impl GotoTarget<'_> {
|
||||
|
||||
Some(ty)
|
||||
}
|
||||
|
||||
pub(crate) fn definitions<'db>(
|
||||
self,
|
||||
model: &SemanticModel<'db>,
|
||||
) -> Option<Vec<Definition<'db>>> {
|
||||
match self {
|
||||
GotoTarget::Expression(expr_ref) => expr_ref.definitions(model),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for GotoTarget<'_> {
|
||||
@@ -254,7 +294,7 @@ pub(crate) fn find_goto_target(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{CursorTest, IntoDiagnostic, cursor_test};
|
||||
use crate::{NavigationTarget, goto_type_definition};
|
||||
use crate::{NavigationTarget, goto_definition, goto_type_definition};
|
||||
use insta::assert_snapshot;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
||||
@@ -792,13 +832,13 @@ f(**kwargs<CURSOR>)
|
||||
|
||||
assert_snapshot!(test.goto_type_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> stdlib/types.pyi:691:11
|
||||
--> stdlib/types.pyi:689:11
|
||||
|
|
||||
689 | if sys.version_info >= (3, 10):
|
||||
690 | @final
|
||||
691 | class NoneType:
|
||||
687 | if sys.version_info >= (3, 10):
|
||||
688 | @final
|
||||
689 | class NoneType:
|
||||
| ^^^^^^^^
|
||||
692 | def __bool__(self) -> Literal[False]: ...
|
||||
690 | def __bool__(self) -> Literal[False]: ...
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:17
|
||||
@@ -828,6 +868,516 @@ f(**kwargs<CURSOR>)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_function_call() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def ab(a, b): ...
|
||||
|
||||
a<CURSOR>b(1, 2)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:17
|
||||
|
|
||||
2 | def ab(a, b): ...
|
||||
| ^^
|
||||
3 |
|
||||
4 | ab(1, 2)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:13
|
||||
|
|
||||
2 | def ab(a, b): ...
|
||||
3 |
|
||||
4 | ab(1, 2)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | ab = 1
|
||||
| ^^
|
||||
3 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:19
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_rebind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
ab = 2
|
||||
ab = 3
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:13
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | ab = 2
|
||||
4 | ab = 3
|
||||
| ^^
|
||||
5 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | ab = 2
|
||||
4 | ab = 3
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_cond_rebind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
if cond:
|
||||
ab = 2
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | ab = 1
|
||||
| ^^
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:17
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
| ^^
|
||||
5 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_exhaustive_bind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
if cond:
|
||||
ab = 2
|
||||
else:
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:3:17
|
||||
|
|
||||
2 | if cond:
|
||||
3 | ab = 2
|
||||
| ^^
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:19
|
||||
|
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
6 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | ab = 2
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
| ^^
|
||||
6 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:19
|
||||
|
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
6 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_only_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_exhaustive_bind_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
if cond:
|
||||
ab = 2
|
||||
else:
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:17
|
||||
|
|
||||
2 | ab: int
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
| ^^
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
7 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | ab = 2
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
| ^^
|
||||
7 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
7 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_bind_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:3:13
|
||||
|
|
||||
2 | ab: int
|
||||
3 | ab = 1
|
||||
| ^^
|
||||
4 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:19
|
||||
|
|
||||
2 | ab: int
|
||||
3 | ab = 1
|
||||
4 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_first_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a<CURSOR>b = 1
|
||||
print(ab)
|
||||
ab = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_second_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(ab)
|
||||
a<CURSOR>b = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_loadstore() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(ab)
|
||||
a<CURSOR>b += 2
|
||||
print(ab)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = A<CURSOR>B(5)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:19
|
||||
|
|
||||
2 | class AB:
|
||||
| ^^
|
||||
3 | def __init__(self, val: int):
|
||||
4 | self.myval = val
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | self.myval = val
|
||||
5 |
|
||||
6 | x = AB(5)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_implicit_instance_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = AB(5)
|
||||
print(x.my<CURSOR>val)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_explicit_instance_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
myval: int
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = AB(5)
|
||||
print(x.my<CURSOR>val)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_path_parent() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
xyz = AB(5)
|
||||
print(x<CURSOR>yz.myval)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:6:13
|
||||
|
|
||||
4 | self.myval = val
|
||||
5 |
|
||||
6 | xyz = AB(5)
|
||||
| ^^^
|
||||
7 | print(xyz.myval)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
6 | xyz = AB(5)
|
||||
7 | print(xyz.myval)
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_class_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
RED = "red"
|
||||
BLUE = "blue"
|
||||
|
||||
x = AB.RE<CURSOR>D
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_path_parent() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
RED = "red"
|
||||
BLUE = "blue"
|
||||
|
||||
x = A<CURSOR>B.RED
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:19
|
||||
|
|
||||
2 | class AB:
|
||||
| ^^
|
||||
3 | RED = "red"
|
||||
4 | BLUE = "blue"
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | BLUE = "blue"
|
||||
5 |
|
||||
6 | x = AB.RED
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_load() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global ab
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global ab
|
||||
a<CURSOR>b = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_type_definition(&self) -> String {
|
||||
let Some(targets) =
|
||||
@@ -847,6 +1397,24 @@ f(**kwargs<CURSOR>)
|
||||
.map(|target| GotoTypeDefinitionDiagnostic::new(source, &target)),
|
||||
)
|
||||
}
|
||||
|
||||
fn goto_definition(&self) -> String {
|
||||
let Some(targets) = goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
||||
else {
|
||||
return "No goto target found".to_string();
|
||||
};
|
||||
|
||||
if targets.is_empty() {
|
||||
return "No definitions found".to_string();
|
||||
}
|
||||
|
||||
let source = targets.range;
|
||||
self.render_diagnostics(
|
||||
targets
|
||||
.into_iter()
|
||||
.map(|target| GotoTypeDefinitionDiagnostic::new(source, &target)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct GotoTypeDefinitionDiagnostic {
|
||||
|
||||
@@ -5,17 +5,13 @@ mod goto;
|
||||
mod hover;
|
||||
mod inlay_hints;
|
||||
mod markup;
|
||||
mod semantic_tokens;
|
||||
|
||||
pub use completion::completion;
|
||||
pub use db::Db;
|
||||
pub use goto::goto_type_definition;
|
||||
pub use goto::{goto_definition, goto_type_definition};
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::inlay_hints;
|
||||
pub use markup::MarkupKind;
|
||||
pub use semantic_tokens::{
|
||||
SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, semantic_tokens,
|
||||
};
|
||||
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -324,8 +324,8 @@ impl SalsaMemoryDump {
|
||||
struct DisplayShort<'a>(&'a SalsaMemoryDump);
|
||||
|
||||
fn round_memory(total: usize) -> usize {
|
||||
// Round the number to the nearest power of 1.05. This gives us a
|
||||
// 2.5% threshold before the memory usage number is considered to have
|
||||
// Round the number to the nearest power of 1.1. This gives us a
|
||||
// 5% threshold before the memory usage number is considered to have
|
||||
// changed.
|
||||
//
|
||||
// TODO: Small changes in memory usage may cause the number to be rounded
|
||||
@@ -334,7 +334,7 @@ impl SalsaMemoryDump {
|
||||
// over time that are unrelated to the current change. Ideally we could compare
|
||||
// the exact numbers across runs and compute the difference, but we don't have
|
||||
// the infrastructure for that currently.
|
||||
const BASE: f64 = 1.05;
|
||||
const BASE: f64 = 1.1;
|
||||
BASE.powf(bytes_to_mb(total).log(BASE).round()) as usize
|
||||
}
|
||||
|
||||
|
||||
@@ -577,7 +577,6 @@ pub struct SrcOptions {
|
||||
/// A list of file and directory patterns to exclude from type checking.
|
||||
///
|
||||
/// Patterns follow a syntax similar to `.gitignore`:
|
||||
///
|
||||
/// - `./src/` matches only a directory
|
||||
/// - `./src` matches both files and directories
|
||||
/// - `src` matches files or directories named `src`
|
||||
|
||||
@@ -343,7 +343,7 @@ def _(c: Callable[[int, Unpack[Ts]], int]):
|
||||
from typing import Callable
|
||||
|
||||
def _(c: Callable[[int], int]):
|
||||
reveal_type(c.__init__) # revealed: bound method object.__init__() -> None
|
||||
reveal_type(c.__init__) # revealed: def __init__(self) -> None
|
||||
reveal_type(c.__class__) # revealed: type
|
||||
reveal_type(c.__call__) # revealed: (int, /) -> int
|
||||
```
|
||||
|
||||
@@ -37,7 +37,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
|
||||
# See https://github.com/astral-sh/ruff/issues/15960 for a related discussion.
|
||||
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
|
||||
|
||||
reveal_type(c_instance.declared_only) # revealed: bytes
|
||||
# TODO: Should be `bytes` with no error, like mypy and pyright?
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.declared_only) # revealed: Unknown
|
||||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
|
||||
@@ -56,9 +58,6 @@ c_instance.declared_and_bound = "incompatible"
|
||||
# error: [unresolved-attribute] "Attribute `inferred_from_value` can only be accessed on instances, not on the class object `<class 'C'>` itself."
|
||||
reveal_type(C.inferred_from_value) # revealed: Unknown
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(C.declared_and_bound) # revealed: Unknown
|
||||
|
||||
# mypy shows no error here, but pyright raises "reportAttributeAccessIssue"
|
||||
# error: [invalid-attribute-access] "Cannot assign to instance attribute `inferred_from_value` from the class object `<class 'C'>`"
|
||||
C.inferred_from_value = "overwritten on class"
|
||||
@@ -144,14 +143,16 @@ class C:
|
||||
c_instance = C(True)
|
||||
|
||||
reveal_type(c_instance.only_declared_in_body) # revealed: str | None
|
||||
reveal_type(c_instance.only_declared_in_init) # revealed: str | None
|
||||
# TODO: should be `str | None` without error
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.only_declared_in_init) # revealed: Unknown
|
||||
reveal_type(c_instance.declared_in_body_and_init) # revealed: str | None
|
||||
|
||||
reveal_type(c_instance.declared_in_body_defined_in_init) # revealed: str | None
|
||||
|
||||
# TODO: This should be `str | None`. Fixing this requires an overhaul of the `Symbol` API,
|
||||
# which is planned in https://github.com/astral-sh/ruff/issues/14297
|
||||
reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | str | None
|
||||
reveal_type(c_instance.bound_in_body_declared_in_init) # revealed: Unknown | Literal["a"]
|
||||
|
||||
reveal_type(c_instance.bound_in_body_and_init) # revealed: Unknown | None | Literal["a"]
|
||||
```
|
||||
@@ -182,7 +183,9 @@ reveal_type(c_instance.inferred_from_other_attribute) # revealed: Unknown
|
||||
|
||||
reveal_type(c_instance.inferred_from_param) # revealed: Unknown | int | None
|
||||
|
||||
reveal_type(c_instance.declared_only) # revealed: bytes
|
||||
# TODO: should be `bytes` with no error, like mypy and pyright?
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.declared_only) # revealed: Unknown
|
||||
|
||||
reveal_type(c_instance.declared_and_bound) # revealed: bool
|
||||
|
||||
@@ -689,14 +692,16 @@ class C:
|
||||
|
||||
reveal_type(C.pure_class_variable1) # revealed: str
|
||||
|
||||
reveal_type(C.pure_class_variable2) # revealed: Unknown | Literal[1]
|
||||
# TODO: Should be `Unknown | Literal[1]`.
|
||||
reveal_type(C.pure_class_variable2) # revealed: Unknown
|
||||
|
||||
c_instance = C()
|
||||
|
||||
# It is okay to access a pure class variable on an instance.
|
||||
reveal_type(c_instance.pure_class_variable1) # revealed: str
|
||||
|
||||
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown | Literal[1]
|
||||
# TODO: Should be `Unknown | Literal[1]`.
|
||||
reveal_type(c_instance.pure_class_variable2) # revealed: Unknown
|
||||
|
||||
# error: [invalid-attribute-access] "Cannot assign to ClassVar `pure_class_variable1` from an instance of type `C`"
|
||||
c_instance.pure_class_variable1 = "value set on instance"
|
||||
@@ -712,24 +717,6 @@ class Subclass(C):
|
||||
reveal_type(Subclass.pure_class_variable1) # revealed: str
|
||||
```
|
||||
|
||||
If a class variable is additionally qualified as `Final`, we do not union with `Unknown` for bare
|
||||
`ClassVar`s:
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
class D:
|
||||
final1: Final[ClassVar] = 1
|
||||
final2: ClassVar[Final] = 1
|
||||
final3: ClassVar[Final[int]] = 1
|
||||
final4: Final[ClassVar[int]] = 1
|
||||
|
||||
reveal_type(D.final1) # revealed: Literal[1]
|
||||
reveal_type(D.final2) # revealed: Literal[1]
|
||||
reveal_type(D.final3) # revealed: int
|
||||
reveal_type(D.final4) # revealed: int
|
||||
```
|
||||
|
||||
#### Variable only mentioned in a class method
|
||||
|
||||
We also consider a class variable to be a pure class variable if it is only mentioned in a class
|
||||
@@ -1787,7 +1774,7 @@ date.day = 8
|
||||
date.month = 4
|
||||
date.year = 2025
|
||||
|
||||
# error: [unresolved-attribute] "Can not assign object of type `Literal["UTC"]` to attribute `tz` on type `Date` with custom `__setattr__` method."
|
||||
# error: [unresolved-attribute] "Can not assign object of `Literal["UTC"]` to attribute `tz` on type `Date` with custom `__setattr__` method."
|
||||
date.tz = "UTC"
|
||||
```
|
||||
|
||||
|
||||
@@ -201,36 +201,6 @@ type IntOrStr = int | str
|
||||
reveal_type(IntOrStr.__or__) # revealed: bound method typing.TypeAliasType.__or__(right: Any) -> _SpecialForm
|
||||
```
|
||||
|
||||
## Method calls on types not disjoint from `None`
|
||||
|
||||
Very few methods are defined on `object`, `None`, and other types not disjoint from `None`. However,
|
||||
descriptor-binding behaviour works on these types in exactly the same way as descriptor binding on
|
||||
other types. This is despite the fact that `None` is used as a sentinel internally by the descriptor
|
||||
protocol to indicate that a method was accessed on the class itself rather than an instance of the
|
||||
class:
|
||||
|
||||
```py
|
||||
from typing import Protocol, Literal
|
||||
from ty_extensions import AlwaysFalsy
|
||||
|
||||
class Foo: ...
|
||||
|
||||
class SupportsStr(Protocol):
|
||||
def __str__(self) -> str: ...
|
||||
|
||||
class Falsy(Protocol):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | None):
|
||||
a.__str__()
|
||||
b.__str__()
|
||||
c.__str__()
|
||||
d.__str__()
|
||||
# TODO: these should not error
|
||||
e.__str__() # error: [missing-argument]
|
||||
f.__str__() # error: [missing-argument]
|
||||
```
|
||||
|
||||
## Error cases: Calling `__get__` for methods
|
||||
|
||||
The `__get__` method on `types.FunctionType` has the following overloaded signature in typeshed:
|
||||
@@ -264,18 +234,16 @@ method_wrapper(C())
|
||||
method_wrapper(C(), None)
|
||||
method_wrapper(None, C)
|
||||
|
||||
reveal_type(object.__str__.__get__(object(), None)()) # revealed: str
|
||||
|
||||
# TODO: passing `None` without an `owner` argument fails at runtime.
|
||||
# Ideally we would emit a diagnostic here:
|
||||
# Passing `None` without an `owner` argument is an
|
||||
# error: [invalid-argument-type] "Argument to method wrapper `__get__` of function `f` is incorrect: Expected `~None`, found `None`"
|
||||
method_wrapper(None)
|
||||
|
||||
# Passing something that is not assignable to `type` as the `owner` argument is an
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None, 1)
|
||||
|
||||
# TODO: passing `None` as the `owner` argument when `instance` is `None` fails at runtime.
|
||||
# Ideally we would emit a diagnostic here.
|
||||
# Passing `None` as the `owner` argument when `instance` is `None` is an
|
||||
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
|
||||
method_wrapper(None, None)
|
||||
|
||||
# Calling `__get__` without any arguments is an
|
||||
|
||||
@@ -444,33 +444,6 @@ To do
|
||||
|
||||
To do
|
||||
|
||||
## `Final` fields
|
||||
|
||||
Dataclass fields can be annotated with `Final`, which means that the field cannot be reassigned
|
||||
after the instance is created. Fields that are additionally annotated with `ClassVar` are not part
|
||||
of the `__init__` signature.
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
from typing import Final, ClassVar
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
# a `Final` annotation without a right-hand side is not allowed in normal classes,
|
||||
# but valid for dataclasses. The field will be initialized in the synthesized
|
||||
# `__init__` method
|
||||
instance_variable_no_default: Final[int]
|
||||
instance_variable: Final[int] = 1
|
||||
class_variable1: ClassVar[Final[int]] = 1
|
||||
class_variable2: ClassVar[Final[int]] = 1
|
||||
|
||||
reveal_type(C.__init__) # revealed: (self: C, instance_variable_no_default: int, instance_variable: int = Literal[1]) -> None
|
||||
|
||||
c = C(1)
|
||||
# TODO: this should be an error
|
||||
c.instance_variable = 2
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
### Normal class inheriting from a dataclass
|
||||
|
||||
@@ -619,9 +619,8 @@ wrapper_descriptor()
|
||||
# error: [no-matching-overload] "No overload of wrapper descriptor `FunctionType.__get__` matches arguments"
|
||||
wrapper_descriptor(f)
|
||||
|
||||
# TODO: Calling it without the `owner` argument if `instance` is not `None` fails at runtime.
|
||||
# Ideally we would emit a diagnostic here,
|
||||
# but this is hard to model without introducing false positives elsewhere
|
||||
# Calling it without the `owner` argument if `instance` is not `None` is an
|
||||
# error: [invalid-argument-type] "Argument to wrapper descriptor `FunctionType.__get__` is incorrect: Expected `~None`, found `None`"
|
||||
wrapper_descriptor(f, None)
|
||||
|
||||
# But calling it with an instance is fine (in this case, the `owner` argument is optional):
|
||||
|
||||
@@ -2,53 +2,27 @@
|
||||
|
||||
## Basic functionality
|
||||
|
||||
`assert_never` makes sure that the type of the argument is `Never`.
|
||||
|
||||
### Correct usage
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def _(never: Never):
|
||||
assert_never(never) # fine
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
If it is not, a `type-assertion-failure` diagnostic is emitted.
|
||||
`assert_never` makes sure that the type of the argument is `Never`. If it is not, a
|
||||
`type-assertion-failure` diagnostic is emitted.
|
||||
|
||||
```py
|
||||
from typing_extensions import assert_never, Never, Any
|
||||
from ty_extensions import Unknown
|
||||
|
||||
def _():
|
||||
def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||
assert_never(never) # fine
|
||||
|
||||
assert_never(0) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never("") # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never(None) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never([]) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never({}) # error: [type-assertion-failure]
|
||||
|
||||
def _():
|
||||
assert_never(()) # error: [type-assertion-failure]
|
||||
|
||||
def _(flag: bool, never: Never):
|
||||
assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
||||
def _(any_: Any):
|
||||
assert_never(any_) # error: [type-assertion-failure]
|
||||
|
||||
def _(unknown: Unknown):
|
||||
assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
|
||||
@@ -205,18 +205,3 @@ python-version = "3.13"
|
||||
import aifc # error: [unresolved-import]
|
||||
from distutils import sysconfig # error: [unresolved-import]
|
||||
```
|
||||
|
||||
## Cannot shadow core standard library modules
|
||||
|
||||
`types.py`:
|
||||
|
||||
```py
|
||||
x: int
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
from types import x
|
||||
|
||||
from types import FunctionType
|
||||
```
|
||||
|
||||
@@ -1476,7 +1476,8 @@ class P1(Protocol):
|
||||
class P2(Protocol):
|
||||
def x(self, y: int) -> None: ...
|
||||
|
||||
static_assert(is_equivalent_to(P1, P2))
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(P1, P2)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
As with protocols that only have non-method members, this also holds true when they appear in
|
||||
@@ -1486,7 +1487,8 @@ differently ordered unions:
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
static_assert(is_equivalent_to(A | B | P1, P2 | B | A))
|
||||
# TODO: this should pass
|
||||
static_assert(is_equivalent_to(A | B | P1, P2 | B | A)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## Narrowing of protocols
|
||||
@@ -1860,21 +1862,6 @@ class Bar(Protocol):
|
||||
static_assert(is_equivalent_to(Foo, Bar))
|
||||
```
|
||||
|
||||
### Disjointness of recursive protocol and recursive final type
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from ty_extensions import is_disjoint_from, static_assert
|
||||
|
||||
class Proto(Protocol):
|
||||
x: "Proto"
|
||||
|
||||
class Nominal:
|
||||
x: "Nominal"
|
||||
|
||||
static_assert(not is_disjoint_from(Proto, Nominal))
|
||||
```
|
||||
|
||||
### Regression test: narrowing with self-referential protocols
|
||||
|
||||
This snippet caused us to panic on an early version of the implementation for protocols.
|
||||
@@ -1894,86 +1881,6 @@ if isinstance(obj, (B, A)):
|
||||
reveal_type(obj) # revealed: (Unknown & B) | (Unknown & A)
|
||||
```
|
||||
|
||||
### Protocols that use `Self`
|
||||
|
||||
`Self` is a `TypeVar` with an upper bound of the class in which it is defined. This means that
|
||||
`Self` annotations in protocols can also be tricky to handle without infinite recursion and stack
|
||||
overflows.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol, Self
|
||||
from ty_extensions import static_assert
|
||||
|
||||
class _HashObject(Protocol):
|
||||
def copy(self) -> Self: ...
|
||||
|
||||
class Foo: ...
|
||||
|
||||
# Attempting to build this union caused us to overflow on an early version of
|
||||
# <https://github.com/astral-sh/ruff/pull/18659>
|
||||
x: Foo | _HashObject
|
||||
```
|
||||
|
||||
Some other similar cases that caused issues in our early `Protocol` implementation:
|
||||
|
||||
`a.py`:
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol, Self
|
||||
|
||||
class PGconn(Protocol):
|
||||
def connect(self) -> Self: ...
|
||||
|
||||
class Connection:
|
||||
pgconn: PGconn
|
||||
|
||||
def is_crdb(conn: PGconn) -> bool:
|
||||
return isinstance(conn, Connection)
|
||||
```
|
||||
|
||||
and:
|
||||
|
||||
`b.py`:
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol
|
||||
|
||||
class PGconn(Protocol):
|
||||
def connect[T: PGconn](self: T) -> T: ...
|
||||
|
||||
class Connection:
|
||||
pgconn: PGconn
|
||||
|
||||
def f(x: PGconn):
|
||||
isinstance(x, Connection)
|
||||
```
|
||||
|
||||
### Recursive protocols used as the first argument to `cast()`
|
||||
|
||||
These caused issues in an early version of our `Protocol` implementation due to the fact that we use
|
||||
a recursive function in our `cast()` implementation to check whether a type contains `Unknown` or
|
||||
`Todo`. Recklessly recursing into a type causes stack overflows if the type is recursive:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import cast, Protocol
|
||||
|
||||
class Iterator[T](Protocol):
|
||||
def __iter__(self) -> Iterator[T]: ...
|
||||
|
||||
def f(value: Iterator):
|
||||
cast(Iterator, value) # error: [redundant-cast]
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
Add tests for:
|
||||
|
||||
@@ -3,7 +3,7 @@ source: crates/ty_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: assert_never.md - `assert_never` - Basic functionality - Diagnostics
|
||||
mdtest name: assert_never.md - `assert_never` - Basic functionality
|
||||
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.md
|
||||
---
|
||||
|
||||
@@ -15,47 +15,35 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/directives/assert_never.
|
||||
1 | from typing_extensions import assert_never, Never, Any
|
||||
2 | from ty_extensions import Unknown
|
||||
3 |
|
||||
4 | def _():
|
||||
5 | assert_never(0) # error: [type-assertion-failure]
|
||||
4 | def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||
5 | assert_never(never) # fine
|
||||
6 |
|
||||
7 | def _():
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 |
|
||||
10 | def _():
|
||||
11 | assert_never(None) # error: [type-assertion-failure]
|
||||
12 |
|
||||
13 | def _():
|
||||
14 | assert_never([]) # error: [type-assertion-failure]
|
||||
15 |
|
||||
16 | def _():
|
||||
17 | assert_never({}) # error: [type-assertion-failure]
|
||||
18 |
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
21 |
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:5:5
|
||||
--> src/mdtest_snippet.py:7:5
|
||||
|
|
||||
4 | def _():
|
||||
5 | assert_never(0) # error: [type-assertion-failure]
|
||||
5 | assert_never(never) # fine
|
||||
6 |
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[0]`
|
||||
6 |
|
||||
7 | def _():
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[0]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -66,13 +54,13 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:8:5
|
||||
|
|
||||
7 | def _():
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[""]`
|
||||
9 |
|
||||
10 | def _():
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[""]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -81,15 +69,16 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
--> src/mdtest_snippet.py:9:5
|
||||
|
|
||||
10 | def _():
|
||||
11 | assert_never(None) # error: [type-assertion-failure]
|
||||
7 | assert_never(0) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `None`
|
||||
12 |
|
||||
13 | def _():
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `None` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -98,15 +87,16 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:14:5
|
||||
--> src/mdtest_snippet.py:10:5
|
||||
|
|
||||
13 | def _():
|
||||
14 | assert_never([]) # error: [type-assertion-failure]
|
||||
8 | assert_never("") # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `list[Unknown]`
|
||||
15 |
|
||||
16 | def _():
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `list[Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -115,15 +105,16 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:17:5
|
||||
--> src/mdtest_snippet.py:11:5
|
||||
|
|
||||
16 | def _():
|
||||
17 | assert_never({}) # error: [type-assertion-failure]
|
||||
9 | assert_never(None) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `dict[Unknown, Unknown]`
|
||||
18 |
|
||||
19 | def _():
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `dict[Unknown, Unknown]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -132,15 +123,15 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:20:5
|
||||
--> src/mdtest_snippet.py:12:5
|
||||
|
|
||||
19 | def _():
|
||||
20 | assert_never(()) # error: [type-assertion-failure]
|
||||
10 | assert_never([]) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--^
|
||||
| |
|
||||
| Inferred type of argument is `tuple[()]`
|
||||
21 |
|
||||
22 | def _(flag: bool, never: Never):
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `tuple[()]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -149,15 +140,16 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:23:5
|
||||
--> src/mdtest_snippet.py:13:5
|
||||
|
|
||||
22 | def _(flag: bool, never: Never):
|
||||
23 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
11 | assert_never({}) # error: [type-assertion-failure]
|
||||
12 | assert_never(()) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^--------------------^
|
||||
| |
|
||||
| Inferred type of argument is `Literal[1]`
|
||||
24 |
|
||||
25 | def _(any_: Any):
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Literal[1]` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -166,15 +158,15 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:26:5
|
||||
--> src/mdtest_snippet.py:15:5
|
||||
|
|
||||
25 | def _(any_: Any):
|
||||
26 | assert_never(any_) # error: [type-assertion-failure]
|
||||
13 | assert_never(1 if flag else never) # error: [type-assertion-failure]
|
||||
14 |
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^----^
|
||||
| |
|
||||
| Inferred type of argument is `Any`
|
||||
27 |
|
||||
28 | def _(unknown: Unknown):
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
|
|
||||
info: `Never` and `Any` are not equivalent types
|
||||
info: rule `type-assertion-failure` is enabled by default
|
||||
@@ -183,10 +175,10 @@ info: rule `type-assertion-failure` is enabled by default
|
||||
|
||||
```
|
||||
error[type-assertion-failure]: Argument does not have asserted type `Never`
|
||||
--> src/mdtest_snippet.py:29:5
|
||||
--> src/mdtest_snippet.py:16:5
|
||||
|
|
||||
28 | def _(unknown: Unknown):
|
||||
29 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
15 | assert_never(any_) # error: [type-assertion-failure]
|
||||
16 | assert_never(unknown) # error: [type-assertion-failure]
|
||||
| ^^^^^^^^^^^^^-------^
|
||||
| |
|
||||
| Inferred type of argument is `Unknown`
|
||||
@@ -570,229 +570,6 @@ def f():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Calls to functions returning `Never` / `NoReturn`
|
||||
|
||||
These calls should be treated as terminal statements.
|
||||
|
||||
### No implicit return
|
||||
|
||||
If we see a call to a function returning `Never`, we should be able to understand that the function
|
||||
cannot implicitly return `None`. In the below examples, verify that there are no errors emitted for
|
||||
invalid return type.
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
def f() -> NoReturn:
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
Let's try cases where the function annotated with `NoReturn` is some sub-expression.
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
# TODO: this is currently not yet supported
|
||||
# error: [invalid-return-type]
|
||||
def _() -> NoReturn:
|
||||
3 + sys.exit(1)
|
||||
|
||||
# TODO: this is currently not yet supported
|
||||
# error: [invalid-return-type]
|
||||
def _() -> NoReturn:
|
||||
3 if sys.exit(1) else 4
|
||||
```
|
||||
|
||||
### Type narrowing
|
||||
|
||||
If a variable's type is a union, and some types in the union result in a function marked with
|
||||
`NoReturn` being called, then we should correctly narrow the variable's type.
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
import sys
|
||||
|
||||
def g(x: int | None):
|
||||
if x is None:
|
||||
sys.exit(1)
|
||||
|
||||
# TODO: should be just `int`, not `int | None`
|
||||
# See https://github.com/astral-sh/ty/issues/685
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
### Possibly unresolved diagnostics
|
||||
|
||||
If the codepath on which a variable is not defined eventually returns `Never`, use of the variable
|
||||
should not give any diagnostics.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x = 3
|
||||
else:
|
||||
sys.exit()
|
||||
|
||||
x # No possibly-unresolved-references diagnostic here.
|
||||
```
|
||||
|
||||
Similarly, there shouldn't be any diagnostics if the `except` block of a `try/except` construct has
|
||||
a call with `NoReturn`.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
def _():
|
||||
try:
|
||||
x = 3
|
||||
except:
|
||||
sys.exit()
|
||||
|
||||
x # No possibly-unresolved-references diagnostic here.
|
||||
```
|
||||
|
||||
### Bindings in branches
|
||||
|
||||
In case of a `NoReturn` call being present in conditionals, the revealed type of the end of the
|
||||
branch should reflect the path which did not hit any of the `NoReturn` calls. These tests are
|
||||
similar to the ones for `return` above.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
def call_in_then_branch(cond: bool):
|
||||
if cond:
|
||||
x = "terminal"
|
||||
reveal_type(x) # revealed: Literal["terminal"]
|
||||
sys.exit()
|
||||
else:
|
||||
x = "test"
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
|
||||
def call_in_else_branch(cond: bool):
|
||||
if cond:
|
||||
x = "test"
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
else:
|
||||
x = "terminal"
|
||||
reveal_type(x) # revealed: Literal["terminal"]
|
||||
sys.exit()
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
|
||||
def call_in_both_branches(cond: bool):
|
||||
if cond:
|
||||
x = "terminal1"
|
||||
reveal_type(x) # revealed: Literal["terminal1"]
|
||||
sys.exit()
|
||||
else:
|
||||
x = "terminal2"
|
||||
reveal_type(x) # revealed: Literal["terminal2"]
|
||||
sys.exit()
|
||||
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
def call_in_nested_then_branch(cond1: bool, cond2: bool):
|
||||
if cond1:
|
||||
x = "test1"
|
||||
reveal_type(x) # revealed: Literal["test1"]
|
||||
else:
|
||||
if cond2:
|
||||
x = "terminal"
|
||||
reveal_type(x) # revealed: Literal["terminal"]
|
||||
sys.exit()
|
||||
else:
|
||||
x = "test2"
|
||||
reveal_type(x) # revealed: Literal["test2"]
|
||||
reveal_type(x) # revealed: Literal["test2"]
|
||||
reveal_type(x) # revealed: Literal["test1", "test2"]
|
||||
|
||||
def call_in_nested_else_branch(cond1: bool, cond2: bool):
|
||||
if cond1:
|
||||
x = "test1"
|
||||
reveal_type(x) # revealed: Literal["test1"]
|
||||
else:
|
||||
if cond2:
|
||||
x = "test2"
|
||||
reveal_type(x) # revealed: Literal["test2"]
|
||||
else:
|
||||
x = "terminal"
|
||||
reveal_type(x) # revealed: Literal["terminal"]
|
||||
sys.exit()
|
||||
reveal_type(x) # revealed: Literal["test2"]
|
||||
reveal_type(x) # revealed: Literal["test1", "test2"]
|
||||
|
||||
def call_in_both_nested_branches(cond1: bool, cond2: bool):
|
||||
if cond1:
|
||||
x = "test"
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
else:
|
||||
x = "terminal0"
|
||||
if cond2:
|
||||
x = "terminal1"
|
||||
reveal_type(x) # revealed: Literal["terminal1"]
|
||||
sys.exit()
|
||||
else:
|
||||
x = "terminal2"
|
||||
reveal_type(x) # revealed: Literal["terminal2"]
|
||||
sys.exit()
|
||||
reveal_type(x) # revealed: Literal["test"]
|
||||
```
|
||||
|
||||
### Overloads
|
||||
|
||||
If only some overloads of a function are marked with `NoReturn`, we should run the overload
|
||||
evaluation algorithm when evaluating the constraints.
|
||||
|
||||
```py
|
||||
from typing import NoReturn, overload
|
||||
|
||||
@overload
|
||||
def f(x: int) -> NoReturn: ...
|
||||
@overload
|
||||
def f(x: str) -> int: ...
|
||||
def f(x): ...
|
||||
|
||||
# No errors
|
||||
def _() -> NoReturn:
|
||||
f(3)
|
||||
|
||||
# This should be an error because of implicitly returning `None`
|
||||
# error: [invalid-return-type]
|
||||
def _() -> NoReturn:
|
||||
f("")
|
||||
```
|
||||
|
||||
### Other callables
|
||||
|
||||
If other types of callables are annotated with `NoReturn`, we should still be ablt to infer correct
|
||||
reachability.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
from typing import NoReturn
|
||||
|
||||
class C:
|
||||
def __call__(self) -> NoReturn:
|
||||
sys.exit()
|
||||
|
||||
def die(self) -> NoReturn:
|
||||
sys.exit()
|
||||
|
||||
# No "implicitly returns `None`" diagnostic
|
||||
def _() -> NoReturn:
|
||||
C()()
|
||||
|
||||
# No "implicitly returns `None`" diagnostic
|
||||
def _() -> NoReturn:
|
||||
C().die()
|
||||
```
|
||||
|
||||
## Nested functions
|
||||
|
||||
Free references inside of a function body refer to variables defined in the containing scope.
|
||||
|
||||
@@ -300,20 +300,6 @@ static_assert(not is_equivalent_to(CallableTypeOf[f12], CallableTypeOf[f13]))
|
||||
static_assert(not is_equivalent_to(CallableTypeOf[f13], CallableTypeOf[f12]))
|
||||
```
|
||||
|
||||
### Unions containing `Callable`s
|
||||
|
||||
Two unions containing different `Callable` types are equivalent even if the unions are differently
|
||||
ordered:
|
||||
|
||||
```py
|
||||
from ty_extensions import CallableTypeOf, Unknown, is_equivalent_to, static_assert
|
||||
|
||||
def f(x): ...
|
||||
def g(x: Unknown): ...
|
||||
|
||||
static_assert(is_equivalent_to(CallableTypeOf[f] | int | str, str | int | CallableTypeOf[g]))
|
||||
```
|
||||
|
||||
### Unions containing `Callable`s containing unions
|
||||
|
||||
Differently ordered unions inside `Callable`s inside unions can still be equivalent:
|
||||
|
||||
@@ -21,7 +21,8 @@ class C:
|
||||
reveal_type(C.a) # revealed: int
|
||||
reveal_type(C.b) # revealed: int
|
||||
reveal_type(C.c) # revealed: int
|
||||
reveal_type(C.d) # revealed: Unknown | Literal[1]
|
||||
# TODO: should be Unknown | Literal[1]
|
||||
reveal_type(C.d) # revealed: Unknown
|
||||
reveal_type(C.e) # revealed: int
|
||||
|
||||
c = C()
|
||||
|
||||
@@ -3,124 +3,44 @@
|
||||
[`typing.Final`] is a type qualifier that is used to indicate that a symbol may not be reassigned in
|
||||
any scope. Final names declared in class scopes cannot be overridden in subclasses.
|
||||
|
||||
## Basic type inference
|
||||
|
||||
### `Final` with type
|
||||
|
||||
Declared symbols that are additionally qualified with `Final` use the declared type when accessed
|
||||
from another scope. Local uses of the symbol will use the inferred type, which may be more specific:
|
||||
## Basic
|
||||
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
from typing import Final, Annotated
|
||||
|
||||
FINAL_A: Final[int] = 1
|
||||
FINAL_A: int = 1
|
||||
FINAL_B: Annotated[Final[int], "the annotation for FINAL_B"] = 1
|
||||
FINAL_C: Final[Annotated[int, "the annotation for FINAL_C"]] = 1
|
||||
FINAL_D: "Final[int]" = 1
|
||||
FINAL_F: Final[int]
|
||||
FINAL_F = 1
|
||||
FINAL_D: Final = 1
|
||||
FINAL_E: "Final[int]" = 1
|
||||
|
||||
reveal_type(FINAL_A) # revealed: Literal[1]
|
||||
reveal_type(FINAL_B) # revealed: Literal[1]
|
||||
reveal_type(FINAL_C) # revealed: Literal[1]
|
||||
reveal_type(FINAL_D) # revealed: Literal[1]
|
||||
reveal_type(FINAL_D) # revealed: Literal[1]
|
||||
reveal_type(FINAL_E) # revealed: Literal[1]
|
||||
|
||||
def nonlocal_uses():
|
||||
reveal_type(FINAL_A) # revealed: int
|
||||
reveal_type(FINAL_B) # revealed: int
|
||||
reveal_type(FINAL_C) # revealed: int
|
||||
reveal_type(FINAL_D) # revealed: int
|
||||
reveal_type(FINAL_F) # revealed: int
|
||||
```
|
||||
|
||||
Imported types:
|
||||
|
||||
```py
|
||||
from mod import FINAL_A, FINAL_B, FINAL_C, FINAL_D, FINAL_F
|
||||
|
||||
reveal_type(FINAL_A) # revealed: int
|
||||
reveal_type(FINAL_B) # revealed: int
|
||||
reveal_type(FINAL_C) # revealed: int
|
||||
reveal_type(FINAL_D) # revealed: int
|
||||
reveal_type(FINAL_F) # revealed: int
|
||||
```
|
||||
|
||||
### `Final` without a type
|
||||
|
||||
When a symbol is qualified with `Final` but no type is specified, the type is inferred from the
|
||||
right-hand side of the assignment. We do not union the inferred type with `Unknown`, because the
|
||||
symbol cannot be modified:
|
||||
|
||||
`mod.py`:
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
FINAL_A: Final = 1
|
||||
|
||||
reveal_type(FINAL_A) # revealed: Literal[1]
|
||||
|
||||
def nonlocal_uses():
|
||||
reveal_type(FINAL_A) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from mod import FINAL_A
|
||||
|
||||
reveal_type(FINAL_A) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### In class definitions
|
||||
|
||||
```py
|
||||
from typing import Final
|
||||
|
||||
class C:
|
||||
FINAL_A: Final[int] = 1
|
||||
FINAL_B: Final = 1
|
||||
|
||||
def __init__(self):
|
||||
self.FINAL_C: Final[int] = 1
|
||||
self.FINAL_D: Final = 1
|
||||
|
||||
reveal_type(C.FINAL_A) # revealed: int
|
||||
reveal_type(C.FINAL_B) # revealed: Literal[1]
|
||||
|
||||
reveal_type(C().FINAL_A) # revealed: int
|
||||
reveal_type(C().FINAL_B) # revealed: Literal[1]
|
||||
reveal_type(C().FINAL_C) # revealed: int
|
||||
# TODO: this should be `Literal[1]`
|
||||
reveal_type(C().FINAL_D) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Not modifiable
|
||||
|
||||
Symbols qualified with `Final` cannot be reassigned, and attempting to do so will result in an
|
||||
error:
|
||||
|
||||
```py
|
||||
from typing import Final, Annotated
|
||||
|
||||
FINAL_A: Final[int] = 1
|
||||
FINAL_B: Annotated[Final[int], "the annotation for FINAL_B"] = 1
|
||||
FINAL_C: Final[Annotated[int, "the annotation for FINAL_C"]] = 1
|
||||
FINAL_D: "Final[int]" = 1
|
||||
FINAL_E: Final[int]
|
||||
FINAL_E = 1
|
||||
FINAL_F: Final = 1
|
||||
|
||||
# TODO: all of these should be errors
|
||||
# TODO: All of these should be errors:
|
||||
FINAL_A = 2
|
||||
FINAL_B = 2
|
||||
FINAL_C = 2
|
||||
FINAL_D = 2
|
||||
FINAL_E = 2
|
||||
FINAL_F = 2
|
||||
```
|
||||
|
||||
Public types:
|
||||
|
||||
```py
|
||||
from mod import FINAL_A, FINAL_B, FINAL_C, FINAL_D, FINAL_E
|
||||
|
||||
# TODO: All of these should be Literal[1]
|
||||
reveal_type(FINAL_A) # revealed: int
|
||||
reveal_type(FINAL_B) # revealed: int
|
||||
reveal_type(FINAL_C) # revealed: int
|
||||
reveal_type(FINAL_D) # revealed: Unknown
|
||||
reveal_type(FINAL_E) # revealed: int
|
||||
```
|
||||
|
||||
## Too many arguments
|
||||
@@ -162,10 +82,6 @@ from typing import Final
|
||||
|
||||
# TODO: This should be an error
|
||||
NO_RHS: Final
|
||||
|
||||
class C:
|
||||
# TODO: This should be an error
|
||||
NO_RHS: Final
|
||||
```
|
||||
|
||||
[`typing.final`]: https://docs.python.org/3/library/typing.html#typing.Final
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
flake8
|
||||
sphinx
|
||||
prefect
|
||||
trio
|
||||
@@ -15,7 +15,7 @@ pub use program::{
|
||||
PythonVersionWithSource, SearchPathSettings,
|
||||
};
|
||||
pub use python_platform::PythonPlatform;
|
||||
pub use semantic_model::{Completion, HasType, NameKind, SemanticModel};
|
||||
pub use semantic_model::{Completion, HasDefinition, HasType, NameKind, SemanticModel};
|
||||
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
||||
pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
|
||||
|
||||
@@ -156,9 +156,6 @@ pub enum KnownModule {
|
||||
TyExtensions,
|
||||
#[strum(serialize = "importlib")]
|
||||
ImportLib,
|
||||
#[cfg(test)]
|
||||
#[strum(serialize = "unittest.mock")]
|
||||
UnittestMock,
|
||||
}
|
||||
|
||||
impl KnownModule {
|
||||
@@ -178,8 +175,6 @@ impl KnownModule {
|
||||
Self::TypeCheckerInternals => "_typeshed._type_checker_internals",
|
||||
Self::TyExtensions => "ty_extensions",
|
||||
Self::ImportLib => "importlib",
|
||||
#[cfg(test)]
|
||||
Self::UnittestMock => "unittest.mock",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -535,23 +535,14 @@ struct ModuleNameIngredient<'db> {
|
||||
pub(super) name: ModuleName,
|
||||
}
|
||||
|
||||
/// Returns `true` if the module name refers to a standard library module which can't be shadowed
|
||||
/// by a first-party module.
|
||||
///
|
||||
/// This includes "builtin" modules, which can never be shadowed at runtime either, as well as the
|
||||
/// `types` module, which tends to be imported early in Python startup, so can't be consistently
|
||||
/// shadowed, and is important to type checking.
|
||||
fn is_non_shadowable(minor_version: u8, module_name: &str) -> bool {
|
||||
module_name == "types" || ruff_python_stdlib::sys::is_builtin_module(minor_version, module_name)
|
||||
}
|
||||
|
||||
/// Given a module name and a list of search paths in which to lookup modules,
|
||||
/// attempt to resolve the module name
|
||||
fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<ResolvedName> {
|
||||
let program = Program::get(db);
|
||||
let python_version = program.python_version(db);
|
||||
let resolver_state = ResolverContext::new(db, python_version);
|
||||
let is_non_shadowable = is_non_shadowable(python_version.minor, name.as_str());
|
||||
let is_builtin_module =
|
||||
ruff_python_stdlib::sys::is_builtin_module(python_version.minor, name.as_str());
|
||||
|
||||
let name = RelaxedModuleName::new(name);
|
||||
let stub_name = name.to_stub_package();
|
||||
@@ -562,8 +553,7 @@ fn resolve_name(db: &dyn Db, name: &ModuleName) -> Option<ResolvedName> {
|
||||
// the module name always resolves to the stdlib module,
|
||||
// even if there's a module of the same name in the first-party root
|
||||
// (which would normally result in the stdlib module being overridden).
|
||||
// TODO: offer a diagnostic if there is a first-party module of the same name
|
||||
if is_non_shadowable && !search_path.is_standard_library() {
|
||||
if is_builtin_module && !search_path.is_standard_library() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::semantic_index::{
|
||||
};
|
||||
use crate::semantic_index::{DeclarationWithConstraint, global_scope, use_def_map};
|
||||
use crate::types::{
|
||||
DynamicType, KnownClass, Truthiness, Type, TypeAndQualifiers, TypeQualifiers, UnionBuilder,
|
||||
UnionType, binding_type, declaration_type, todo_type,
|
||||
KnownClass, Truthiness, Type, TypeAndQualifiers, TypeQualifiers, UnionBuilder, UnionType,
|
||||
binding_type, declaration_type, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet, KnownModule, Program, resolve_module};
|
||||
|
||||
@@ -524,21 +524,6 @@ impl<'db> PlaceAndQualifiers<'db> {
|
||||
self.qualifiers.contains(TypeQualifiers::CLASS_VAR)
|
||||
}
|
||||
|
||||
/// Returns `Some(…)` if the place is qualified with `typing.Final` without a specified type.
|
||||
pub(crate) fn is_bare_final(&self) -> Option<TypeQualifiers> {
|
||||
match self {
|
||||
PlaceAndQualifiers { place, qualifiers }
|
||||
if (qualifiers.contains(TypeQualifiers::FINAL)
|
||||
&& place
|
||||
.ignore_possibly_unbound()
|
||||
.is_some_and(|ty| ty.is_unknown())) =>
|
||||
{
|
||||
Some(*qualifiers)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn map_type(
|
||||
self,
|
||||
@@ -660,42 +645,6 @@ fn place_by_id<'db>(
|
||||
ConsideredDefinitions::AllReachable => use_def.all_reachable_bindings(place_id),
|
||||
};
|
||||
|
||||
// If a symbol is undeclared, but qualified with `typing.Final`, we use the right-hand side
|
||||
// inferred type, without unioning with `Unknown`, because it can not be modified.
|
||||
if let Some(qualifiers) = declared
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(PlaceAndQualifiers::is_bare_final)
|
||||
{
|
||||
let bindings = all_considered_bindings();
|
||||
return place_from_bindings_impl(db, bindings, requires_explicit_reexport)
|
||||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
|
||||
// Handle bare `ClassVar` annotations by falling back to the union of `Unknown` and the
|
||||
// inferred type.
|
||||
match declared {
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: Place::Type(Type::Dynamic(DynamicType::Unknown), declaredness),
|
||||
qualifiers,
|
||||
}) if qualifiers.contains(TypeQualifiers::CLASS_VAR) => {
|
||||
let bindings = all_considered_bindings();
|
||||
match place_from_bindings_impl(db, bindings, requires_explicit_reexport) {
|
||||
Place::Type(inferred, boundness) => {
|
||||
return Place::Type(
|
||||
UnionType::from_elements(db, [Type::unknown(), inferred]),
|
||||
boundness,
|
||||
)
|
||||
.with_qualifiers(qualifiers);
|
||||
}
|
||||
Place::Unbound => {
|
||||
return Place::Type(Type::unknown(), declaredness).with_qualifiers(qualifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match declared {
|
||||
// Place is declared, trust the declared type
|
||||
Ok(
|
||||
|
||||
@@ -124,30 +124,6 @@ pub(crate) fn attribute_assignments<'db, 's>(
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all attribute declarations (and their method scope IDs) with a symbol name matching
|
||||
/// the one given for a specific class body scope.
|
||||
///
|
||||
/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
|
||||
/// introduces a direct dependency on that file's AST.
|
||||
pub(crate) fn attribute_declarations<'db, 's>(
|
||||
db: &'db dyn Db,
|
||||
class_body_scope: ScopeId<'db>,
|
||||
name: &'s str,
|
||||
) -> impl Iterator<Item = (DeclarationsIterator<'db, 'db>, FileScopeId)> + use<'s, 'db> {
|
||||
let file = class_body_scope.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
|
||||
attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
|
||||
let place_table = index.place_table(function_scope_id);
|
||||
let place = place_table.place_id_by_instance_attribute_name(name)?;
|
||||
let use_def = &index.use_def_maps[function_scope_id];
|
||||
Some((
|
||||
use_def.inner.all_reachable_declarations(place),
|
||||
function_scope_id,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns all attribute assignments as scope IDs for a specific class body scope.
|
||||
///
|
||||
/// Only call this when doing type inference on the same file as `class_body_scope`, otherwise it
|
||||
|
||||
@@ -35,8 +35,8 @@ use crate::semantic_index::place::{
|
||||
PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
|
||||
};
|
||||
use crate::semantic_index::predicate::{
|
||||
CallableAndCallExpr, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
PredicateOrLiteral, ScopedPredicateId, StarImportPlaceholderPredicate,
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, PredicateOrLiteral,
|
||||
ScopedPredicateId, StarImportPlaceholderPredicate,
|
||||
};
|
||||
use crate::semantic_index::re_exports::exported_names;
|
||||
use crate::semantic_index::reachability_constraints::{
|
||||
@@ -1901,45 +1901,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
||||
value,
|
||||
range: _,
|
||||
node_index: _,
|
||||
}) => {
|
||||
if self.in_module_scope() {
|
||||
if let Some(expr) = dunder_all_extend_argument(value) {
|
||||
self.add_standalone_expression(expr);
|
||||
}
|
||||
}) if self.in_module_scope() => {
|
||||
if let Some(expr) = dunder_all_extend_argument(value) {
|
||||
self.add_standalone_expression(expr);
|
||||
}
|
||||
|
||||
self.visit_expr(value);
|
||||
|
||||
// If the statement is a call, it could possibly be a call to a function
|
||||
// marked with `NoReturn` (for example, `sys.exit()`). In this case, we use a special
|
||||
// kind of constraint to mark the following code as unreachable.
|
||||
//
|
||||
// Ideally, these constraints should be added for every call expression, even those in
|
||||
// sub-expressions and in the module-level scope. But doing so makes the number of
|
||||
// such constraints so high that it significantly degrades performance. We thus cut
|
||||
// scope here and add these constraints only at statement level function calls,
|
||||
// like `sys.exit()`, and not within sub-expression like `3 + sys.exit()` etc.
|
||||
//
|
||||
// We also only add these inside function scopes, since considering module-level
|
||||
// constraints can affect the the type of imported symbols, leading to a lot more
|
||||
// work in third-party code.
|
||||
if let ast::Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() {
|
||||
if !self.source_type.is_stub() && self.in_function_scope() {
|
||||
let callable = self.add_standalone_expression(func);
|
||||
let call_expr = self.add_standalone_expression(value.as_ref());
|
||||
|
||||
let predicate = Predicate {
|
||||
node: PredicateNode::ReturnsNever(CallableAndCallExpr {
|
||||
callable,
|
||||
call_expr,
|
||||
}),
|
||||
is_positive: false,
|
||||
};
|
||||
self.record_reachability_constraint(PredicateOrLiteral::Predicate(
|
||||
predicate,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
walk_stmt(self, stmt);
|
||||
|
||||
@@ -102,16 +102,9 @@ impl PredicateOrLiteral<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(crate) struct CallableAndCallExpr<'db> {
|
||||
pub(crate) callable: Expression<'db>,
|
||||
pub(crate) call_expr: Expression<'db>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(crate) enum PredicateNode<'db> {
|
||||
Expression(Expression<'db>),
|
||||
ReturnsNever(CallableAndCallExpr<'db>),
|
||||
Pattern(PatternPredicate<'db>),
|
||||
StarImportPlaceholder(StarImportPlaceholderPredicate<'db>),
|
||||
}
|
||||
|
||||
@@ -204,8 +204,7 @@ use crate::place::{RequiresExplicitReExport, imported_symbol};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::place_table;
|
||||
use crate::semantic_index::predicate::{
|
||||
CallableAndCallExpr, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
Predicates, ScopedPredicateId,
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, Predicates, ScopedPredicateId,
|
||||
};
|
||||
use crate::types::{Truthiness, Type, infer_expression_type};
|
||||
|
||||
@@ -685,53 +684,6 @@ impl ReachabilityConstraints {
|
||||
let ty = infer_expression_type(db, test_expr);
|
||||
ty.bool(db).negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(CallableAndCallExpr {
|
||||
callable,
|
||||
call_expr,
|
||||
}) => {
|
||||
// We first infer just the type of the callable. In the most likely case that the
|
||||
// function is not marked with `NoReturn`, or that it always returns `NoReturn`,
|
||||
// doing so allows us to avoid the more expensive work of inferring the entire call
|
||||
// expression (which could involve inferring argument types to possibly run the overload
|
||||
// selection algorithm).
|
||||
// Avoiding this on the happy-path is important because these constraints can be
|
||||
// very large in number, since we add them on all statement level function calls.
|
||||
let ty = infer_expression_type(db, callable);
|
||||
|
||||
let overloads_iterator =
|
||||
if let Some(Type::Callable(callable)) = ty.into_callable(db) {
|
||||
callable.signatures(db).overloads.iter()
|
||||
} else {
|
||||
return Truthiness::AlwaysFalse.negate_if(!predicate.is_positive);
|
||||
};
|
||||
|
||||
let (no_overloads_return_never, all_overloads_return_never) = overloads_iterator
|
||||
.fold((true, true), |(none, all), overload| {
|
||||
let overload_returns_never =
|
||||
overload.return_ty.is_some_and(|return_type| {
|
||||
return_type.is_equivalent_to(db, Type::Never)
|
||||
});
|
||||
|
||||
(
|
||||
none && !overload_returns_never,
|
||||
all && overload_returns_never,
|
||||
)
|
||||
});
|
||||
|
||||
if no_overloads_return_never {
|
||||
Truthiness::AlwaysFalse
|
||||
} else if all_overloads_return_never {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
let call_expr_ty = infer_expression_type(db, call_expr);
|
||||
if call_expr_ty.is_equivalent_to(db, Type::Never) {
|
||||
Truthiness::AlwaysTrue
|
||||
} else {
|
||||
Truthiness::AlwaysFalse
|
||||
}
|
||||
}
|
||||
.negate_if(!predicate.is_positive)
|
||||
}
|
||||
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
|
||||
@@ -192,17 +192,6 @@
|
||||
//! for that place that we need for that use or definition. When we reach the end of the scope, it
|
||||
//! records the state for each place as the public definitions of that place.
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1
|
||||
//! x = 2
|
||||
//! y = x
|
||||
//! if flag:
|
||||
//! x = 3
|
||||
//! else:
|
||||
//! x = 4
|
||||
//! z = x
|
||||
//! ```
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
|
||||
//! the new place (before we process the first binding), we create a new undefined `PlaceState`
|
||||
//! which has a single live binding (the "unbound" definition) and a single live declaration (the
|
||||
@@ -509,18 +498,6 @@ impl<'db> UseDefMap<'db> {
|
||||
.is_always_false()
|
||||
}
|
||||
|
||||
pub(crate) fn is_declaration_reachable(
|
||||
&self,
|
||||
db: &dyn crate::Db,
|
||||
declaration: &DeclarationWithConstraint<'db>,
|
||||
) -> Truthiness {
|
||||
self.reachability_constraints.evaluate(
|
||||
db,
|
||||
&self.predicates,
|
||||
declaration.reachability_constraint,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn is_binding_reachable(
|
||||
&self,
|
||||
db: &dyn crate::Db,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use ruff_db::files::{File, FilePath};
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{self as ast, ExprContext};
|
||||
use ruff_python_ast::{Expr, ExprRef, name::Name};
|
||||
use ruff_source_file::LineIndex;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{KnownModule, Module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::place::FileScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::ide_support::all_declarations_and_bindings;
|
||||
@@ -175,6 +177,41 @@ pub struct Completion {
|
||||
pub builtin: bool,
|
||||
}
|
||||
|
||||
pub trait HasDefinition {
|
||||
/// Returns the definitions of `self`.
|
||||
///
|
||||
/// ## Panics
|
||||
/// May panic if `self` is from another file than `model`.
|
||||
fn definitions<'db>(&self, model: &SemanticModel<'db>) -> Option<Vec<Definition<'db>>>;
|
||||
}
|
||||
|
||||
impl HasDefinition for ast::ExprRef<'_> {
|
||||
fn definitions<'db>(&self, model: &SemanticModel<'db>) -> Option<Vec<Definition<'db>>> {
|
||||
match self {
|
||||
ExprRef::Name(name) => match name.ctx {
|
||||
ExprContext::Load => {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let file_scope = index.expression_scope_id(*self);
|
||||
let scope = file_scope.to_scope_id(model.db, model.file);
|
||||
let use_def = index.use_def_map(file_scope);
|
||||
let use_id = self.scoped_use_id(model.db, scope);
|
||||
|
||||
Some(
|
||||
use_def
|
||||
.bindings_at_use(use_id)
|
||||
.filter_map(|binding| binding.binding.definition())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
ExprContext::Store => None,
|
||||
ExprContext::Del => None,
|
||||
ExprContext::Invalid => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasType {
|
||||
/// Returns the inferred type of `self`.
|
||||
///
|
||||
|
||||
@@ -19,7 +19,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::cyclic::{PairVisitor, TypeTransformer};
|
||||
pub(crate) use self::cyclic::TypeTransformer;
|
||||
pub use self::diagnostic::TypeCheckDiagnostics;
|
||||
pub(crate) use self::diagnostic::register_lints;
|
||||
pub(crate) use self::infer::{
|
||||
@@ -46,7 +46,7 @@ use crate::types::generics::{
|
||||
GenericContext, PartialSpecialization, Specialization, walk_generic_context,
|
||||
walk_partial_specialization, walk_specialization,
|
||||
};
|
||||
pub use crate::types::ide_support::{all_members, definition_kind_for_name};
|
||||
pub use crate::types::ide_support::all_members;
|
||||
use crate::types::infer::infer_unpack_types;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
@@ -1095,74 +1095,6 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_callable(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Type::Callable(_) => Some(self),
|
||||
|
||||
Type::Dynamic(_) => Some(CallableType::single(db, Signature::dynamic(self))),
|
||||
|
||||
Type::FunctionLiteral(function_literal) => {
|
||||
Some(Type::Callable(function_literal.into_callable_type(db)))
|
||||
}
|
||||
Type::BoundMethod(bound_method) => Some(bound_method.into_callable_type(db)),
|
||||
|
||||
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
|
||||
let call_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
)
|
||||
.place;
|
||||
|
||||
if let Place::Type(ty, Boundness::Bound) = call_symbol {
|
||||
ty.into_callable(db)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
Some(ClassType::NonGeneric(class_literal).into_callable(db))
|
||||
}
|
||||
|
||||
Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
|
||||
|
||||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
|
||||
SubclassOfInner::Dynamic(dynamic) => Some(CallableType::single(
|
||||
db,
|
||||
Signature::new(Parameters::unknown(), Some(Type::Dynamic(dynamic))),
|
||||
)),
|
||||
},
|
||||
|
||||
Type::Union(union) => union.try_map(db, |element| element.into_callable(db)),
|
||||
|
||||
Type::Never
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeIs(_) => None,
|
||||
|
||||
// TODO
|
||||
Type::MethodWrapper(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_) => None,
|
||||
}
|
||||
}
|
||||
/// Return true if this type is a [subtype of] type `target`.
|
||||
///
|
||||
/// For fully static types, this means that the set of objects represented by `self` is a
|
||||
@@ -1373,14 +1305,24 @@ impl<'db> Type<'db> {
|
||||
| Type::ModuleLiteral(_),
|
||||
) => false,
|
||||
|
||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
|
||||
self_callable.has_relation_to(db, other_callable, relation)
|
||||
(Type::NominalInstance(_) | Type::ProtocolInstance(_), Type::Callable(_)) => {
|
||||
let call_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
)
|
||||
.place;
|
||||
// If the type of __call__ is a subtype of a callable type, this instance is.
|
||||
// Don't add other special cases here; our subtyping of a callable type
|
||||
// shouldn't get out of sync with the calls we will actually allow.
|
||||
if let Place::Type(t, Boundness::Bound) = call_symbol {
|
||||
t.has_relation_to(db, target, relation)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
(_, Type::Callable(_)) => self
|
||||
.into_callable(db)
|
||||
.is_some_and(|callable| callable.has_relation_to(db, target, relation)),
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
left.has_relation_to(db, right, relation)
|
||||
}
|
||||
@@ -1407,6 +1349,16 @@ impl<'db> Type<'db> {
|
||||
) => (self.literal_fallback_instance(db))
|
||||
.is_some_and(|instance| instance.has_relation_to(db, target, relation)),
|
||||
|
||||
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
||||
self_function_literal
|
||||
.into_callable_type(db)
|
||||
.has_relation_to(db, target, relation)
|
||||
}
|
||||
|
||||
(Type::BoundMethod(self_bound_method), Type::Callable(_)) => self_bound_method
|
||||
.into_callable_type(db)
|
||||
.has_relation_to(db, target, relation),
|
||||
|
||||
// A `FunctionLiteral` type is a single-valued type like the other literals handled above,
|
||||
// so it also, for now, just delegates to its instance fallback.
|
||||
(Type::FunctionLiteral(_), _) => KnownClass::FunctionType
|
||||
@@ -1424,6 +1376,10 @@ impl<'db> Type<'db> {
|
||||
.to_instance(db)
|
||||
.has_relation_to(db, target, relation),
|
||||
|
||||
(Type::Callable(self_callable), Type::Callable(other_callable)) => {
|
||||
self_callable.has_relation_to(db, other_callable, relation)
|
||||
}
|
||||
|
||||
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
|
||||
// TODO: Implement subtyping using an equivalent `Callable` type.
|
||||
false
|
||||
@@ -1500,6 +1456,26 @@ impl<'db> Type<'db> {
|
||||
self_subclass_ty.has_relation_to(db, target_subclass_ty, relation)
|
||||
}
|
||||
|
||||
(Type::ClassLiteral(class_literal), Type::Callable(_)) => {
|
||||
ClassType::NonGeneric(class_literal)
|
||||
.into_callable(db)
|
||||
.has_relation_to(db, target, relation)
|
||||
}
|
||||
|
||||
(Type::GenericAlias(alias), Type::Callable(_)) => ClassType::Generic(alias)
|
||||
.into_callable(db)
|
||||
.has_relation_to(db, target, relation),
|
||||
|
||||
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
|
||||
(Type::SubclassOf(subclass_of_ty), Type::Callable(_))
|
||||
if subclass_of_ty.subclass_of().into_class().is_some() =>
|
||||
{
|
||||
let class = subclass_of_ty.subclass_of().into_class().unwrap();
|
||||
class
|
||||
.into_callable(db)
|
||||
.has_relation_to(db, target, relation)
|
||||
}
|
||||
|
||||
// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
|
||||
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
|
||||
// is an instance of its metaclass `abc.ABCMeta`.
|
||||
@@ -1637,30 +1613,17 @@ impl<'db> Type<'db> {
|
||||
/// Note: This function aims to have no false positives, but might return
|
||||
/// wrong `false` answers in some cases.
|
||||
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
let mut visitor = PairVisitor::new(false);
|
||||
self.is_disjoint_from_impl(db, other, &mut visitor)
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Type<'db>,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
fn any_protocol_members_absent_or_disjoint<'db>(
|
||||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
other: Type<'db>,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
protocol.interface(db).members(db).any(|member| {
|
||||
other
|
||||
.member(db, member.name())
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.is_none_or(|attribute_type| {
|
||||
member.has_disjoint_type_from(db, attribute_type, visitor)
|
||||
})
|
||||
.is_none_or(|attribute_type| member.has_disjoint_type_from(db, attribute_type))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1694,19 +1657,19 @@ impl<'db> Type<'db> {
|
||||
match typevar.bound_or_constraints(db) {
|
||||
None => false,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
bound.is_disjoint_from_impl(db, other, visitor)
|
||||
bound.is_disjoint_from(db, other)
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|constraint| constraint.is_disjoint_from_impl(db, other, visitor)),
|
||||
.all(|constraint| constraint.is_disjoint_from(db, other)),
|
||||
}
|
||||
}
|
||||
|
||||
(Type::Union(union), other) | (other, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.all(|e| e.is_disjoint_from_impl(db, other, visitor)),
|
||||
.all(|e| e.is_disjoint_from(db, other)),
|
||||
|
||||
// If we have two intersections, we test the positive elements of each one against the other intersection
|
||||
// Negative elements need a positive element on the other side in order to be disjoint.
|
||||
@@ -1715,11 +1678,11 @@ impl<'db> Type<'db> {
|
||||
self_intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|p| p.is_disjoint_from_impl(db, other, visitor))
|
||||
.any(|p| p.is_disjoint_from(db, other))
|
||||
|| other_intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|p: &Type<'_>| p.is_disjoint_from_impl(db, self, visitor))
|
||||
.any(|p: &Type<'_>| p.is_disjoint_from(db, self))
|
||||
}
|
||||
|
||||
(Type::Intersection(intersection), other)
|
||||
@@ -1727,7 +1690,7 @@ impl<'db> Type<'db> {
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|p| p.is_disjoint_from_impl(db, other, visitor))
|
||||
.any(|p| p.is_disjoint_from(db, other))
|
||||
// A & B & Not[C] is disjoint from C
|
||||
|| intersection
|
||||
.negative(db)
|
||||
@@ -1841,17 +1804,17 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(left), Type::ProtocolInstance(right)) => {
|
||||
left.is_disjoint_from_impl(db, right, visitor)
|
||||
left.is_disjoint_from(db, right)
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(protocol), Type::SpecialForm(special_form))
|
||||
| (Type::SpecialForm(special_form), Type::ProtocolInstance(protocol)) => {
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, special_form.instance_fallback(db), visitor)
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, special_form.instance_fallback(db))
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(protocol), Type::KnownInstance(known_instance))
|
||||
| (Type::KnownInstance(known_instance), Type::ProtocolInstance(protocol)) => {
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, known_instance.instance_fallback(db), visitor)
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, known_instance.instance_fallback(db))
|
||||
}
|
||||
|
||||
// The absence of a protocol member on one of these types guarantees
|
||||
@@ -1904,7 +1867,7 @@ impl<'db> Type<'db> {
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::IntLiteral(..)),
|
||||
) => any_protocol_members_absent_or_disjoint(db, protocol, ty, visitor),
|
||||
) => any_protocol_members_absent_or_disjoint(db, protocol, ty),
|
||||
|
||||
// This is the same as the branch above --
|
||||
// once guard patterns are stabilised, it could be unified with that branch
|
||||
@@ -1913,7 +1876,7 @@ impl<'db> Type<'db> {
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol))
|
||||
if n.class.is_final(db) =>
|
||||
{
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor)
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, nominal)
|
||||
}
|
||||
|
||||
(Type::ProtocolInstance(protocol), other)
|
||||
@@ -1921,7 +1884,7 @@ impl<'db> Type<'db> {
|
||||
protocol.interface(db).members(db).any(|member| {
|
||||
matches!(
|
||||
other.member(db, member.name()).place,
|
||||
Place::Type(attribute_type, _) if member.has_disjoint_type_from(db, attribute_type, visitor)
|
||||
Place::Type(attribute_type, _) if member.has_disjoint_type_from(db, attribute_type)
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1944,18 +1907,18 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
(Type::SubclassOf(left), Type::SubclassOf(right)) => left.is_disjoint_from_impl(db, right),
|
||||
(Type::SubclassOf(left), Type::SubclassOf(right)) => left.is_disjoint_from(db, right),
|
||||
|
||||
// for `type[Any]`/`type[Unknown]`/`type[Todo]`, we know the type cannot be any larger than `type`,
|
||||
// so although the type is dynamic we can still determine disjointedness in some situations
|
||||
(Type::SubclassOf(subclass_of_ty), other)
|
||||
| (other, Type::SubclassOf(subclass_of_ty)) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => {
|
||||
KnownClass::Type.to_instance(db).is_disjoint_from_impl(db, other, visitor)
|
||||
KnownClass::Type.to_instance(db).is_disjoint_from(db, other)
|
||||
}
|
||||
SubclassOfInner::Class(class) => class
|
||||
.metaclass_instance_type(db)
|
||||
.is_disjoint_from_impl(db, other, visitor),
|
||||
.is_disjoint_from(db, other),
|
||||
},
|
||||
|
||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||
@@ -2040,18 +2003,18 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
|
||||
.to_instance(db)
|
||||
.is_disjoint_from_impl(db, other, visitor),
|
||||
.is_disjoint_from(db, other),
|
||||
|
||||
(Type::MethodWrapper(_), other) | (other, Type::MethodWrapper(_)) => {
|
||||
KnownClass::MethodWrapperType
|
||||
.to_instance(db)
|
||||
.is_disjoint_from_impl(db, other, visitor)
|
||||
.is_disjoint_from(db, other)
|
||||
}
|
||||
|
||||
(Type::WrapperDescriptor(_), other) | (other, Type::WrapperDescriptor(_)) => {
|
||||
KnownClass::WrapperDescriptorType
|
||||
.to_instance(db)
|
||||
.is_disjoint_from_impl(db, other, visitor)
|
||||
.is_disjoint_from(db, other)
|
||||
}
|
||||
|
||||
(Type::Callable(_) | Type::FunctionLiteral(_), Type::Callable(_))
|
||||
@@ -2113,15 +2076,15 @@ impl<'db> Type<'db> {
|
||||
(Type::ModuleLiteral(..), other @ Type::NominalInstance(..))
|
||||
| (other @ Type::NominalInstance(..), Type::ModuleLiteral(..)) => {
|
||||
// Modules *can* actually be instances of `ModuleType` subclasses
|
||||
other.is_disjoint_from_impl(db, KnownClass::ModuleType.to_instance(db), visitor)
|
||||
other.is_disjoint_from(db, KnownClass::ModuleType.to_instance(db))
|
||||
}
|
||||
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
|
||||
left.is_disjoint_from_impl(db, right)
|
||||
left.is_disjoint_from(db, right)
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||
tuple.is_disjoint_from_impl(db, other_tuple, visitor)
|
||||
tuple.is_disjoint_from(db, other_tuple)
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), Type::NominalInstance(instance))
|
||||
@@ -2134,13 +2097,13 @@ impl<'db> Type<'db> {
|
||||
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
||||
KnownClass::Property
|
||||
.to_instance(db)
|
||||
.is_disjoint_from_impl(db, other, visitor)
|
||||
.is_disjoint_from(db, other)
|
||||
}
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => !self.is_equivalent_to(db, other),
|
||||
(Type::BoundSuper(_), other) | (other, Type::BoundSuper(_)) => KnownClass::Super
|
||||
.to_instance(db)
|
||||
.is_disjoint_from_impl(db, other, visitor),
|
||||
.is_disjoint_from(db, other),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3069,6 +3032,10 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::ModuleLiteral(module) => module.static_member(db, name_str).into(),
|
||||
|
||||
Type::AlwaysFalsy | Type::AlwaysTruthy => {
|
||||
self.class_member_with_policy(db, name, policy)
|
||||
}
|
||||
|
||||
_ if policy.no_instance_fallback() => self.invoke_descriptor_protocol(
|
||||
db,
|
||||
name_str,
|
||||
@@ -3090,8 +3057,6 @@ impl<'db> Type<'db> {
|
||||
| Type::KnownInstance(..)
|
||||
| Type::PropertyInstance(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::AlwaysTruthy
|
||||
| Type::AlwaysFalsy
|
||||
| Type::TypeIs(..) => {
|
||||
let fallback = self.instance_member(db, name_str);
|
||||
|
||||
@@ -3531,6 +3496,7 @@ impl<'db> Type<'db> {
|
||||
// For `builtins.property.__get__`, we use the same signature. The return types are not
|
||||
// specified yet, they will be dynamically added in `Bindings::evaluate_known_cases`.
|
||||
|
||||
let not_none = Type::none(db).negate(db);
|
||||
CallableBinding::from_overloads(
|
||||
self,
|
||||
[
|
||||
@@ -3546,7 +3512,7 @@ impl<'db> Type<'db> {
|
||||
Signature::new(
|
||||
Parameters::new([
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(not_none),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
@@ -3572,6 +3538,7 @@ impl<'db> Type<'db> {
|
||||
// TODO: Consider merging this signature with the one in the previous match clause,
|
||||
// since the previous one is just this signature with the `self` parameters
|
||||
// removed.
|
||||
let not_none = Type::none(db).negate(db);
|
||||
let descriptor = match kind {
|
||||
WrapperDescriptorKind::FunctionTypeDunderGet => {
|
||||
KnownClass::FunctionType.to_instance(db)
|
||||
@@ -3602,7 +3569,7 @@ impl<'db> Type<'db> {
|
||||
Parameter::positional_only(Some(Name::new_static("self")))
|
||||
.with_annotated_type(descriptor),
|
||||
Parameter::positional_only(Some(Name::new_static("instance")))
|
||||
.with_annotated_type(Type::object(db)),
|
||||
.with_annotated_type(not_none),
|
||||
Parameter::positional_only(Some(Name::new_static("owner")))
|
||||
.with_annotated_type(UnionType::from_elements(
|
||||
db,
|
||||
@@ -7222,7 +7189,7 @@ impl<'db> BoundMethodType<'db> {
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct CallableType<'db> {
|
||||
#[returns(ref)]
|
||||
pub(crate) signatures: CallableSignature<'db>,
|
||||
signatures: CallableSignature<'db>,
|
||||
|
||||
/// We use `CallableType` to represent function-like objects, like the synthesized methods
|
||||
/// of dataclasses or NamedTuples. These callables act like real functions when accessed
|
||||
@@ -7336,10 +7303,6 @@ impl<'db> CallableType<'db> {
|
||||
///
|
||||
/// See [`Type::is_equivalent_to`] for more details.
|
||||
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.is_function_like(db) == other.is_function_like(db)
|
||||
&& self
|
||||
.signatures(db)
|
||||
|
||||
@@ -730,6 +730,38 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Overload) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `typing.overload` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Override) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `typing.override` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::AbstractMethod) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `abc.abstractmethod` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::Final) => {
|
||||
// TODO: This can be removed once we understand legacy generics because the
|
||||
// typeshed definition for `typing.final` is an identity function.
|
||||
if let [Some(ty)] = overload.parameter_types() {
|
||||
overload.set_return_type(*ty);
|
||||
}
|
||||
}
|
||||
|
||||
Some(KnownFunction::GetattrStatic) => {
|
||||
let [Some(instance_ty), Some(attr_name), default] =
|
||||
overload.parameter_types()
|
||||
|
||||
@@ -11,7 +11,7 @@ use super::{
|
||||
};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||
use crate::semantic_index::place::NodeWithScopeKind;
|
||||
use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex, attribute_declarations};
|
||||
use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex};
|
||||
use crate::types::context::InferContext;
|
||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
@@ -1763,7 +1763,10 @@ impl<'db> ClassLiteral<'db> {
|
||||
let class_map = use_def_map(db, class_body_scope);
|
||||
let class_table = place_table(db, class_body_scope);
|
||||
|
||||
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
||||
for (attribute_assignments, method_scope_id) in
|
||||
attribute_assignments(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
||||
let method_name = method_def.name.as_str();
|
||||
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
||||
@@ -1771,53 +1774,10 @@ impl<'db> ClassLiteral<'db> {
|
||||
{
|
||||
let method_decorator = MethodDecorator::try_from_fn_type(db, method_type);
|
||||
if method_decorator != Ok(target_method_decorator) {
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
// First check declarations
|
||||
for (attribute_declarations, method_scope_id) in
|
||||
attribute_declarations(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
if !is_valid_scope(method_scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for attribute_declaration in attribute_declarations {
|
||||
let DefinitionState::Defined(decl) = attribute_declaration.declaration else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let DefinitionKind::AnnotatedAssignment(annotated) = decl.kind(db) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if use_def_map(db, method_scope)
|
||||
.is_declaration_reachable(db, &attribute_declaration)
|
||||
.is_always_false()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let annotation_ty =
|
||||
infer_expression_type(db, index.expression(annotated.annotation(&module)));
|
||||
|
||||
return Place::bound(annotation_ty);
|
||||
}
|
||||
}
|
||||
|
||||
for (attribute_assignments, method_scope_id) in
|
||||
attribute_assignments(db, class_body_scope, name)
|
||||
{
|
||||
let method_scope = method_scope_id.to_scope_id(db, file);
|
||||
if !is_valid_scope(method_scope) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let method_map = use_def_map(db, method_scope);
|
||||
|
||||
// The attribute assignment inherits the reachability of the method which contains it
|
||||
@@ -2055,7 +2015,6 @@ impl<'db> ClassLiteral<'db> {
|
||||
|
||||
let declarations = use_def.end_of_scope_declarations(place_id);
|
||||
let declared_and_qualifiers = place_from_declarations(db, declarations);
|
||||
|
||||
match declared_and_qualifiers {
|
||||
Ok(PlaceAndQualifiers {
|
||||
place: mut declared @ Place::Type(declared_ty, declaredness),
|
||||
|
||||
@@ -1,61 +1,25 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::FxIndexSet;
|
||||
use crate::types::Type;
|
||||
use std::cmp::Eq;
|
||||
use std::hash::Hash;
|
||||
|
||||
pub(crate) type TypeTransformer<'db> = CycleDetector<Type<'db>, Type<'db>>;
|
||||
|
||||
impl Default for TypeTransformer<'_> {
|
||||
fn default() -> Self {
|
||||
// TODO: proper recursive type handling
|
||||
|
||||
// This must be Any, not e.g. a todo type, because Any is the normalized form of the
|
||||
// dynamic type (that is, todo types are normalized to Any).
|
||||
CycleDetector::new(Type::any())
|
||||
}
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct TypeTransformer<'db> {
|
||||
seen: FxIndexSet<Type<'db>>,
|
||||
}
|
||||
|
||||
pub(crate) type PairVisitor<'db> = CycleDetector<(Type<'db>, Type<'db>), bool>;
|
||||
impl<'db> TypeTransformer<'db> {
|
||||
pub(crate) fn visit(
|
||||
&mut self,
|
||||
ty: Type<'db>,
|
||||
func: impl FnOnce(&mut Self) -> Type<'db>,
|
||||
) -> Type<'db> {
|
||||
if !self.seen.insert(ty) {
|
||||
// TODO: proper recursive type handling
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CycleDetector<T, R> {
|
||||
/// If the type we're visiting is present in `seen`,
|
||||
/// it indicates that we've hit a cycle (due to a recursive type);
|
||||
/// we need to immediately short circuit the whole operation and return the fallback value.
|
||||
/// That's why we pop items off the end of `seen` after we've visited them.
|
||||
seen: FxIndexSet<T>,
|
||||
|
||||
/// Unlike `seen`, this field is a pure performance optimisation (and an essential one).
|
||||
/// If the type we're trying to normalize is present in `cache`, it doesn't necessarily mean we've hit a cycle:
|
||||
/// it just means that we've already visited this inner type as part of a bigger call chain we're currently in.
|
||||
/// Since this cache is just a performance optimisation, it doesn't make sense to pop items off the end of the
|
||||
/// cache after they've been visited (it would sort-of defeat the point of a cache if we did!)
|
||||
cache: FxHashMap<T, R>,
|
||||
|
||||
fallback: R,
|
||||
}
|
||||
|
||||
impl<T: Hash + Eq + Copy, R: Copy> CycleDetector<T, R> {
|
||||
pub(crate) fn new(fallback: R) -> Self {
|
||||
CycleDetector {
|
||||
seen: FxIndexSet::default(),
|
||||
cache: FxHashMap::default(),
|
||||
fallback,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn visit(&mut self, item: T, func: impl FnOnce(&mut Self) -> R) -> R {
|
||||
if !self.seen.insert(item) {
|
||||
return self.fallback;
|
||||
}
|
||||
if let Some(ty) = self.cache.get(&item) {
|
||||
self.seen.pop();
|
||||
return *ty;
|
||||
// This must be Any, not e.g. a todo type, because Any is the normalized form of the
|
||||
// dynamic type (that is, todo types are normalized to Any).
|
||||
return Type::any();
|
||||
}
|
||||
let ret = func(self);
|
||||
self.cache.insert(item, ret);
|
||||
self.seen.pop();
|
||||
ret
|
||||
}
|
||||
|
||||
@@ -2426,7 +2426,7 @@ fn report_invalid_base<'ctx, 'db>(
|
||||
|
||||
/// This function receives an unresolved `from foo import bar` import,
|
||||
/// where `foo` can be resolved to a module but that module does not
|
||||
/// have a `bar` member or submodule.
|
||||
/// have a `bar` member or submdoule.
|
||||
///
|
||||
/// If the `foo` module originates from the standard library and `foo.bar`
|
||||
/// *does* exist as a submodule in the standard library on *other* Python
|
||||
|
||||
@@ -767,8 +767,8 @@ impl<'db> FunctionType<'db> {
|
||||
}
|
||||
|
||||
/// Convert the `FunctionType` into a [`Type::Callable`].
|
||||
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> CallableType<'db> {
|
||||
CallableType::new(db, self.signature(db), false)
|
||||
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(db, self.signature(db), false))
|
||||
}
|
||||
|
||||
/// Convert the `FunctionType` into a [`Type::BoundMethod`].
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
use crate::place::{Place, imported_symbol, place_from_bindings, place_from_declarations};
|
||||
use crate::semantic_index::definition::DefinitionKind;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::{
|
||||
attribute_scopes, global_scope, imported_modules, place_table, semantic_index, use_def_map,
|
||||
};
|
||||
use crate::types::{ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type};
|
||||
use crate::{Db, NameKind};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::name::Name;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
@@ -244,37 +241,3 @@ impl AllMembers {
|
||||
pub fn all_members<'db>(db: &'db dyn Db, ty: Type<'db>) -> FxHashSet<Name> {
|
||||
AllMembers::of(db, ty).members
|
||||
}
|
||||
|
||||
/// Get the primary definition kind for a name expression within a specific file.
|
||||
/// Returns the first definition kind that is reachable for this name in its scope.
|
||||
/// This is useful for IDE features like semantic tokens.
|
||||
pub fn definition_kind_for_name<'db>(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
name: &ast::ExprName,
|
||||
) -> Option<DefinitionKind<'db>> {
|
||||
let index = semantic_index(db, file);
|
||||
let name_str = name.id.as_str();
|
||||
|
||||
// Get the scope for this name expression
|
||||
let file_scope = index.try_expression_scope_id(&ast::Expr::Name(name.clone()))?;
|
||||
|
||||
// Get the place table for this scope
|
||||
let place_table = index.place_table(file_scope);
|
||||
|
||||
// Look up the place by name
|
||||
let place_id = place_table.place_id_by_name(name_str)?;
|
||||
|
||||
// Get the use-def map and look up definitions for this place
|
||||
let use_def_map = index.use_def_map(file_scope);
|
||||
let declarations = use_def_map.all_reachable_declarations(place_id);
|
||||
|
||||
// Find the first valid definition and return its kind
|
||||
for declaration in declarations {
|
||||
if let Some(def) = declaration.declaration.definition() {
|
||||
return Some(def.kind(db).clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -2122,9 +2122,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
node_index: _,
|
||||
value,
|
||||
}) => {
|
||||
// If this is a call expression, we would have added a `ReturnsNever` constraint,
|
||||
// meaning this will be a standalone expression.
|
||||
self.infer_maybe_standalone_expression(value);
|
||||
self.infer_expression(value);
|
||||
}
|
||||
ast::Stmt::If(if_statement) => self.infer_if_statement(if_statement),
|
||||
ast::Stmt::Try(try_statement) => self.infer_try_statement(try_statement),
|
||||
@@ -3495,7 +3493,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
self.context.report_lint(&UNRESOLVED_ATTRIBUTE, target)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Can not assign object of type `{}` to attribute \
|
||||
"Can not assign object of `{}` to attribute \
|
||||
`{attribute}` on type `{}` with \
|
||||
custom `__setattr__` method.",
|
||||
value_ty.display(db),
|
||||
@@ -5265,8 +5263,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// arguments after matching them to parameters, but before checking that the argument types
|
||||
// are assignable to any parameter annotations.
|
||||
let call_arguments = Self::parse_arguments(arguments);
|
||||
|
||||
let callable_type = self.infer_maybe_standalone_expression(func);
|
||||
let callable_type = self.infer_expression(func);
|
||||
|
||||
if let Type::FunctionLiteral(function) = callable_type {
|
||||
// Make sure that the `function.definition` is only called when the function is defined
|
||||
|
||||
@@ -5,7 +5,6 @@ use std::marker::PhantomData;
|
||||
use super::protocol_class::ProtocolInterface;
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::types::cyclic::PairVisitor;
|
||||
use crate::types::protocol_class::walk_protocol_interface;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance};
|
||||
@@ -119,7 +118,7 @@ impl<'db> NominalInstanceType<'db> {
|
||||
self.class.is_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
!self.class.could_coexist_in_mro_with(db, other.class)
|
||||
}
|
||||
|
||||
@@ -270,14 +269,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
let self_normalized = self.normalized(db);
|
||||
if self_normalized == Type::ProtocolInstance(other) {
|
||||
return true;
|
||||
}
|
||||
self_normalized == other.normalized(db)
|
||||
self.normalized(db) == other.normalized(db)
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type is disjoint from the protocol `other`.
|
||||
@@ -285,12 +277,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
/// TODO: a protocol `X` is disjoint from a protocol `Y` if `X` and `Y`
|
||||
/// have a member with the same name but disjoint types
|
||||
#[expect(clippy::unused_self)]
|
||||
pub(super) fn is_disjoint_from_impl(
|
||||
self,
|
||||
_db: &'db dyn Db,
|
||||
_other: Self,
|
||||
_visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
pub(super) fn is_disjoint_from(self, _db: &'db dyn Db, _other: Self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::place::{PlaceExpr, PlaceTable, ScopeId, ScopedPlaceId};
|
||||
use crate::semantic_index::place_table;
|
||||
use crate::semantic_index::predicate::{
|
||||
CallableAndCallExpr, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
PatternPredicate, PatternPredicateKind, Predicate, PredicateNode,
|
||||
};
|
||||
use crate::types::function::KnownFunction;
|
||||
use crate::types::infer::infer_same_file_expression_type;
|
||||
@@ -59,7 +59,6 @@ pub(crate) fn infer_narrowing_constraint<'db>(
|
||||
all_negative_narrowing_constraints_for_pattern(db, pattern)
|
||||
}
|
||||
}
|
||||
PredicateNode::ReturnsNever(_) => return None,
|
||||
PredicateNode::StarImportPlaceholder(_) => return None,
|
||||
};
|
||||
if let Some(constraints) = constraints {
|
||||
@@ -347,7 +346,6 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
PredicateNode::Pattern(pattern) => {
|
||||
self.evaluate_pattern_predicate(pattern, self.is_positive)
|
||||
}
|
||||
PredicateNode::ReturnsNever(_) => return None,
|
||||
PredicateNode::StarImportPlaceholder(_) => return None,
|
||||
};
|
||||
if let Some(mut constraints) = constraints {
|
||||
@@ -431,9 +429,6 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
||||
match self.predicate {
|
||||
PredicateNode::Expression(expression) => expression.scope(self.db),
|
||||
PredicateNode::Pattern(pattern) => pattern.scope(self.db),
|
||||
PredicateNode::ReturnsNever(CallableAndCallExpr { callable, .. }) => {
|
||||
callable.scope(self.db)
|
||||
}
|
||||
PredicateNode::StarImportPlaceholder(definition) => definition.scope(self.db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,6 @@ mod flaky {
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{intersection, union};
|
||||
use crate::types::{KnownClass, Type};
|
||||
|
||||
// Negating `T` twice is equivalent to `T`.
|
||||
type_property_test!(
|
||||
@@ -312,14 +311,4 @@ mod flaky {
|
||||
bottom_materialization_of_type_is_assigneble_to_type, db,
|
||||
forall types t. t.bottom_materialization(db).is_assignable_to(db, t)
|
||||
);
|
||||
|
||||
// Any type assignable to `Iterable[object]` should be considered iterable.
|
||||
//
|
||||
// Note that the inverse is not true, due to the fact that we recognize the old-style
|
||||
// iteration protocol as well as the new-style iteration protocol: not all objects that
|
||||
// we consider iterable are assignable to `Iterable[object]`.
|
||||
type_property_test!(
|
||||
all_type_assignable_to_iterable_are_iterable, db,
|
||||
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object(db)])) => t.try_iterate(db).is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,10 +55,6 @@ pub(crate) enum Ty {
|
||||
params: CallableParams,
|
||||
returns: Option<Box<Ty>>,
|
||||
},
|
||||
/// `unittest.mock.Mock` is interesting because it is a nominal instance type
|
||||
/// where the class has `Any` in its MRO
|
||||
UnittestMockInstance,
|
||||
UnittestMockLiteral,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -148,13 +144,6 @@ impl Ty {
|
||||
Ty::AbcClassLiteral(s) => known_module_symbol(db, KnownModule::Abc, s)
|
||||
.place
|
||||
.expect_type(),
|
||||
Ty::UnittestMockLiteral => known_module_symbol(db, KnownModule::UnittestMock, "Mock")
|
||||
.place
|
||||
.expect_type(),
|
||||
Ty::UnittestMockInstance => Ty::UnittestMockLiteral
|
||||
.into_type(db)
|
||||
.to_instance(db)
|
||||
.unwrap(),
|
||||
Ty::TypingLiteral => Type::SpecialForm(SpecialFormType::Literal),
|
||||
Ty::BuiltinClassLiteral(s) => builtins_symbol(db, s).place.expect_type(),
|
||||
Ty::KnownClassInstance(known_class) => known_class.to_instance(db),
|
||||
@@ -234,13 +223,11 @@ fn arbitrary_core_type(g: &mut Gen, fully_static: bool) -> Ty {
|
||||
let bool_lit = Ty::BooleanLiteral(bool::arbitrary(g));
|
||||
|
||||
// Update this if new non-fully-static types are added below.
|
||||
let fully_static_index = 5;
|
||||
let fully_static_index = 3;
|
||||
let types = &[
|
||||
Ty::Any,
|
||||
Ty::Unknown,
|
||||
Ty::SubclassOfAny,
|
||||
Ty::UnittestMockLiteral,
|
||||
Ty::UnittestMockInstance,
|
||||
// Add fully static types below, dynamic types above.
|
||||
// Update `fully_static_index` above if adding new dynamic types!
|
||||
Ty::Never,
|
||||
|
||||
@@ -11,7 +11,6 @@ use crate::{
|
||||
types::{
|
||||
CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature,
|
||||
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance,
|
||||
cyclic::PairVisitor,
|
||||
signatures::{Parameter, Parameters},
|
||||
},
|
||||
};
|
||||
@@ -260,7 +259,7 @@ impl<'db> ProtocolMemberData<'db> {
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash)]
|
||||
enum ProtocolMemberKind<'db> {
|
||||
Method(CallableType<'db>),
|
||||
Method(Type<'db>), // TODO: use CallableType
|
||||
Property(PropertyInstanceType<'db>),
|
||||
Other(Type<'db>),
|
||||
}
|
||||
@@ -335,7 +334,7 @@ fn walk_protocol_member<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match member.kind {
|
||||
ProtocolMemberKind::Method(method) => visitor.visit_callable_type(db, method),
|
||||
ProtocolMemberKind::Method(method) => visitor.visit_type(db, method),
|
||||
ProtocolMemberKind::Property(property) => {
|
||||
visitor.visit_property_instance_type(db, property);
|
||||
}
|
||||
@@ -354,24 +353,17 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
|
||||
fn ty(&self) -> Type<'db> {
|
||||
match &self.kind {
|
||||
ProtocolMemberKind::Method(callable) => Type::Callable(*callable),
|
||||
ProtocolMemberKind::Method(callable) => *callable,
|
||||
ProtocolMemberKind::Property(property) => Type::PropertyInstance(*property),
|
||||
ProtocolMemberKind::Other(ty) => *ty,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn has_disjoint_type_from(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: Type<'db>,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
pub(super) fn has_disjoint_type_from(&self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
match &self.kind {
|
||||
// TODO: implement disjointness for property/method members as well as attribute members
|
||||
ProtocolMemberKind::Property(_) | ProtocolMemberKind::Method(_) => false,
|
||||
ProtocolMemberKind::Other(ty) => {
|
||||
visitor.visit((*ty, other), |v| ty.is_disjoint_from_impl(db, other, v))
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => ty.is_disjoint_from(db, other),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,10 +500,13 @@ fn cached_protocol_interface<'db>(
|
||||
(Type::Callable(callable), BoundOnClass::Yes)
|
||||
if callable.is_function_like(db) =>
|
||||
{
|
||||
ProtocolMemberKind::Method(callable)
|
||||
ProtocolMemberKind::Method(ty)
|
||||
}
|
||||
(Type::FunctionLiteral(function), BoundOnClass::Yes) => {
|
||||
ProtocolMemberKind::Method(function.into_callable_type(db))
|
||||
// TODO: method members that have `FunctionLiteral` types should be upcast
|
||||
// to `CallableType` so that two protocols with identical method members
|
||||
// are recognized as equivalent.
|
||||
(Type::FunctionLiteral(_function), BoundOnClass::Yes) => {
|
||||
ProtocolMemberKind::Method(ty)
|
||||
}
|
||||
_ => ProtocolMemberKind::Other(ty),
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user