Compare commits
2 Commits
david/unre
...
zb/fuzz-ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48eb23b488 | ||
|
|
7f624cd0bb |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -13,10 +13,9 @@
|
||||
# flake8-pyi
|
||||
/crates/ruff_linter/src/rules/flake8_pyi/ @AlexWaygood
|
||||
|
||||
# Script for fuzzing the parser/red-knot etc.
|
||||
/python/py-fuzzer/ @AlexWaygood
|
||||
# Script for fuzzing the parser
|
||||
/scripts/fuzz-parser/ @AlexWaygood
|
||||
|
||||
# red-knot
|
||||
/crates/red_knot* @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/crates/ruff_db/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
/scripts/knot_benchmark/ @carljm @MichaReiser @AlexWaygood @sharkdp
|
||||
|
||||
74
.github/workflows/ci.yaml
vendored
74
.github/workflows/ci.yaml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_python_parser/**
|
||||
- python/py-fuzzer/**
|
||||
- scripts/fuzz-parser/**
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
linter:
|
||||
@@ -82,7 +82,6 @@ jobs:
|
||||
code:
|
||||
- "**/*"
|
||||
- "!**/*.md"
|
||||
- "crates/red_knot_python_semantic/resources/mdtest/**/*.md"
|
||||
- "!docs/**"
|
||||
- "!assets/**"
|
||||
|
||||
@@ -116,7 +115,7 @@ jobs:
|
||||
|
||||
cargo-test-linux:
|
||||
name: "cargo test (linux)"
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
@@ -158,36 +157,9 @@ jobs:
|
||||
name: ruff
|
||||
path: target/debug/ruff
|
||||
|
||||
cargo-test-linux-release:
|
||||
name: "cargo test (linux, release)"
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
cargo-test-windows:
|
||||
name: "cargo test (windows)"
|
||||
runs-on: windows-latest-xlarge
|
||||
runs-on: windows-latest
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
@@ -225,8 +197,6 @@ jobs:
|
||||
cache: "npm"
|
||||
cache-dependency-path: playground/package-lock.json
|
||||
- uses: jetli/wasm-pack-action@v0.4.0
|
||||
with:
|
||||
version: v0.13.1
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "Test ruff_wasm"
|
||||
run: |
|
||||
@@ -240,7 +210,8 @@ jobs:
|
||||
cargo-build-release:
|
||||
name: "cargo build (release)"
|
||||
runs-on: macos-latest
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
needs: determine_changes
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -284,11 +255,11 @@ jobs:
|
||||
NEXTEST_PROFILE: "ci"
|
||||
run: cargo +${{ steps.msrv.outputs.value }} insta test --all-features --unreferenced reject --test-runner nextest
|
||||
|
||||
cargo-fuzz-build:
|
||||
name: "cargo fuzz build"
|
||||
cargo-fuzz:
|
||||
name: "cargo fuzz"
|
||||
runs-on: ubuntu-latest
|
||||
needs: determine_changes
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
if: ${{ needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main' }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -297,6 +268,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "fuzz -> target"
|
||||
cache-all-crates: "true"
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
with:
|
||||
@@ -307,7 +279,7 @@ jobs:
|
||||
- run: cargo fuzz build -s none
|
||||
|
||||
fuzz-parser:
|
||||
name: "fuzz parser"
|
||||
name: "Fuzz the parser"
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
@@ -318,7 +290,13 @@ jobs:
|
||||
FORCE_COLOR: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install Python requirements
|
||||
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -330,15 +308,7 @@ jobs:
|
||||
# Make executable, since artifact download doesn't preserve this
|
||||
chmod +x ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
|
||||
(
|
||||
uvx \
|
||||
--python=${{ env.PYTHON_VERSION }} \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable=${{ steps.download-cached-binary.outputs.download-path }}/ruff \
|
||||
--bin=ruff \
|
||||
0-500
|
||||
)
|
||||
python scripts/fuzz-parser/fuzz.py 0-500 --test-executable ${{ steps.download-cached-binary.outputs.download-path }}/ruff
|
||||
|
||||
scripts:
|
||||
name: "test scripts"
|
||||
@@ -362,7 +332,7 @@ jobs:
|
||||
|
||||
ecosystem:
|
||||
name: "ecosystem"
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- cargo-test-linux
|
||||
- determine_changes
|
||||
@@ -592,12 +562,12 @@ jobs:
|
||||
run: rustup show
|
||||
- name: "Cache rust"
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: "Run checks"
|
||||
- name: "Formatter progress"
|
||||
run: scripts/formatter_ecosystem_checks.sh
|
||||
- name: "Github step summary"
|
||||
run: cat target/formatter-ecosystem/stats.txt > $GITHUB_STEP_SUMMARY
|
||||
run: cat target/progress_projects_stats.txt > $GITHUB_STEP_SUMMARY
|
||||
- name: "Remove checkouts from cache"
|
||||
run: rm -r target/formatter-ecosystem
|
||||
run: rm -r target/progress_projects
|
||||
|
||||
check-ruff-lsp:
|
||||
name: "test ruff-lsp"
|
||||
|
||||
19
.github/workflows/daily_fuzz.yaml
vendored
19
.github/workflows/daily_fuzz.yaml
vendored
@@ -32,7 +32,13 @@ jobs:
|
||||
if: ${{ github.repository == 'astral-sh/ruff' || github.event_name != 'schedule' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: astral-sh/setup-uv@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install Python requirements
|
||||
run: uv pip install -r scripts/fuzz-parser/requirements.txt --system
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
@@ -43,16 +49,7 @@ jobs:
|
||||
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI
|
||||
run: cargo build --locked
|
||||
- name: Fuzz
|
||||
run: |
|
||||
(
|
||||
uvx \
|
||||
--python=3.12 \
|
||||
--from=./python/py-fuzzer \
|
||||
fuzz \
|
||||
--test-executable=target/debug/ruff \
|
||||
--bin=ruff \
|
||||
$(shuf -i 0-9999999999999999999 -n 1000)
|
||||
)
|
||||
run: python scripts/fuzz-parser/fuzz.py $(shuf -i 0-9999999999999999999 -n 1000) --test-executable target/debug/ruff
|
||||
|
||||
create-issue-on-failure:
|
||||
name: Create an issue if the daily fuzz surfaced any bugs
|
||||
|
||||
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
||||
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
|
||||
# This file was autogenerated by cargo-dist: https://opensource.axo.dev/cargo-dist/
|
||||
#
|
||||
# Copyright 2022-2024, axodotdev
|
||||
# SPDX-License-Identifier: MIT or Apache-2.0
|
||||
@@ -6,7 +6,7 @@
|
||||
# CI that:
|
||||
#
|
||||
# * checks for a Git Tag that looks like a release
|
||||
# * builds artifacts with dist (archives, installers, hashes)
|
||||
# * builds artifacts with cargo-dist (archives, installers, hashes)
|
||||
# * uploads those artifacts to temporary workflow zip
|
||||
# * on success, uploads the artifacts to a GitHub Release
|
||||
#
|
||||
@@ -24,10 +24,10 @@ permissions:
|
||||
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
|
||||
#
|
||||
# If PACKAGE_NAME is specified, then the announcement will be for that
|
||||
# package (erroring out if it doesn't have the given version or isn't dist-able).
|
||||
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
|
||||
#
|
||||
# If PACKAGE_NAME isn't specified, then the announcement will be for all
|
||||
# (dist-able) packages in the workspace with that version (this mode is
|
||||
# (cargo-dist-able) packages in the workspace with that version (this mode is
|
||||
# intended for workspaces with only one dist-able package, or with all dist-able
|
||||
# packages versioned/released in lockstep).
|
||||
#
|
||||
@@ -48,7 +48,7 @@ on:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
# Run 'dist plan' (or host) to determine what tasks we need to do
|
||||
# Run 'cargo dist plan' (or host) to determine what tasks we need to do
|
||||
plan:
|
||||
runs-on: "ubuntu-20.04"
|
||||
outputs:
|
||||
@@ -62,16 +62,16 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install dist
|
||||
- name: Install cargo-dist
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.25.2-prerelease.3/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.22.1/cargo-dist-installer.sh | sh"
|
||||
- name: Cache cargo-dist
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
path: ~/.cargo/bin/cargo-dist
|
||||
# sure would be cool if github gave us proper conditionals...
|
||||
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
|
||||
# functionality based on whether this is a pull_request, and whether it's from a fork.
|
||||
@@ -79,8 +79,8 @@ jobs:
|
||||
# but also really annoying to build CI around when it needs secrets to work right.)
|
||||
- id: plan
|
||||
run: |
|
||||
dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
|
||||
echo "dist ran successfully"
|
||||
cargo dist ${{ (inputs.tag && inputs.tag != 'dry-run' && format('host --steps=create --tag={0}', inputs.tag)) || 'plan' }} --output-format=json > plan-dist-manifest.json
|
||||
echo "cargo dist ran successfully"
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
@@ -124,12 +124,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
- name: Install cached cargo-dist
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
- run: chmod +x ~/.cargo/bin/cargo-dist
|
||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||
- name: Fetch local artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -140,8 +140,8 @@ jobs:
|
||||
- id: cargo-dist
|
||||
shell: bash
|
||||
run: |
|
||||
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
|
||||
echo "dist ran successfully"
|
||||
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
|
||||
echo "cargo dist ran successfully"
|
||||
|
||||
# Parse out what we just built and upload it to scratch storage
|
||||
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
|
||||
@@ -174,12 +174,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install cached dist
|
||||
- name: Install cached cargo-dist
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/
|
||||
- run: chmod +x ~/.cargo/bin/dist
|
||||
- run: chmod +x ~/.cargo/bin/cargo-dist
|
||||
# Fetch artifacts from scratch-storage
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
- id: host
|
||||
shell: bash
|
||||
run: |
|
||||
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
|
||||
cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
|
||||
echo "artifacts uploaded and released successfully"
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.8.0
|
||||
|
||||
- **Default to Python 3.9**
|
||||
|
||||
Ruff now defaults to Python 3.9 instead of 3.8 if no explicit Python version is configured using [`ruff.target-version`](https://docs.astral.sh/ruff/settings/#target-version) or [`project.requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) ([#13896](https://github.com/astral-sh/ruff/pull/13896))
|
||||
|
||||
- **Changed location of `pydoclint` diagnostics**
|
||||
|
||||
[`pydoclint`](https://docs.astral.sh/ruff/rules/#pydoclint-doc) diagnostics now point to the first-line of the problematic docstring. Previously, this was not the case.
|
||||
|
||||
If you've opted into these preview rules but have them suppressed using
|
||||
[`noqa`](https://docs.astral.sh/ruff/linter/#error-suppression) comments in
|
||||
some places, this change may mean that you need to move the `noqa` suppression
|
||||
comments. Most users should be unaffected by this change.
|
||||
|
||||
- **Use XDG (i.e. `~/.local/bin`) instead of the Cargo home directory in the standalone installer**
|
||||
|
||||
Previously, Ruff's installer used `$CARGO_HOME` or `~/.cargo/bin` for its target install directory. Now, Ruff will be installed into `$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin` (in that order).
|
||||
|
||||
This change is only relevant to users of the standalone Ruff installer (using the shell or PowerShell script). If you installed Ruff using uv or pip, you should be unaffected.
|
||||
|
||||
- **Changes to the line width calculation**
|
||||
|
||||
Ruff now uses a new version of the [unicode-width](https://github.com/unicode-rs/unicode-width) Rust crate to calculate the line width. In very rare cases, this may lead to lines containing Unicode characters being reformatted, or being considered too long when they were not before ([`E501`](https://docs.astral.sh/ruff/rules/line-too-long/)).
|
||||
|
||||
## 0.7.0
|
||||
|
||||
- The pytest rules `PT001` and `PT023` now default to omitting the decorator parentheses when there are no arguments
|
||||
|
||||
139
CHANGELOG.md
139
CHANGELOG.md
@@ -1,142 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- Formatter: Avoid invalid syntax for format-spec with quotes for all Python versions ([#14625](https://github.com/astral-sh/ruff/pull/14625))
|
||||
- Formatter: Consider quotes inside format-specs when choosing the quotes for an f-string ([#14493](https://github.com/astral-sh/ruff/pull/14493))
|
||||
- Formatter: Do not consider f-strings with escaped newlines as multiline ([#14624](https://github.com/astral-sh/ruff/pull/14624))
|
||||
- Formatter: Fix f-string formatting in assignment statement ([#14454](https://github.com/astral-sh/ruff/pull/14454))
|
||||
- Formatter: Fix unnecessary space around power operator (`**`) in overlong f-string expressions ([#14489](https://github.com/astral-sh/ruff/pull/14489))
|
||||
- \[`airflow`\] Avoid implicit `schedule` argument to `DAG` and `@dag` (`AIR301`) ([#14581](https://github.com/astral-sh/ruff/pull/14581))
|
||||
- \[`flake8-builtins`\] Exempt private built-in modules (`A005`) ([#14505](https://github.com/astral-sh/ruff/pull/14505))
|
||||
- \[`flake8-pytest-style`\] Fix `pytest.mark.parametrize` rules to check calls instead of decorators ([#14515](https://github.com/astral-sh/ruff/pull/14515))
|
||||
- \[`flake8-type-checking`\] Implement `runtime-cast-value` (`TC006`) ([#14511](https://github.com/astral-sh/ruff/pull/14511))
|
||||
- \[`flake8-type-checking`\] Implement `unquoted-type-alias` (`TC007`) and `quoted-type-alias` (`TC008`) ([#12927](https://github.com/astral-sh/ruff/pull/12927))
|
||||
- \[`flake8-use-pathlib`\] Recommend `Path.iterdir()` over `os.listdir()` (`PTH208`) ([#14509](https://github.com/astral-sh/ruff/pull/14509))
|
||||
- \[`pylint`\] Extend `invalid-envvar-default` to detect `os.environ.get` (`PLW1508`) ([#14512](https://github.com/astral-sh/ruff/pull/14512))
|
||||
- \[`pylint`\] Implement `len-test` (`PLC1802`) ([#14309](https://github.com/astral-sh/ruff/pull/14309))
|
||||
- \[`refurb`\] Fix bug where methods defined using lambdas were flagged by `FURB118` ([#14639](https://github.com/astral-sh/ruff/pull/14639))
|
||||
- \[`ruff`\] Auto-add `r` prefix when string has no backslashes for `unraw-re-pattern` (`RUF039`) ([#14536](https://github.com/astral-sh/ruff/pull/14536))
|
||||
- \[`ruff`\] Implement `invalid-assert-message-literal-argument` (`RUF040`) ([#14488](https://github.com/astral-sh/ruff/pull/14488))
|
||||
- \[`ruff`\] Implement `unnecessary-nested-literal` (`RUF041`) ([#14323](https://github.com/astral-sh/ruff/pull/14323))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Ignore more rules for stub files ([#14541](https://github.com/astral-sh/ruff/pull/14541))
|
||||
- \[`pep8-naming`\] Eliminate false positives for single-letter names (`N811`, `N814`) ([#14584](https://github.com/astral-sh/ruff/pull/14584))
|
||||
- \[`pyflakes`\] Avoid false positives in `@no_type_check` contexts (`F821`, `F722`) ([#14615](https://github.com/astral-sh/ruff/pull/14615))
|
||||
- \[`ruff`\] Detect redirected-noqa in file-level comments (`RUF101`) ([#14635](https://github.com/astral-sh/ruff/pull/14635))
|
||||
- \[`ruff`\] Mark fixes for `unsorted-dunder-all` and `unsorted-dunder-slots` as unsafe when there are complex comments in the sequence (`RUF022`, `RUF023`) ([#14560](https://github.com/astral-sh/ruff/pull/14560))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid fixing code to `None | None` for `redundant-none-literal` (`PYI061`) and `never-union` (`RUF020`) ([#14583](https://github.com/astral-sh/ruff/pull/14583), [#14589](https://github.com/astral-sh/ruff/pull/14589))
|
||||
- \[`flake8-bugbear`\] Fix `mutable-contextvar-default` to resolve annotated function calls properly (`B039`) ([#14532](https://github.com/astral-sh/ruff/pull/14532))
|
||||
- \[`flake8-type-checking`\] Avoid syntax errors and type checking problem for quoted annotations autofix (`TC003`, `TC006`) ([#14634](https://github.com/astral-sh/ruff/pull/14634))
|
||||
- \[`pylint`\] Do not wrap function calls in parentheses in the fix for unnecessary-dunder-call (`PLC2801`) ([#14601](https://github.com/astral-sh/ruff/pull/14601))
|
||||
- \[`ruff`\] Handle `attrs`'s `auto_attribs` correctly (`RUF009`) ([#14520](https://github.com/astral-sh/ruff/pull/14520))
|
||||
|
||||
## 0.8.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.8.0) for a migration guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
See also, the "Remapped rules" section which may result in disabled rules.
|
||||
|
||||
- **Default to Python 3.9**
|
||||
|
||||
Ruff now defaults to Python 3.9 instead of 3.8 if no explicit Python version is configured using [`ruff.target-version`](https://docs.astral.sh/ruff/settings/#target-version) or [`project.requires-python`](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#python-requires) ([#13896](https://github.com/astral-sh/ruff/pull/13896))
|
||||
|
||||
- **Changed location of `pydoclint` diagnostics**
|
||||
|
||||
[`pydoclint`](https://docs.astral.sh/ruff/rules/#pydoclint-doc) diagnostics now point to the first-line of the problematic docstring. Previously, this was not the case.
|
||||
|
||||
If you've opted into these preview rules but have them suppressed using
|
||||
[`noqa`](https://docs.astral.sh/ruff/linter/#error-suppression) comments in
|
||||
some places, this change may mean that you need to move the `noqa` suppression
|
||||
comments. Most users should be unaffected by this change.
|
||||
|
||||
- **Use XDG (i.e. `~/.local/bin`) instead of the Cargo home directory in the standalone installer**
|
||||
|
||||
Previously, Ruff's installer used `$CARGO_HOME` or `~/.cargo/bin` for its target install directory. Now, Ruff will be installed into `$XDG_BIN_HOME`, `$XDG_DATA_HOME/../bin`, or `~/.local/bin` (in that order).
|
||||
|
||||
This change is only relevant to users of the standalone Ruff installer (using the shell or PowerShell script). If you installed Ruff using uv or pip, you should be unaffected.
|
||||
|
||||
- **Changes to the line width calculation**
|
||||
|
||||
Ruff now uses a new version of the [unicode-width](https://github.com/unicode-rs/unicode-width) Rust crate to calculate the line width. In very rare cases, this may lead to lines containing Unicode characters being reformatted, or being considered too long when they were not before ([`E501`](https://docs.astral.sh/ruff/rules/line-too-long/)).
|
||||
|
||||
### Removed Rules
|
||||
|
||||
The following deprecated rules have been removed:
|
||||
|
||||
- [`missing-type-self`](https://docs.astral.sh/ruff/rules/missing-type-self/) (`ANN101`)
|
||||
- [`missing-type-cls`](https://docs.astral.sh/ruff/rules/missing-type-cls/) (`ANN102`)
|
||||
- [`syntax-error`](https://docs.astral.sh/ruff/rules/syntax-error/) (`E999`)
|
||||
- [`pytest-missing-fixture-name-underscore`](https://docs.astral.sh/ruff/rules/pytest-missing-fixture-name-underscore/) (`PT004`)
|
||||
- [`pytest-incorrect-fixture-name-underscore`](https://docs.astral.sh/ruff/rules/pytest-incorrect-fixture-name-underscore/) (`PT005`)
|
||||
- [`unpacked-list-comprehension`](https://docs.astral.sh/ruff/rules/unpacked-list-comprehension/) (`UP027`)
|
||||
|
||||
### Remapped rules
|
||||
|
||||
The following rules have been remapped to new rule codes:
|
||||
|
||||
- [`flake8-type-checking`](https://docs.astral.sh/ruff/rules/#flake8-type-checking-tc): `TCH` to `TC`
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`builtin-import-shadowing`](https://docs.astral.sh/ruff/rules/builtin-import-shadowing/) (`A004`)
|
||||
- [`mutable-contextvar-default`](https://docs.astral.sh/ruff/rules/mutable-contextvar-default/) (`B039`)
|
||||
- [`fast-api-redundant-response-model`](https://docs.astral.sh/ruff/rules/fast-api-redundant-response-model/) (`FAST001`)
|
||||
- [`fast-api-non-annotated-dependency`](https://docs.astral.sh/ruff/rules/fast-api-non-annotated-dependency/) (`FAST002`)
|
||||
- [`dict-index-missing-items`](https://docs.astral.sh/ruff/rules/dict-index-missing-items/) (`PLC0206`)
|
||||
- [`pep484-style-positional-only-parameter`](https://docs.astral.sh/ruff/rules/pep484-style-positional-only-parameter/) (`PYI063`)
|
||||
- [`redundant-final-literal`](https://docs.astral.sh/ruff/rules/redundant-final-literal/) (`PYI064`)
|
||||
- [`bad-version-info-order`](https://docs.astral.sh/ruff/rules/bad-version-info-order/) (`PYI066`)
|
||||
- [`parenthesize-chained-operators`](https://docs.astral.sh/ruff/rules/parenthesize-chained-operators/) (`RUF021`)
|
||||
- [`unsorted-dunder-all`](https://docs.astral.sh/ruff/rules/unsorted-dunder-all/) (`RUF022`)
|
||||
- [`unsorted-dunder-slots`](https://docs.astral.sh/ruff/rules/unsorted-dunder-slots/) (`RUF023`)
|
||||
- [`assert-with-print-message`](https://docs.astral.sh/ruff/rules/assert-with-print-message/) (`RUF030`)
|
||||
- [`unnecessary-default-type-args`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/) (`UP043`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`ambiguous-variable-name`](https://docs.astral.sh/ruff/rules/ambiguous-variable-name/) (`E741`): Violations in stub files are now ignored. Stub authors typically don't control variable names.
|
||||
- [`printf-string-formatting`](https://docs.astral.sh/ruff/rules/printf-string-formatting/) (`UP031`): Report all `printf`-like usages even if no autofix is available
|
||||
|
||||
The following fixes have been stabilized:
|
||||
|
||||
- [`zip-instead-of-pairwise`](https://docs.astral.sh/ruff/rules/zip-instead-of-pairwise/) (`RUF007`)
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-datetimez`\] Exempt `min.time()` and `max.time()` (`DTZ901`) ([#14394](https://github.com/astral-sh/ruff/pull/14394))
|
||||
- \[`flake8-pie`\] Mark fix as unsafe if the following statement is a string literal (`PIE790`) ([#14393](https://github.com/astral-sh/ruff/pull/14393))
|
||||
- \[`flake8-pyi`\] New rule `redundant-none-literal` (`PYI061`) ([#14316](https://github.com/astral-sh/ruff/pull/14316))
|
||||
- \[`flake8-pyi`\] Add autofix for `redundant-numeric-union` (`PYI041`) ([#14273](https://github.com/astral-sh/ruff/pull/14273))
|
||||
- \[`ruff`\] New rule `map-int-version-parsing` (`RUF048`) ([#14373](https://github.com/astral-sh/ruff/pull/14373))
|
||||
- \[`ruff`\] New rule `redundant-bool-literal` (`RUF038`) ([#14319](https://github.com/astral-sh/ruff/pull/14319))
|
||||
- \[`ruff`\] New rule `unraw-re-pattern` (`RUF039`) ([#14446](https://github.com/astral-sh/ruff/pull/14446))
|
||||
- \[`pycodestyle`\] Exempt `pytest.importorskip()` calls (`E402`) ([#14474](https://github.com/astral-sh/ruff/pull/14474))
|
||||
- \[`pylint`\] Autofix suggests using sets when possible (`PLR1714`) ([#14372](https://github.com/astral-sh/ruff/pull/14372))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- [`invalid-pyproject-toml`](https://docs.astral.sh/ruff/rules/invalid-pyproject-toml/) (`RUF200`): Updated to reflect the provisionally accepted [PEP 639](https://peps.python.org/pep-0639/).
|
||||
- \[`flake8-pyi`\] Avoid panic in unfixable case (`PYI041`) ([#14402](https://github.com/astral-sh/ruff/pull/14402))
|
||||
- \[`flake8-type-checking`\] Correctly handle quotes in subscript expression when generating an autofix ([#14371](https://github.com/astral-sh/ruff/pull/14371))
|
||||
- \[`pylint`\] Suggest correct autofix for `__contains__` (`PLC2801`) ([#14424](https://github.com/astral-sh/ruff/pull/14424))
|
||||
|
||||
### Configuration
|
||||
|
||||
- Ruff now emits a warning instead of an error when a configuration [`ignore`](https://docs.astral.sh/ruff/settings/#lint_ignore)s a rule that has been removed ([#14435](https://github.com/astral-sh/ruff/pull/14435))
|
||||
- Ruff now validates that `lint.flake8-import-conventions.aliases` only uses valid module names and aliases ([#14477](https://github.com/astral-sh/ruff/pull/14477))
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### Preview features
|
||||
@@ -1115,7 +978,7 @@ The following deprecated CLI commands have been removed:
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Implement `return-in-generator` (`B901`) ([#11644](https://github.com/astral-sh/ruff/pull/11644))
|
||||
- \[`flake8-pyi`\] Implement `pep484-style-positional-only-parameter` (`PYI063`) ([#11699](https://github.com/astral-sh/ruff/pull/11699))
|
||||
- \[`flake8-pyi`\] Implement `PYI063` ([#11699](https://github.com/astral-sh/ruff/pull/11699))
|
||||
- \[`pygrep_hooks`\] Check blanket ignores via file-level pragmas (`PGH004`) ([#11540](https://github.com/astral-sh/ruff/pull/11540))
|
||||
|
||||
### Rule changes
|
||||
|
||||
@@ -139,7 +139,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
|
||||
1. Create a file for your rule (e.g., `crates/ruff_linter/src/rules/flake8_bugbear/rules/assert_false.rs`).
|
||||
|
||||
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
|
||||
`#[derive(ViolationMetadata)]` to see examples.
|
||||
`#[violation]` to see examples.
|
||||
|
||||
1. In that file, define a function that adds the violation to the diagnostic list as appropriate
|
||||
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
|
||||
|
||||
197
Cargo.lock
generated
197
Cargo.lock
generated
@@ -212,12 +212,6 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boxcar"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.11.0"
|
||||
@@ -413,7 +407,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -693,7 +687,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -704,7 +698,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -758,23 +752,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dir-test"
|
||||
version = "0.4.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b12781621d53fd9087021f5a338df5c57c04f84a6231c1f4726f45e2e333470b"
|
||||
checksum = "5c44bdf9319ad5223afb7eb15a7110452b0adf0373ea6756561b2c708eef0dd1"
|
||||
dependencies = [
|
||||
"dir-test-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dir-test-macros"
|
||||
version = "0.4.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1340852f50b2285d01a7f598cc5d08b572669c3e09e614925175cc3c26787b91"
|
||||
checksum = "644f96047137dfaa7a09e34d4623f9e52a1926ecc25ba32ad2ba3fc422536b25"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -826,7 +820,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1068,9 +1062,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
@@ -1246,7 +1240,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1319,7 +1313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1420,7 +1414,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1526,9 +1520,9 @@ checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.5.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa3e60579a8cba3d86aa4a5f7fc98973cc0fd2ac270bf02f85a9bef09700b075"
|
||||
checksum = "1586dd7a857d8a61a577afde1a24cc9573ff549eff092d5ce968b1ec93cc61b6"
|
||||
dependencies = [
|
||||
"chic",
|
||||
"libcst_derive",
|
||||
@@ -1546,7 +1540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1710,9 +1704,9 @@ checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c"
|
||||
|
||||
[[package]]
|
||||
name = "newtype-uuid"
|
||||
version = "1.1.3"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c8781e2ef64806278a55ad223f0bc875772fd40e1fe6e73e8adbf027817229d"
|
||||
checksum = "3526cb7c660872e401beaf3297f95f548ce3b4b4bdd8121b7c0713771d7c4a6e"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
@@ -1941,6 +1935,18 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a"
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c29f9c43de378b4e4e0cd7dbcce0e5cfb80443de8c05620368b2948bc936a1"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep440_rs"
|
||||
version = "0.7.2"
|
||||
@@ -1950,29 +1956,22 @@ dependencies = [
|
||||
"serde",
|
||||
"unicode-width 0.2.0",
|
||||
"unscanny",
|
||||
"version-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pep508_rs"
|
||||
version = "0.9.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c2feee999fa547bacab06a4881bacc74688858b92fa8ef1e206c748b0a76048"
|
||||
checksum = "910c513bea0f4f833122321c0f20e8c704e01de98692f6989c2ec21f43d88b1e"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"indexmap",
|
||||
"itertools 0.13.0",
|
||||
"once_cell",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"regex",
|
||||
"rustc-hash 2.0.0",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror 1.0.67",
|
||||
"unicode-width 0.2.0",
|
||||
"tracing",
|
||||
"unicode-width 0.1.13",
|
||||
"url",
|
||||
"urlencoding",
|
||||
"version-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2012,7 +2011,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2127,47 +2126,46 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyproject-toml"
|
||||
version = "0.13.4"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643af57c3f36ba90a8b53e972727d8092f7408a9ebfbaf4c3d2c17b07c58d835"
|
||||
checksum = "95c3dd745f99aa3c554b7bb00859f7d18c2f1d6afd749ccc86d60b61e702abd9"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.4.0",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 1.0.67",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-junit"
|
||||
version = "0.5.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7"
|
||||
checksum = "62ffd2f9a162cfae131bed6d9d1ed60adced33be340a94f96952897d7cb0c240"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"indexmap",
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror 2.0.3",
|
||||
"thiserror 1.0.67",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.37.1"
|
||||
version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03"
|
||||
checksum = "96a05e2e8efddfa51a84ca47cec303fac86c8541b686d37cac5efc0e094417bc"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -2266,7 +2264,7 @@ dependencies = [
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.1",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.13.0",
|
||||
@@ -2372,7 +2370,7 @@ dependencies = [
|
||||
"glob",
|
||||
"insta",
|
||||
"notify",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.7.2",
|
||||
"rayon",
|
||||
"red_knot_python_semantic",
|
||||
"red_knot_vendored",
|
||||
@@ -2489,7 +2487,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2676,7 +2674,7 @@ dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.1.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2708,7 +2706,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2731,7 +2729,7 @@ dependencies = [
|
||||
"natord",
|
||||
"path-absolutize",
|
||||
"pathdiff",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.7.2",
|
||||
"pyproject-toml",
|
||||
"quick-junit",
|
||||
"regex",
|
||||
@@ -2762,7 +2760,7 @@ dependencies = [
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
"unicode-width 0.2.0",
|
||||
"unicode-width 0.1.13",
|
||||
"unicode_names2",
|
||||
"url",
|
||||
]
|
||||
@@ -2775,7 +2773,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3023,7 +3021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3062,7 +3060,7 @@ dependencies = [
|
||||
"matchit",
|
||||
"path-absolutize",
|
||||
"path-slash",
|
||||
"pep440_rs",
|
||||
"pep440_rs 0.7.2",
|
||||
"regex",
|
||||
"ruff_cache",
|
||||
"ruff_formatter",
|
||||
@@ -3072,7 +3070,6 @@ dependencies = [
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_semantic",
|
||||
"ruff_python_stdlib",
|
||||
"ruff_source_file",
|
||||
"rustc-hash 2.0.0",
|
||||
"schemars",
|
||||
@@ -3195,7 +3192,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -3229,7 +3226,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3278,7 +3275,7 @@ checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3289,7 +3286,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3312,7 +3309,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3353,7 +3350,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3461,7 +3458,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3472,9 +3469,20 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.89"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3489,7 +3497,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3552,7 +3560,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3563,7 +3571,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"test-case-core",
|
||||
]
|
||||
|
||||
@@ -3593,7 +3601,7 @@ checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3604,7 +3612,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3726,7 +3734,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3865,9 +3873,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -3942,9 +3950,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
||||
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -3952,12 +3960,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
@@ -3996,7 +3998,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4005,15 +4007,6 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version-ranges"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8d079415ceb2be83fc355adbadafe401307d5c309c7e6ade6638e6f9f42f42d"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@@ -4091,7 +4084,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -4125,7 +4118,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -4159,7 +4152,7 @@ checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4462,7 +4455,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4483,7 +4476,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4503,7 +4496,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -4532,7 +4525,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -65,7 +65,7 @@ compact_str = "0.8.0"
|
||||
criterion = { version = "0.5.1", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
dir-test = { version = "0.3.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
env_logger = { version = "0.11.0" }
|
||||
@@ -111,7 +111,7 @@ pathdiff = { version = "0.2.1" }
|
||||
pep440_rs = { version = "0.7.1" }
|
||||
pretty_assertions = "1.3.0"
|
||||
proc-macro2 = { version = "1.0.79" }
|
||||
pyproject-toml = { version = "0.13.4" }
|
||||
pyproject-toml = { version = "0.9.0" }
|
||||
quick-junit = { version = "0.5.0" }
|
||||
quote = { version = "1.0.23" }
|
||||
rand = { version = "0.8.5" }
|
||||
@@ -151,7 +151,7 @@ tracing-tree = { version = "0.4.0" }
|
||||
typed-arena = { version = "2.0.2" }
|
||||
unic-ucd-category = { version = "0.9" }
|
||||
unicode-ident = { version = "1.0.12" }
|
||||
unicode-width = { version = "0.2.0" }
|
||||
unicode-width = { version = "0.1.11" }
|
||||
unicode_names2 = { version = "1.2.2" }
|
||||
unicode-normalization = { version = "0.1.23" }
|
||||
ureq = { version = "2.9.6" }
|
||||
@@ -248,10 +248,10 @@ debug = 1
|
||||
[profile.dist]
|
||||
inherits = "release"
|
||||
|
||||
# Config for 'dist'
|
||||
# Config for 'cargo dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.25.2-prerelease.3"
|
||||
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.22.1"
|
||||
# CI backends to support
|
||||
ci = "github"
|
||||
# The installers to generate for each app
|
||||
@@ -282,13 +282,13 @@ targets = [
|
||||
]
|
||||
# Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true)
|
||||
auto-includes = false
|
||||
# Whether dist should create a Github Release or use an existing draft
|
||||
# Whether cargo-dist should create a GitHub Release or use an existing draft
|
||||
create-release = true
|
||||
# Which actions to run on pull requests
|
||||
pr-run-mode = "skip"
|
||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
||||
dispatch-releases = true
|
||||
# Which phase dist should use to create the GitHub release
|
||||
# Which phase cargo-dist should use to create the GitHub release
|
||||
github-release = "announce"
|
||||
# Whether CI should include auto-generated code to build local artifacts
|
||||
build-local-artifacts = false
|
||||
@@ -297,10 +297,14 @@ local-artifacts-jobs = ["./build-binaries", "./build-docker"]
|
||||
# Publish jobs to run in CI
|
||||
publish-jobs = ["./publish-pypi", "./publish-wasm"]
|
||||
# Post-announce jobs to run in CI
|
||||
post-announce-jobs = ["./notify-dependents", "./publish-docs", "./publish-playground"]
|
||||
post-announce-jobs = [
|
||||
"./notify-dependents",
|
||||
"./publish-docs",
|
||||
"./publish-playground",
|
||||
]
|
||||
# Custom permissions for GitHub Jobs
|
||||
github-custom-job-permissions = { "build-docker" = { packages = "write", contents = "read" }, "publish-wasm" = { contents = "read", id-token = "write", packages = "write" } }
|
||||
# Whether to install an updater program
|
||||
install-updater = false
|
||||
# Path that installers should place binaries in
|
||||
install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
||||
install-path = "CARGO_HOME"
|
||||
|
||||
10
README.md
10
README.md
@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.8.1/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.8.1/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.7.4/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.4/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.8.1
|
||||
rev: v0.7.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
@@ -238,8 +238,8 @@ exclude = [
|
||||
line-length = 88
|
||||
indent-width = 4
|
||||
|
||||
# Assume Python 3.9
|
||||
target-version = "py39"
|
||||
# Assume Python 3.8
|
||||
target-version = "py38"
|
||||
|
||||
[lint]
|
||||
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||
|
||||
36
clippy.toml
36
clippy.toml
@@ -1,25 +1,21 @@
|
||||
doc-valid-idents = [
|
||||
"..",
|
||||
"CodeQL",
|
||||
"FastAPI",
|
||||
"IPython",
|
||||
"LangChain",
|
||||
"LibCST",
|
||||
"McCabe",
|
||||
"NumPy",
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"SQLAlchemy",
|
||||
"StackOverflow",
|
||||
"PyCharm",
|
||||
"SNMPv1",
|
||||
"SNMPv2",
|
||||
"SNMPv3",
|
||||
"PyFlakes"
|
||||
"..",
|
||||
"CodeQL",
|
||||
"FastAPI",
|
||||
"IPython",
|
||||
"LangChain",
|
||||
"LibCST",
|
||||
"McCabe",
|
||||
"NumPy",
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"SQLAlchemy",
|
||||
"StackOverflow",
|
||||
"PyCharm",
|
||||
]
|
||||
|
||||
ignore-interior-mutability = [
|
||||
# Interned is read-only. The wrapped `Rc` never gets updated.
|
||||
"ruff_formatter::format_element::Interned",
|
||||
# The expression is read-only.
|
||||
"ruff_python_ast::hashable::HashableExpr",
|
||||
# Interned is read-only. The wrapped `Rc` never gets updated.
|
||||
"ruff_formatter::format_element::Interned",
|
||||
# The expression is read-only.
|
||||
"ruff_python_ast::hashable::HashableExpr",
|
||||
]
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
|
||||
use red_knot_python_semantic::{resolve_module, ModuleName, Program, PythonVersion, SitePackages};
|
||||
use red_knot_workspace::db::{Db, RootDatabase};
|
||||
use red_knot_workspace::watch;
|
||||
@@ -13,7 +14,7 @@ use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::files::{system_path_to_file, File, FileError};
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::{setup_logging, setup_logging_with_filter};
|
||||
use ruff_db::testing::setup_logging;
|
||||
use ruff_db::Upcast;
|
||||
|
||||
struct TestCase {
|
||||
@@ -46,8 +47,6 @@ impl TestCase {
|
||||
}
|
||||
|
||||
fn try_stop_watch(&mut self, timeout: Duration) -> Option<Vec<watch::ChangeEvent>> {
|
||||
tracing::debug!("Try stopping watch with timeout {:?}", timeout);
|
||||
|
||||
let watcher = self
|
||||
.watcher
|
||||
.take()
|
||||
@@ -57,11 +56,8 @@ impl TestCase {
|
||||
.changes_receiver
|
||||
.recv_timeout(timeout)
|
||||
.unwrap_or_default();
|
||||
|
||||
watcher.flush();
|
||||
tracing::debug!("Flushed file watcher");
|
||||
watcher.stop();
|
||||
tracing::debug!("Stopping file watcher");
|
||||
|
||||
for event in &self.changes_receiver {
|
||||
all_events.extend(event);
|
||||
@@ -604,8 +600,6 @@ fn directory_moved_to_trash() -> anyhow::Result<()> {
|
||||
|
||||
#[test]
|
||||
fn directory_renamed() -> anyhow::Result<()> {
|
||||
let _tracing = setup_logging_with_filter("file_watching=TRACE,red_knot=TRACE");
|
||||
|
||||
let mut case = setup([
|
||||
("bar.py", "import sub.a"),
|
||||
("sub/__init__.py", ""),
|
||||
@@ -646,10 +640,6 @@ fn directory_renamed() -> anyhow::Result<()> {
|
||||
|
||||
let changes = case.stop_watch();
|
||||
|
||||
for event in &changes {
|
||||
tracing::debug!("Event: {:?}", event);
|
||||
}
|
||||
|
||||
case.apply_changes(changes);
|
||||
|
||||
// `import sub.a` should no longer resolve
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# NoReturn & Never
|
||||
|
||||
`NoReturn` is used to annotate the return type for functions that never return. `Never` is the
|
||||
bottom type, representing the empty set of Python objects. These two annotations can be used
|
||||
interchangeably.
|
||||
|
||||
## Function Return Type Annotation
|
||||
|
||||
```py
|
||||
from typing import NoReturn
|
||||
|
||||
def stop() -> NoReturn:
|
||||
raise RuntimeError("no way")
|
||||
|
||||
# revealed: Never
|
||||
reveal_type(stop())
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
```py
|
||||
from typing import NoReturn, Never, Any
|
||||
|
||||
# error: [invalid-type-parameter] "Type `typing.Never` expected no type parameter"
|
||||
x: Never[int]
|
||||
a1: NoReturn
|
||||
# TODO: Test `Never` is only available in python >= 3.11
|
||||
a2: Never
|
||||
b1: Any
|
||||
b2: int
|
||||
|
||||
def f():
|
||||
# revealed: Never
|
||||
reveal_type(a1)
|
||||
# revealed: Never
|
||||
reveal_type(a2)
|
||||
|
||||
# Never is assignable to all types.
|
||||
v1: int = a1
|
||||
v2: str = a1
|
||||
# Other types are not assignable to Never except for Never (and Any).
|
||||
v3: Never = b1
|
||||
v4: Never = a2
|
||||
v5: Any = b2
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Never`"
|
||||
v6: Never = 1
|
||||
```
|
||||
|
||||
## Typing Extensions
|
||||
|
||||
```py
|
||||
from typing_extensions import NoReturn, Never
|
||||
|
||||
x: NoReturn
|
||||
y: Never
|
||||
|
||||
def f():
|
||||
# revealed: Never
|
||||
reveal_type(x)
|
||||
# revealed: Never
|
||||
reveal_type(y)
|
||||
```
|
||||
@@ -9,10 +9,10 @@ Ts = TypeVarTuple("Ts")
|
||||
|
||||
def append_int(*args: *Ts) -> tuple[*Ts, int]:
|
||||
# TODO: should show some representation of the variadic generic type
|
||||
reveal_type(args) # revealed: @Todo(function parameter type)
|
||||
reveal_type(args) # revealed: @Todo
|
||||
|
||||
return (*args, 1)
|
||||
|
||||
# TODO should be tuple[Literal[True], Literal["a"], int]
|
||||
reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(append_int(True, "a")) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -189,31 +189,3 @@ reveal_type(d) # revealed: Foo
|
||||
## Parameter
|
||||
|
||||
TODO: Add tests once parameter inference is supported
|
||||
|
||||
## Invalid expressions
|
||||
|
||||
The expressions in these string annotations aren't valid expressions in this context but we
|
||||
shouldn't panic.
|
||||
|
||||
```py
|
||||
a: "1 or 2"
|
||||
b: "(x := 1)"
|
||||
c: "1 + 2"
|
||||
d: "lambda x: x"
|
||||
e: "x if True else y"
|
||||
f: "{'a': 1, 'b': 2}"
|
||||
g: "{1, 2}"
|
||||
h: "[i for i in range(5)]"
|
||||
i: "{i for i in range(5)}"
|
||||
j: "{i: i for i in range(5)}"
|
||||
k: "(i for i in range(5))"
|
||||
l: "await 1"
|
||||
# error: [forward-annotation-syntax-error]
|
||||
m: "yield 1"
|
||||
# error: [forward-annotation-syntax-error]
|
||||
n: "yield from 1"
|
||||
o: "1 < 2"
|
||||
p: "call()"
|
||||
r: "[1, 2]"
|
||||
s: "(1, 2)"
|
||||
```
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Union
|
||||
|
||||
## Annotation
|
||||
|
||||
`typing.Union` can be used to construct union types same as `|` operator.
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
a: Union[int, str]
|
||||
a1: Union[int, bool]
|
||||
a2: Union[int, Union[float, str]]
|
||||
a3: Union[int, None]
|
||||
a4: Union[Union[float, str]]
|
||||
a5: Union[int]
|
||||
a6: Union[()]
|
||||
|
||||
def f():
|
||||
# revealed: int | str
|
||||
reveal_type(a)
|
||||
# Since bool is a subtype of int we simplify to int here. But we do allow assigning boolean values (see below).
|
||||
# revealed: int
|
||||
reveal_type(a1)
|
||||
# revealed: int | float | str
|
||||
reveal_type(a2)
|
||||
# revealed: int | None
|
||||
reveal_type(a3)
|
||||
# revealed: float | str
|
||||
reveal_type(a4)
|
||||
# revealed: int
|
||||
reveal_type(a5)
|
||||
# revealed: Never
|
||||
reveal_type(a6)
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
a: Union[int, str]
|
||||
a = 1
|
||||
a = ""
|
||||
a1: Union[int, bool]
|
||||
a1 = 1
|
||||
a1 = True
|
||||
# error: [invalid-assignment] "Object of type `Literal[b""]` is not assignable to `int | str`"
|
||||
a = b""
|
||||
```
|
||||
|
||||
## Typing Extensions
|
||||
|
||||
```py
|
||||
from typing_extensions import Union
|
||||
|
||||
a: Union[int, str]
|
||||
|
||||
def f():
|
||||
# revealed: int | str
|
||||
reveal_type(a)
|
||||
```
|
||||
@@ -51,12 +51,12 @@ reveal_type(c) # revealed: tuple[str, int]
|
||||
reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||
|
||||
# TODO: homogenous tuples, PEP-646 tuples
|
||||
reveal_type(e) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(f) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(e) # revealed: @Todo
|
||||
reveal_type(f) # revealed: @Todo
|
||||
reveal_type(g) # revealed: @Todo
|
||||
|
||||
# TODO: support more kinds of type expressions in annotations
|
||||
reveal_type(h) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(h) # revealed: @Todo
|
||||
|
||||
reveal_type(i) # revealed: tuple[str | int, str | int]
|
||||
reveal_type(j) # revealed: tuple[str | int]
|
||||
|
||||
@@ -317,7 +317,7 @@ reveal_type(1 + A()) # revealed: int
|
||||
reveal_type(A() + "foo") # revealed: A
|
||||
# TODO should be `A` since `str.__add__` doesn't support `A` instances
|
||||
# TODO overloads
|
||||
reveal_type("foo" + A()) # revealed: @Todo(return type)
|
||||
reveal_type("foo" + A()) # revealed: @Todo
|
||||
|
||||
reveal_type(A() + b"foo") # revealed: A
|
||||
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
|
||||
@@ -325,7 +325,7 @@ reveal_type(b"foo" + A()) # revealed: bytes
|
||||
|
||||
reveal_type(A() + ()) # revealed: A
|
||||
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
|
||||
reveal_type(() + A()) # revealed: @Todo(return type)
|
||||
reveal_type(() + A()) # revealed: @Todo
|
||||
|
||||
literal_string_instance = "foo" * 1_000_000_000
|
||||
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:
|
||||
@@ -334,7 +334,7 @@ reveal_type(literal_string_instance) # revealed: LiteralString
|
||||
reveal_type(A() + literal_string_instance) # revealed: A
|
||||
# TODO should be `A` since `str.__add__` doesn't support `A` instances
|
||||
# TODO overloads
|
||||
reveal_type(literal_string_instance + A()) # revealed: @Todo(return type)
|
||||
reveal_type(literal_string_instance + A()) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Operations involving instances of classes inheriting from `Any`
|
||||
|
||||
@@ -38,7 +38,7 @@ if (x := 1) and bool_instance():
|
||||
if True or (x := 1):
|
||||
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Never
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if True and (x := 1):
|
||||
# TODO: infer that the second arm is always executed, do not raise a diagnostic
|
||||
|
||||
@@ -16,7 +16,7 @@ async def get_int_async() -> int:
|
||||
return 42
|
||||
|
||||
# TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]`
|
||||
reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
|
||||
reveal_type(get_int_async()) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Generic
|
||||
@@ -44,7 +44,7 @@ def bar() -> str:
|
||||
return "bar"
|
||||
|
||||
# TODO: should reveal `int`, as the decorator replaces `bar` with `foo`
|
||||
reveal_type(bar()) # revealed: @Todo(return type)
|
||||
reveal_type(bar()) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Invalid callable
|
||||
|
||||
@@ -58,9 +58,7 @@ reveal_type(c >= d) # revealed: Literal[True]
|
||||
#### Results with Ambiguity
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
def bool_instance() -> bool: ...
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
@@ -136,158 +134,23 @@ reveal_type(c >= c) # revealed: Literal[True]
|
||||
|
||||
#### Non Boolean Rich Comparisons
|
||||
|
||||
Rich comparison methods defined in a class affect tuple comparisons as well. Proper type inference
|
||||
should be possible even in cases where these methods return non-boolean types.
|
||||
|
||||
Note: Tuples use lexicographic comparisons. If the `==` result for all paired elements in the tuple
|
||||
is True, the comparison then considers the tuple’s length. Regardless of the return type of the
|
||||
dunder methods, the final result can still be a boolean value.
|
||||
|
||||
(+cpython: For tuples, `==` and `!=` always produce boolean results, regardless of the return type
|
||||
of the dunder methods.)
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A:
|
||||
def __eq__(self, o: object) -> str:
|
||||
return "hello"
|
||||
|
||||
def __ne__(self, o: object) -> bytes:
|
||||
return b"world"
|
||||
|
||||
def __lt__(self, o: A) -> float:
|
||||
return 3.14
|
||||
|
||||
def __le__(self, o: A) -> complex:
|
||||
return complex(0.5, -0.5)
|
||||
|
||||
def __gt__(self, o: A) -> tuple:
|
||||
return (1, 2, 3)
|
||||
|
||||
def __ge__(self, o: A) -> list:
|
||||
return [1, 2, 3]
|
||||
def __eq__(self, o) -> str: ...
|
||||
def __ne__(self, o) -> int: ...
|
||||
def __lt__(self, o) -> float: ...
|
||||
def __le__(self, o) -> object: ...
|
||||
def __gt__(self, o) -> tuple: ...
|
||||
def __ge__(self, o) -> list: ...
|
||||
|
||||
a = (A(), A())
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: float | Literal[False]
|
||||
reveal_type(a <= a) # revealed: complex | Literal[True]
|
||||
reveal_type(a > a) # revealed: tuple | Literal[False]
|
||||
reveal_type(a >= a) # revealed: list | Literal[True]
|
||||
|
||||
# If lexicographic comparison is finished before comparing A()
|
||||
b = ("1_foo", A())
|
||||
c = ("2_bar", A())
|
||||
|
||||
reveal_type(b == c) # revealed: Literal[False]
|
||||
reveal_type(b != c) # revealed: Literal[True]
|
||||
reveal_type(b < c) # revealed: Literal[True]
|
||||
reveal_type(b <= c) # revealed: Literal[True]
|
||||
reveal_type(b > c) # revealed: Literal[False]
|
||||
reveal_type(b >= c) # revealed: Literal[False]
|
||||
|
||||
class B:
|
||||
def __lt__(self, o: B) -> set:
|
||||
return set()
|
||||
|
||||
reveal_type((A(), B()) < (A(), B())) # revealed: float | set | Literal[False]
|
||||
```
|
||||
|
||||
#### Special Handling of Eq and NotEq in Lexicographic Comparisons
|
||||
|
||||
> Example: `(int_instance(), "foo") == (int_instance(), "bar")`
|
||||
|
||||
`Eq` and `NotEq` have unique behavior compared to other operators in lexicographic comparisons.
|
||||
Specifically, for `Eq`, if any non-equal pair exists within the tuples being compared, we can
|
||||
immediately conclude that the tuples are not equal. Conversely, for `NotEq`, if any non-equal pair
|
||||
exists, we can determine that the tuples are unequal.
|
||||
|
||||
In contrast, with operators like `<` and `>`, the comparison must consider each pair of elements
|
||||
sequentially, and the final outcome might remain ambiguous until all pairs are compared.
|
||||
|
||||
```py
|
||||
def str_instance() -> str:
|
||||
return "hello"
|
||||
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type("foo" == "bar") # revealed: Literal[False]
|
||||
reveal_type(("foo",) == ("bar",)) # revealed: Literal[False]
|
||||
reveal_type((4, "foo") == (4, "bar")) # revealed: Literal[False]
|
||||
reveal_type((int_instance(), "foo") == (int_instance(), "bar")) # revealed: Literal[False]
|
||||
|
||||
a = (str_instance(), int_instance(), "foo")
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
|
||||
b = (str_instance(), int_instance(), "bar")
|
||||
|
||||
reveal_type(a == b) # revealed: Literal[False]
|
||||
reveal_type(a != b) # revealed: Literal[True]
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
|
||||
c = (str_instance(), int_instance(), "foo", "different_length")
|
||||
reveal_type(a == c) # revealed: Literal[False]
|
||||
reveal_type(a != c) # revealed: Literal[True]
|
||||
reveal_type(a < c) # revealed: bool
|
||||
reveal_type(a <= c) # revealed: bool
|
||||
reveal_type(a > c) # revealed: bool
|
||||
reveal_type(a >= c) # revealed: bool
|
||||
```
|
||||
|
||||
#### Error Propagation
|
||||
|
||||
Errors occurring within a tuple comparison should propagate outward. However, if the tuple
|
||||
comparison can clearly conclude before encountering an error, the error should not be raised.
|
||||
|
||||
```py
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
def str_instance() -> str:
|
||||
return "hello"
|
||||
|
||||
class A: ...
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`"
|
||||
A() < A()
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`"
|
||||
A() <= A()
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`"
|
||||
A() > A()
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`"
|
||||
A() >= A()
|
||||
|
||||
a = (0, int_instance(), A())
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a < a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a <= a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a > a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a >= a) # revealed: Unknown
|
||||
|
||||
# Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`,
|
||||
# and should terminate immediately.
|
||||
b = (99999, int_instance(), A())
|
||||
|
||||
reveal_type(a < b) # revealed: Literal[True]
|
||||
reveal_type(a <= b) # revealed: Literal[True]
|
||||
reveal_type(a > b) # revealed: Literal[False]
|
||||
reveal_type(a >= b) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
### Membership Test Comparisons
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class A: ...
|
||||
|
||||
a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`"
|
||||
reveal_type(a) # revealed: bool
|
||||
|
||||
@@ -35,8 +33,4 @@ reveal_type(e) # revealed: bool
|
||||
f = (1, 2) < (1, "hello")
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(f) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`"
|
||||
g = (bool_instance(), A()) < (bool_instance(), A())
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -50,11 +50,11 @@ def foo(
|
||||
help()
|
||||
except x as e:
|
||||
# TODO: should be `AttributeError`
|
||||
reveal_type(e) # revealed: @Todo(exception type)
|
||||
reveal_type(e) # revealed: @Todo
|
||||
except y as f:
|
||||
# TODO: should be `OSError | RuntimeError`
|
||||
reveal_type(f) # revealed: @Todo(exception type)
|
||||
reveal_type(f) # revealed: @Todo
|
||||
except z as g:
|
||||
# TODO: should be `BaseException`
|
||||
reveal_type(g) # revealed: @Todo(exception type)
|
||||
reveal_type(g) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -22,22 +22,3 @@ reveal_type(1 if None else 2) # revealed: Literal[2]
|
||||
reveal_type(1 if "" else 2) # revealed: Literal[2]
|
||||
reveal_type(1 if 0 else 2) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Leaked Narrowing Constraint
|
||||
|
||||
(issue #14588)
|
||||
|
||||
The test inside an if expression should not affect code outside of the expression.
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x: Literal[42, "hello"] = 42 if bool_instance() else "hello"
|
||||
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
|
||||
_ = ... if isinstance(x, str) else ...
|
||||
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@ box: MyBox[int] = MyBox(5)
|
||||
wrong_innards: MyBox[int] = MyBox("five")
|
||||
|
||||
# TODO reveal int
|
||||
reveal_type(box.data) # revealed: @Todo(instance attributes)
|
||||
reveal_type(box.data) # revealed: @Todo
|
||||
|
||||
reveal_type(MyBox.box_model_number) # revealed: Literal[695]
|
||||
```
|
||||
@@ -39,7 +39,7 @@ class MySecureBox[T](MyBox[T]): ...
|
||||
secure_box: MySecureBox[int] = MySecureBox(5)
|
||||
reveal_type(secure_box) # revealed: MySecureBox
|
||||
# TODO reveal int
|
||||
reveal_type(secure_box.data) # revealed: @Todo(instance attributes)
|
||||
reveal_type(secure_box.data) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Cyclical class definition
|
||||
@@ -60,20 +60,52 @@ reveal_type(S) # revealed: Literal[S]
|
||||
|
||||
## Type params
|
||||
|
||||
A PEP695 type variable defines a value of type `typing.TypeVar`.
|
||||
A PEP695 type variable defines a value of type `typing.TypeVar` with attributes `__name__`,
|
||||
`__bounds__`, `__constraints__`, and `__default__` (the latter three all lazily evaluated):
|
||||
|
||||
```py
|
||||
def f[T]():
|
||||
def f[T, U: A, V: (A, B), W = A, X: A = A1]():
|
||||
reveal_type(T) # revealed: T
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(T.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(U) # revealed: U
|
||||
reveal_type(U.__name__) # revealed: Literal["U"]
|
||||
reveal_type(U.__bound__) # revealed: type[A]
|
||||
reveal_type(U.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(U.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(V) # revealed: V
|
||||
reveal_type(V.__name__) # revealed: Literal["V"]
|
||||
reveal_type(V.__bound__) # revealed: None
|
||||
reveal_type(V.__constraints__) # revealed: tuple[type[A], type[B]]
|
||||
reveal_type(V.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(W) # revealed: W
|
||||
reveal_type(W.__name__) # revealed: Literal["W"]
|
||||
reveal_type(W.__bound__) # revealed: None
|
||||
reveal_type(W.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(W.__default__) # revealed: type[A]
|
||||
|
||||
reveal_type(X) # revealed: X
|
||||
reveal_type(X.__name__) # revealed: Literal["X"]
|
||||
reveal_type(X.__bound__) # revealed: type[A]
|
||||
reveal_type(X.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(X.__default__) # revealed: type[A1]
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class A1(A): ...
|
||||
```
|
||||
|
||||
## Minimum two constraints
|
||||
|
||||
A typevar with less than two constraints emits a diagnostic:
|
||||
A typevar with less than two constraints emits a diagnostic and is treated as unconstrained:
|
||||
|
||||
```py
|
||||
# error: [invalid-typevar-constraints] "TypeVar must have at least two constrained types"
|
||||
def f[T: (int,)]():
|
||||
pass
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
@@ -78,7 +78,7 @@ from other import Literal
|
||||
a1: Literal[26]
|
||||
|
||||
def f():
|
||||
reveal_type(a1) # revealed: @Todo(generics)
|
||||
reveal_type(a1) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Detecting typing_extensions.Literal
|
||||
|
||||
@@ -18,7 +18,7 @@ async def foo():
|
||||
pass
|
||||
|
||||
# TODO: should reveal `Unknown` because `__aiter__` is not defined
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
# revealed: @Todo
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
@@ -40,6 +40,6 @@ async def foo():
|
||||
pass
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
# revealed: @Todo
|
||||
reveal_type(x)
|
||||
```
|
||||
|
||||
@@ -52,29 +52,3 @@ else:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
reveal_type(y) # revealed: Literal[1, 2, 4]
|
||||
```
|
||||
|
||||
## Nested while loops
|
||||
|
||||
```py
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
x = 1
|
||||
|
||||
while flag():
|
||||
x = 2
|
||||
|
||||
while flag():
|
||||
x = 3
|
||||
if flag():
|
||||
break
|
||||
else:
|
||||
x = 4
|
||||
|
||||
if flag():
|
||||
break
|
||||
else:
|
||||
x = 5
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4, 5]
|
||||
```
|
||||
|
||||
@@ -171,7 +171,7 @@ def f(*args, **kwargs) -> int: ...
|
||||
class A(metaclass=f): ...
|
||||
|
||||
# TODO should be `type[int]`
|
||||
reveal_type(A.__class__) # revealed: @Todo(metaclass not a class)
|
||||
reveal_type(A.__class__) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Cyclic
|
||||
|
||||
@@ -256,7 +256,7 @@ class O: ...
|
||||
class X(O): ...
|
||||
class Y(O): ...
|
||||
|
||||
if returns_bool():
|
||||
if bool():
|
||||
foo = Y
|
||||
else:
|
||||
foo = object
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Consolidating narrowed types after if statement
|
||||
|
||||
## After if-else statements, narrowing has no effect if the variable is not mutated in any branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Narrowing can have a persistent effect if the variable is mutated in one branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 10
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## An if statement without an explicit `else` branch is equivalent to one with a no-op `else` branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
y = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 0
|
||||
|
||||
if y is None:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(y) # revealed: int | None
|
||||
```
|
||||
|
||||
## An if-elif without an explicit else branch is equivalent to one with an empty else branch
|
||||
|
||||
```py
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 0
|
||||
elif x > 50:
|
||||
x = 50
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
@@ -17,7 +17,8 @@ reveal_type(__doc__) # revealed: str | None
|
||||
# (needs support for `*` imports)
|
||||
reveal_type(__spec__) # revealed: Unknown | None
|
||||
|
||||
reveal_type(__path__) # revealed: @Todo(generics)
|
||||
# TODO: generics
|
||||
reveal_type(__path__) # revealed: @Todo
|
||||
|
||||
class X:
|
||||
reveal_type(__name__) # revealed: str
|
||||
@@ -63,7 +64,7 @@ reveal_type(typing.__class__) # revealed: Literal[type]
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties and generics;
|
||||
# should be `dict[str, Any]`
|
||||
reveal_type(typing.__dict__) # revealed: @Todo(instance attributes)
|
||||
reveal_type(typing.__dict__) # revealed: @Todo
|
||||
```
|
||||
|
||||
Typeshed includes a fake `__getattr__` method in the stub for `types.ModuleType` to help out with
|
||||
@@ -95,8 +96,8 @@ from foo import __dict__ as foo_dict
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties, and generics;
|
||||
# should be `dict[str, Any]` for both of these:
|
||||
reveal_type(foo.__dict__) # revealed: @Todo(instance attributes)
|
||||
reveal_type(foo_dict) # revealed: @Todo(instance attributes)
|
||||
reveal_type(foo.__dict__) # revealed: @Todo
|
||||
reveal_type(foo_dict) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Conditionally global or `ModuleType` attribute
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
# Statically-known branches
|
||||
|
||||
## Always false
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Always true
|
||||
|
||||
### If
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
### Else
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Combination
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py path=nested_if_true_if_true.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_false.py
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_if_true_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if True:
|
||||
if flag():
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
```
|
||||
|
||||
```py path=nested_if_bool_if_true.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if flag():
|
||||
if True:
|
||||
x = 2
|
||||
else:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_true.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if True:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_false.py
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if False:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[4]
|
||||
```
|
||||
|
||||
```py path=nested_else_if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1
|
||||
|
||||
if False:
|
||||
x = 2
|
||||
else:
|
||||
if flag():
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4]
|
||||
```
|
||||
|
||||
## If-expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
x = 1 if True else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
x = 1 if False else 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Boolean expressions
|
||||
|
||||
### Always true
|
||||
|
||||
```py
|
||||
(x := 1) == 1 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
### Always false
|
||||
|
||||
```py
|
||||
(x := 1) == 0 or (x := 2)
|
||||
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Conditional declarations
|
||||
|
||||
```py path=if_false.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true_else.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
```py path=if_true.py
|
||||
x: str
|
||||
|
||||
if True:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_false_else.py
|
||||
x: str
|
||||
|
||||
if False:
|
||||
pass
|
||||
else:
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
```py path=if_bool.py
|
||||
def flag() -> bool: ...
|
||||
|
||||
x: str
|
||||
|
||||
if flag():
|
||||
x: int
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
## Conditionally defined functions
|
||||
|
||||
```py
|
||||
def f() -> int: ...
|
||||
def g() -> int: ...
|
||||
|
||||
if True:
|
||||
def f() -> str: ...
|
||||
|
||||
else:
|
||||
def g() -> str: ...
|
||||
|
||||
reveal_type(f()) # revealed: str
|
||||
reveal_type(g()) # revealed: int
|
||||
```
|
||||
|
||||
## Conditionally defined class attributes
|
||||
|
||||
```py
|
||||
class C:
|
||||
if True:
|
||||
x: int = 1
|
||||
else:
|
||||
x: str = "a"
|
||||
|
||||
reveal_type(C.x) # revealed: int
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- declarations vs bindings => NoDefault: NoDefaultType
|
||||
- conditional imports
|
||||
- conditional class definitions
|
||||
- compare with tests in if.md=>Statically known branches
|
||||
- boundness
|
||||
- TODO in `issubclass.md`
|
||||
@@ -27,7 +27,7 @@ def int_instance() -> int:
|
||||
|
||||
a = b"abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
reveal_type(a) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Slices
|
||||
@@ -47,11 +47,11 @@ def int_instance() -> int: ...
|
||||
|
||||
byte_slice1 = b[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice1) # revealed: @Todo(return type)
|
||||
reveal_type(byte_slice1) # revealed: @Todo
|
||||
|
||||
def bytes_instance() -> bytes: ...
|
||||
|
||||
byte_slice2 = bytes_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice2) # revealed: @Todo(return type)
|
||||
reveal_type(byte_slice2) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -21,11 +21,10 @@ reveal_type(Identity[0]) # revealed: str
|
||||
## Class getitem union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
flag = True
|
||||
|
||||
class UnionClassGetItem:
|
||||
if bool_instance():
|
||||
if flag:
|
||||
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
@@ -60,10 +59,9 @@ reveal_type(x[0]) # revealed: str | int
|
||||
## Class getitem with unbound method union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
flag = True
|
||||
|
||||
if bool_instance():
|
||||
if flag:
|
||||
class Spam:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
@@ -79,10 +77,9 @@ reveal_type(Spam[42])
|
||||
## TODO: Class getitem non-class union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
flag = True
|
||||
|
||||
if bool_instance():
|
||||
if flag:
|
||||
class Eggs:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
|
||||
@@ -30,11 +30,10 @@ reveal_type(Identity()[0]) # revealed: int
|
||||
## Getitem union
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
flag = True
|
||||
|
||||
class Identity:
|
||||
if bool_instance():
|
||||
if flag:
|
||||
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return index
|
||||
|
||||
@@ -12,13 +12,13 @@ x = [1, 2, 3]
|
||||
reveal_type(x) # revealed: list
|
||||
|
||||
# TODO reveal int
|
||||
reveal_type(x[0]) # revealed: @Todo(return type)
|
||||
reveal_type(x[0]) # revealed: @Todo
|
||||
|
||||
# TODO reveal list
|
||||
reveal_type(x[0:1]) # revealed: @Todo(return type)
|
||||
reveal_type(x[0:1]) # revealed: @Todo
|
||||
|
||||
# TODO error
|
||||
reveal_type(x["a"]) # revealed: @Todo(return type)
|
||||
reveal_type(x["a"]) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Assignments within list assignment
|
||||
|
||||
@@ -23,7 +23,7 @@ def int_instance() -> int: ...
|
||||
|
||||
a = "abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
reveal_type(a) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Slices
|
||||
@@ -78,13 +78,13 @@ def int_instance() -> int: ...
|
||||
|
||||
substring1 = s[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `LiteralString`
|
||||
reveal_type(substring1) # revealed: @Todo(return type)
|
||||
reveal_type(substring1) # revealed: @Todo
|
||||
|
||||
def str_instance() -> str: ...
|
||||
|
||||
substring2 = str_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(substring2) # revealed: @Todo(return type)
|
||||
reveal_type(substring2) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
@@ -71,5 +71,5 @@ def int_instance() -> int: ...
|
||||
|
||||
tuple_slice = t[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||
reveal_type(tuple_slice) # revealed: @Todo(return type)
|
||||
reveal_type(tuple_slice) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -49,16 +49,17 @@ sometimes not:
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info >= (3, 9, 1)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0)) # revealed: bool
|
||||
|
||||
# TODO: While this won't fail at runtime, the user has probably made a mistake
|
||||
# if they're comparing a tuple of length >5 with `sys.version_info`
|
||||
# (`sys.version_info` is a tuple of length 5). It might be worth
|
||||
# emitting a lint diagnostic of some kind warning them about the probable error?
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info >= (3, 9, 1, "final", 0, 5)) # revealed: bool
|
||||
|
||||
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: Literal[False]
|
||||
# TODO: this should be `Literal[False]`; see #14279
|
||||
reveal_type(sys.version_info == (3, 9, 1, "finallllll", 0)) # revealed: bool
|
||||
```
|
||||
|
||||
## Imports and aliases
|
||||
@@ -102,8 +103,8 @@ The fields of `sys.version_info` can be accessed by name:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 13) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 14) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 10) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
|
||||
@@ -112,9 +113,9 @@ properties on instance types:
|
||||
```py path=b.py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.micro) # revealed: @Todo(instance attributes)
|
||||
reveal_type(sys.version_info.releaselevel) # revealed: @Todo(instance attributes)
|
||||
reveal_type(sys.version_info.serial) # revealed: @Todo(instance attributes)
|
||||
reveal_type(sys.version_info.micro) # revealed: @Todo
|
||||
reveal_type(sys.version_info.releaselevel) # revealed: @Todo
|
||||
reveal_type(sys.version_info.serial) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Accessing fields by index/slice
|
||||
@@ -125,14 +126,14 @@ The fields of `sys.version_info` can be accessed by index or by slice:
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 13) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 9) # revealed: Literal[False]
|
||||
|
||||
# revealed: tuple[Literal[3], Literal[13], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
# revealed: tuple[Literal[3], Literal[9], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
reveal_type(sys.version_info[:5])
|
||||
|
||||
reveal_type(sys.version_info[:2] >= (3, 13)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 14)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 14, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:2] >= (3, 9)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 10)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 10, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[3] == "final") # revealed: bool
|
||||
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
|
||||
```
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# Type aliases
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
reveal_type(IntOrStr) # revealed: typing.TypeAliasType
|
||||
reveal_type(IntOrStr.__name__) # revealed: Literal["IntOrStr"]
|
||||
|
||||
x: IntOrStr = 1
|
||||
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int | str
|
||||
```
|
||||
|
||||
## `__value__` attribute
|
||||
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
|
||||
# TODO: This should either fall back to the specified type from typeshed,
|
||||
# which is `Any`, or be the actual type of the runtime value expression
|
||||
# `int | str`, i.e. `types.UnionType`.
|
||||
reveal_type(IntOrStr.__value__) # revealed: @Todo(instance attributes)
|
||||
```
|
||||
|
||||
## Invalid assignment
|
||||
|
||||
```py
|
||||
type OptionalInt = int | None
|
||||
|
||||
# error: [invalid-assignment]
|
||||
x: OptionalInt = "1"
|
||||
```
|
||||
|
||||
## Type aliases in type aliases
|
||||
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
type IntOrStrOrBytes = IntOrStr | bytes
|
||||
|
||||
x: IntOrStrOrBytes = 1
|
||||
|
||||
def f() -> None:
|
||||
reveal_type(x) # revealed: int | str | bytes
|
||||
```
|
||||
|
||||
## Aliased type aliases
|
||||
|
||||
```py
|
||||
type IntOrStr = int | str
|
||||
MyIntOrStr = IntOrStr
|
||||
|
||||
x: MyIntOrStr = 1
|
||||
|
||||
# error: [invalid-assignment]
|
||||
y: MyIntOrStr = None
|
||||
```
|
||||
|
||||
## Generic type aliases
|
||||
|
||||
```py
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
|
||||
# TODO: Should be `tuple[typing.TypeVar | typing.ParamSpec | typing.TypeVarTuple, ...]`,
|
||||
# as specified in the `typeshed` stubs.
|
||||
reveal_type(ListOrSet.__type_params__) # revealed: @Todo(instance attributes)
|
||||
```
|
||||
@@ -84,7 +84,7 @@ reveal_type(b) # revealed: Literal[2]
|
||||
[a, *b, c, d] = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: Literal[2]
|
||||
reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
@@ -95,7 +95,7 @@ reveal_type(d) # revealed: Unknown
|
||||
[a, *b, c] = (1, 2)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
@@ -105,7 +105,7 @@ reveal_type(c) # revealed: Literal[2]
|
||||
[a, *b, c] = (1, 2, 3)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
# TODO: Should be list[int] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
@@ -115,7 +115,7 @@ reveal_type(c) # revealed: Literal[3]
|
||||
[a, *b, c, d] = (1, 2, 3, 4, 5, 6)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
# TODO: Should be list[int] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: Literal[5]
|
||||
reveal_type(d) # revealed: Literal[6]
|
||||
```
|
||||
@@ -127,7 +127,7 @@ reveal_type(d) # revealed: Literal[6]
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[2]
|
||||
# TODO: Should be list[int] once support for assigning to starred expression is added
|
||||
reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(c) # revealed: @Todo
|
||||
```
|
||||
|
||||
### Starred expression (6)
|
||||
@@ -138,7 +138,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(d) # revealed: @Todo
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
```
|
||||
@@ -222,7 +222,7 @@ reveal_type(b) # revealed: LiteralString
|
||||
(a, *b, c, d) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
@@ -233,7 +233,7 @@ reveal_type(d) # revealed: Unknown
|
||||
(a, *b, c) = "ab"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
```
|
||||
|
||||
@@ -243,7 +243,7 @@ reveal_type(c) # revealed: LiteralString
|
||||
(a, *b, c) = "abc"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
```
|
||||
|
||||
@@ -253,7 +253,7 @@ reveal_type(c) # revealed: LiteralString
|
||||
(a, *b, c, d) = "abcdef"
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
reveal_type(b) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(b) # revealed: @Todo
|
||||
reveal_type(c) # revealed: LiteralString
|
||||
reveal_type(d) # revealed: LiteralString
|
||||
```
|
||||
@@ -265,5 +265,5 @@ reveal_type(d) # revealed: LiteralString
|
||||
reveal_type(a) # revealed: LiteralString
|
||||
reveal_type(b) # revealed: LiteralString
|
||||
# TODO: Should be list[int] once support for assigning to starred expression is added
|
||||
reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
reveal_type(c) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -17,5 +17,5 @@ class Manager:
|
||||
|
||||
async def test():
|
||||
async with Manager() as f:
|
||||
reveal_type(f) # revealed: @Todo(async with statement)
|
||||
reveal_type(f) # revealed: @Todo
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ impl PythonVersion {
|
||||
|
||||
impl Default for PythonVersion {
|
||||
fn default() -> Self {
|
||||
Self::PY313 // TODO: temporarily changed to 3.13 to activate all sys.version_info branches
|
||||
Self::PY39
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1229,32 +1229,4 @@ match 1:
|
||||
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn if_statement() {
|
||||
let TestCase { db, file } = test_case(
|
||||
"
|
||||
x = False
|
||||
|
||||
if True:
|
||||
x: bool
|
||||
",
|
||||
);
|
||||
|
||||
let index = semantic_index(&db, file);
|
||||
// let global_table = index.symbol_table(FileScopeId::global());
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
|
||||
// use_def
|
||||
|
||||
use_def.print(&db);
|
||||
|
||||
assert!(false);
|
||||
// let binding = use_def
|
||||
// .first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
// .expect("Expected with item definition for {name}");
|
||||
// assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
|
||||
SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{ActiveConstraintsSnapshot, FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
use crate::semantic_index::SemanticIndex;
|
||||
use crate::unpack::Unpack;
|
||||
use crate::Db;
|
||||
@@ -36,25 +36,12 @@ use super::definition::{
|
||||
|
||||
mod except_handlers;
|
||||
|
||||
/// Are we in a state where a `break` statement is allowed?
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum LoopState {
|
||||
InLoop,
|
||||
NotInLoop,
|
||||
}
|
||||
|
||||
impl LoopState {
|
||||
fn is_inside(self) -> bool {
|
||||
matches!(self, LoopState::InLoop)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct SemanticIndexBuilder<'db> {
|
||||
// Builder state
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
module: &'db ParsedModule,
|
||||
scope_stack: Vec<(FileScopeId, LoopState)>,
|
||||
scope_stack: Vec<FileScopeId>,
|
||||
/// The assignments we're currently visiting, with
|
||||
/// the most recent visit at the end of the Vec
|
||||
current_assignments: Vec<CurrentAssignment<'db>>,
|
||||
@@ -116,24 +103,9 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
*self
|
||||
.scope_stack
|
||||
.last()
|
||||
.map(|(scope, _)| scope)
|
||||
.expect("Always to have a root scope")
|
||||
}
|
||||
|
||||
fn loop_state(&self) -> LoopState {
|
||||
self.scope_stack
|
||||
.last()
|
||||
.expect("Always to have a root scope")
|
||||
.1
|
||||
}
|
||||
|
||||
fn set_inside_loop(&mut self, state: LoopState) {
|
||||
self.scope_stack
|
||||
.last_mut()
|
||||
.expect("Always to have a root scope")
|
||||
.1 = state;
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, node: NodeWithScopeRef) {
|
||||
let parent = self.current_scope();
|
||||
self.push_scope_with_parent(node, Some(parent));
|
||||
@@ -159,16 +131,15 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
|
||||
|
||||
self.scope_ids_by_scope.push(scope_id);
|
||||
let previous = self.scopes_by_node.insert(node.node_key(), file_scope_id);
|
||||
debug_assert_eq!(previous, None);
|
||||
self.scopes_by_node.insert(node.node_key(), file_scope_id);
|
||||
|
||||
debug_assert_eq!(ast_id_scope, file_scope_id);
|
||||
|
||||
self.scope_stack.push((file_scope_id, LoopState::NotInLoop));
|
||||
self.scope_stack.push(file_scope_id);
|
||||
}
|
||||
|
||||
fn pop_scope(&mut self) -> FileScopeId {
|
||||
let (id, _) = self.scope_stack.pop().expect("Root scope to be present");
|
||||
let id = self.scope_stack.pop().expect("Root scope to be present");
|
||||
let children_end = self.scopes.next_index();
|
||||
let scope = &mut self.scopes[id];
|
||||
scope.descendents = scope.descendents.start..children_end;
|
||||
@@ -200,20 +171,12 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_use_def_map().snapshot()
|
||||
}
|
||||
|
||||
fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
self.current_use_def_map().constraints_snapshot()
|
||||
}
|
||||
|
||||
fn flow_restore(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
fn flow_restore(&mut self, state: FlowSnapshot) {
|
||||
self.current_use_def_map_mut().restore(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn flow_merge(&mut self, state: FlowSnapshot, active_constraints: ActiveConstraintsSnapshot) {
|
||||
fn flow_merge(&mut self, state: FlowSnapshot) {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
self.current_use_def_map_mut()
|
||||
.restore_constraints(active_constraints);
|
||||
}
|
||||
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
@@ -626,27 +589,6 @@ where
|
||||
},
|
||||
);
|
||||
}
|
||||
ast::Stmt::TypeAlias(type_alias) => {
|
||||
let symbol = self.add_symbol(
|
||||
type_alias
|
||||
.name
|
||||
.as_name_expr()
|
||||
.map(|name| name.id.clone())
|
||||
.unwrap_or("<unknown>".into()),
|
||||
);
|
||||
self.add_definition(symbol, type_alias);
|
||||
self.visit_expr(&type_alias.name);
|
||||
|
||||
self.with_type_params(
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias),
|
||||
type_alias.type_params.as_ref(),
|
||||
|builder| {
|
||||
builder.push_scope(NodeWithScopeRef::TypeAlias(type_alias));
|
||||
builder.visit_expr(&type_alias.value);
|
||||
builder.pop_scope()
|
||||
},
|
||||
);
|
||||
}
|
||||
ast::Stmt::Import(node) => {
|
||||
for alias in &node.names {
|
||||
let symbol_name = if let Some(asname) = &alias.asname {
|
||||
@@ -773,44 +715,37 @@ where
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(&node.test);
|
||||
let mut constraints = vec![constraint];
|
||||
self.visit_body(&node.body);
|
||||
let mut post_clauses: Vec<FlowSnapshot> = vec![];
|
||||
let elif_else_clauses = node
|
||||
.elif_else_clauses
|
||||
.iter()
|
||||
.map(|clause| (clause.test.as_ref(), clause.body.as_slice()));
|
||||
let has_else = node
|
||||
.elif_else_clauses
|
||||
.last()
|
||||
.is_some_and(|clause| clause.test.is_none());
|
||||
let elif_else_clauses = elif_else_clauses.chain(if has_else {
|
||||
// if there's an `else` clause already, we don't need to add another
|
||||
None
|
||||
} else {
|
||||
// if there's no `else` branch, we should add a no-op `else` branch
|
||||
Some((None, Default::default()))
|
||||
});
|
||||
for (clause_test, clause_body) in elif_else_clauses {
|
||||
for clause in &node.elif_else_clauses {
|
||||
// snapshot after every block except the last; the last one will just become
|
||||
// the state that we merge the other snapshots into
|
||||
post_clauses.push(self.flow_snapshot());
|
||||
// we can only take an elif/else branch if none of the previous ones were
|
||||
// taken, so the block entry state is always `pre_if`
|
||||
self.flow_restore(pre_if.clone(), pre_if_constraints.clone());
|
||||
self.flow_restore(pre_if.clone());
|
||||
for constraint in &constraints {
|
||||
self.record_negated_constraint(*constraint);
|
||||
}
|
||||
if let Some(elif_test) = clause_test {
|
||||
if let Some(elif_test) = &clause.test {
|
||||
self.visit_expr(elif_test);
|
||||
constraints.push(self.record_expression_constraint(elif_test));
|
||||
}
|
||||
self.visit_body(clause_body);
|
||||
self.visit_body(&clause.body);
|
||||
}
|
||||
for post_clause_state in post_clauses {
|
||||
self.flow_merge(post_clause_state, pre_if_constraints.clone());
|
||||
self.flow_merge(post_clause_state);
|
||||
}
|
||||
let has_else = node
|
||||
.elif_else_clauses
|
||||
.last()
|
||||
.is_some_and(|clause| clause.test.is_none());
|
||||
if !has_else {
|
||||
// if there's no else clause, then it's possible we took none of the branches,
|
||||
// and the pre_if state can reach here
|
||||
self.flow_merge(pre_if);
|
||||
}
|
||||
}
|
||||
ast::Stmt::While(ast::StmtWhile {
|
||||
@@ -822,17 +757,13 @@ where
|
||||
self.visit_expr(test);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
|
||||
// Save aside any break states from an outer loop
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
|
||||
// TODO: definitions created inside the body should be fully visible
|
||||
// to other statements/expressions inside the body --Alex/Carl
|
||||
let outer_loop_state = self.loop_state();
|
||||
self.set_inside_loop(LoopState::InLoop);
|
||||
self.visit_body(body);
|
||||
self.set_inside_loop(outer_loop_state);
|
||||
|
||||
// Get the break states from the body of this loop, and restore the saved outer
|
||||
// ones.
|
||||
@@ -841,13 +772,13 @@ where
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.flow_merge(pre_loop);
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a while loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone()); // TODO?
|
||||
self.flow_merge(break_state);
|
||||
}
|
||||
}
|
||||
ast::Stmt::With(ast::StmtWith {
|
||||
@@ -871,9 +802,7 @@ where
|
||||
self.visit_body(body);
|
||||
}
|
||||
ast::Stmt::Break(_) => {
|
||||
if self.loop_state().is_inside() {
|
||||
self.loop_break_states.push(self.flow_snapshot());
|
||||
}
|
||||
self.loop_break_states.push(self.flow_snapshot());
|
||||
}
|
||||
|
||||
ast::Stmt::For(
|
||||
@@ -890,7 +819,6 @@ where
|
||||
self.visit_expr(iter);
|
||||
|
||||
let pre_loop = self.flow_snapshot();
|
||||
let pre_loop_constraints = self.constraints_snapshot();
|
||||
let saved_break_states = std::mem::take(&mut self.loop_break_states);
|
||||
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
@@ -901,23 +829,20 @@ where
|
||||
// TODO: Definitions created by loop variables
|
||||
// (and definitions created inside the body)
|
||||
// are fully visible to other statements/expressions inside the body --Alex/Carl
|
||||
let outer_loop_state = self.loop_state();
|
||||
self.set_inside_loop(LoopState::InLoop);
|
||||
self.visit_body(body);
|
||||
self.set_inside_loop(outer_loop_state);
|
||||
|
||||
let break_states =
|
||||
std::mem::replace(&mut self.loop_break_states, saved_break_states);
|
||||
|
||||
// We may execute the `else` clause without ever executing the body, so merge in
|
||||
// the pre-loop state before visiting `else`.
|
||||
self.flow_merge(pre_loop, pre_loop_constraints.clone());
|
||||
self.flow_merge(pre_loop);
|
||||
self.visit_body(orelse);
|
||||
|
||||
// Breaking out of a `for` loop bypasses the `else` clause, so merge in the break
|
||||
// states after visiting `else`.
|
||||
for break_state in break_states {
|
||||
self.flow_merge(break_state, pre_loop_constraints.clone());
|
||||
self.flow_merge(break_state);
|
||||
}
|
||||
}
|
||||
ast::Stmt::Match(ast::StmtMatch {
|
||||
@@ -929,7 +854,6 @@ where
|
||||
self.visit_expr(subject);
|
||||
|
||||
let after_subject = self.flow_snapshot();
|
||||
let after_subject_cs = self.constraints_snapshot();
|
||||
let Some((first, remaining)) = cases.split_first() else {
|
||||
return;
|
||||
};
|
||||
@@ -939,18 +863,18 @@ where
|
||||
let mut post_case_snapshots = vec![];
|
||||
for case in remaining {
|
||||
post_case_snapshots.push(self.flow_snapshot());
|
||||
self.flow_restore(after_subject.clone(), after_subject_cs.clone());
|
||||
self.flow_restore(after_subject.clone());
|
||||
self.add_pattern_constraint(subject, &case.pattern);
|
||||
self.visit_match_case(case);
|
||||
}
|
||||
for post_clause_state in post_case_snapshots {
|
||||
self.flow_merge(post_clause_state, after_subject_cs.clone());
|
||||
self.flow_merge(post_clause_state);
|
||||
}
|
||||
if !cases
|
||||
.last()
|
||||
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
|
||||
{
|
||||
self.flow_merge(after_subject, after_subject_cs.clone());
|
||||
self.flow_merge(after_subject);
|
||||
}
|
||||
}
|
||||
ast::Stmt::Try(ast::StmtTry {
|
||||
@@ -968,7 +892,6 @@ where
|
||||
// We will merge this state with all of the intermediate
|
||||
// states during the `try` block before visiting those suites.
|
||||
let pre_try_block_state = self.flow_snapshot();
|
||||
let pre_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
self.try_node_context_stack_manager.push_context();
|
||||
|
||||
@@ -989,17 +912,14 @@ where
|
||||
// as there necessarily must have been 0 `except` blocks executed
|
||||
// if we hit the `else` block.
|
||||
let post_try_block_state = self.flow_snapshot();
|
||||
let post_try_block_constraints = self.constraints_snapshot();
|
||||
|
||||
// Prepare for visiting the `except` block(s)
|
||||
self.flow_restore(pre_try_block_state, pre_try_block_constraints.clone());
|
||||
self.flow_restore(pre_try_block_state);
|
||||
for state in try_block_snapshots {
|
||||
self.flow_merge(state, pre_try_block_constraints.clone());
|
||||
// TODO?
|
||||
self.flow_merge(state);
|
||||
}
|
||||
|
||||
let pre_except_state = self.flow_snapshot();
|
||||
let pre_except_constraints = self.constraints_snapshot();
|
||||
let num_handlers = handlers.len();
|
||||
|
||||
for (i, except_handler) in handlers.iter().enumerate() {
|
||||
@@ -1038,22 +958,19 @@ where
|
||||
// as we'll immediately call `self.flow_restore()` to a different state
|
||||
// as soon as this loop over the handlers terminates.
|
||||
if i < (num_handlers - 1) {
|
||||
self.flow_restore(
|
||||
pre_except_state.clone(),
|
||||
pre_except_constraints.clone(),
|
||||
);
|
||||
self.flow_restore(pre_except_state.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// If we get to the `else` block, we know that 0 of the `except` blocks can have been executed,
|
||||
// and the entire `try` block must have been executed:
|
||||
self.flow_restore(post_try_block_state, post_try_block_constraints);
|
||||
self.flow_restore(post_try_block_state);
|
||||
}
|
||||
|
||||
self.visit_body(orelse);
|
||||
|
||||
for post_except_state in post_except_states {
|
||||
self.flow_merge(post_except_state, pre_try_block_constraints.clone());
|
||||
self.flow_merge(post_except_state);
|
||||
}
|
||||
|
||||
// TODO: there's lots of complexity here that isn't yet handled by our model.
|
||||
@@ -1210,17 +1127,19 @@ where
|
||||
ast::Expr::If(ast::ExprIf {
|
||||
body, test, orelse, ..
|
||||
}) => {
|
||||
// TODO detect statically known truthy or falsy test (via type inference, not naive
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
self.visit_expr(test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
let pre_if_constraints = self.constraints_snapshot();
|
||||
let constraint = self.record_expression_constraint(test);
|
||||
let pre_if = self.flow_snapshot();
|
||||
self.visit_expr(body);
|
||||
let post_body = self.flow_snapshot();
|
||||
self.flow_restore(pre_if, pre_if_constraints.clone());
|
||||
self.flow_restore(pre_if);
|
||||
|
||||
self.record_negated_constraint(constraint);
|
||||
self.visit_expr(orelse);
|
||||
self.flow_merge(post_body, pre_if_constraints);
|
||||
self.flow_merge(post_body);
|
||||
}
|
||||
ast::Expr::ListComp(
|
||||
list_comprehension @ ast::ExprListComp {
|
||||
@@ -1281,7 +1200,7 @@ where
|
||||
// AST inspection, so we can't simplify here, need to record test expression for
|
||||
// later checking)
|
||||
let mut snapshots = vec![];
|
||||
let pre_op_constraints = self.constraints_snapshot();
|
||||
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
self.visit_expr(value);
|
||||
// In the last value we don't need to take a snapshot nor add a constraint
|
||||
@@ -1296,7 +1215,7 @@ where
|
||||
}
|
||||
}
|
||||
for snapshot in snapshots {
|
||||
self.flow_merge(snapshot, pre_op_constraints.clone());
|
||||
self.flow_merge(snapshot);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -83,7 +83,6 @@ pub(crate) enum DefinitionNodeRef<'a> {
|
||||
For(ForStmtDefinitionNodeRef<'a>),
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
Class(&'a ast::StmtClassDef),
|
||||
TypeAlias(&'a ast::StmtTypeAlias),
|
||||
NamedExpression(&'a ast::ExprNamed),
|
||||
Assignment(AssignmentDefinitionNodeRef<'a>),
|
||||
AnnotatedAssignment(&'a ast::StmtAnnAssign),
|
||||
@@ -110,12 +109,6 @@ impl<'a> From<&'a ast::StmtClassDef> for DefinitionNodeRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtTypeAlias> for DefinitionNodeRef<'a> {
|
||||
fn from(node: &'a ast::StmtTypeAlias) -> Self {
|
||||
Self::TypeAlias(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprNamed> for DefinitionNodeRef<'a> {
|
||||
fn from(node: &'a ast::ExprNamed) -> Self {
|
||||
Self::NamedExpression(node)
|
||||
@@ -272,9 +265,6 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
DefinitionNodeRef::Class(class) => {
|
||||
DefinitionKind::Class(AstNodeRef::new(parsed, class))
|
||||
}
|
||||
DefinitionNodeRef::TypeAlias(type_alias) => {
|
||||
DefinitionKind::TypeAlias(AstNodeRef::new(parsed, type_alias))
|
||||
}
|
||||
DefinitionNodeRef::NamedExpression(named) => {
|
||||
DefinitionKind::NamedExpression(AstNodeRef::new(parsed, named))
|
||||
}
|
||||
@@ -368,7 +358,6 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
}
|
||||
Self::Function(node) => node.into(),
|
||||
Self::Class(node) => node.into(),
|
||||
Self::TypeAlias(node) => node.into(),
|
||||
Self::NamedExpression(node) => node.into(),
|
||||
Self::Assignment(AssignmentDefinitionNodeRef {
|
||||
value: _,
|
||||
@@ -445,7 +434,6 @@ pub enum DefinitionKind<'db> {
|
||||
ImportFrom(ImportFromDefinitionKind),
|
||||
Function(AstNodeRef<ast::StmtFunctionDef>),
|
||||
Class(AstNodeRef<ast::StmtClassDef>),
|
||||
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
|
||||
NamedExpression(AstNodeRef<ast::ExprNamed>),
|
||||
Assignment(AssignmentDefinitionKind<'db>),
|
||||
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
|
||||
@@ -468,7 +456,6 @@ impl DefinitionKind<'_> {
|
||||
// functions, classes, and imports always bind, and we consider them declarations
|
||||
DefinitionKind::Function(_)
|
||||
| DefinitionKind::Class(_)
|
||||
| DefinitionKind::TypeAlias(_)
|
||||
| DefinitionKind::Import(_)
|
||||
| DefinitionKind::ImportFrom(_)
|
||||
| DefinitionKind::TypeVar(_)
|
||||
@@ -695,12 +682,6 @@ impl From<&ast::StmtClassDef> for DefinitionNodeKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::StmtTypeAlias> for DefinitionNodeKey {
|
||||
fn from(node: &ast::StmtTypeAlias) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::ExprName> for DefinitionNodeKey {
|
||||
fn from(node: &ast::ExprName) -> Self {
|
||||
Self(NodeKey::from_node(node))
|
||||
|
||||
@@ -116,11 +116,14 @@ impl<'db> ScopeId<'db> {
|
||||
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
|
||||
// symbol table also uses the term "function-like" for these scopes.
|
||||
matches!(
|
||||
self.node(db).scope_kind(),
|
||||
ScopeKind::Annotation
|
||||
| ScopeKind::Function
|
||||
| ScopeKind::TypeAlias
|
||||
| ScopeKind::Comprehension
|
||||
self.node(db),
|
||||
NodeWithScopeKind::ClassTypeParameters(_)
|
||||
| NodeWithScopeKind::FunctionTypeParameters(_)
|
||||
| NodeWithScopeKind::Function(_)
|
||||
| NodeWithScopeKind::ListComprehension(_)
|
||||
| NodeWithScopeKind::SetComprehension(_)
|
||||
| NodeWithScopeKind::DictComprehension(_)
|
||||
| NodeWithScopeKind::GeneratorExpression(_)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -141,12 +144,6 @@ impl<'db> ScopeId<'db> {
|
||||
}
|
||||
NodeWithScopeKind::Function(function)
|
||||
| NodeWithScopeKind::FunctionTypeParameters(function) => function.name.as_str(),
|
||||
NodeWithScopeKind::TypeAlias(type_alias)
|
||||
| NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias
|
||||
.name
|
||||
.as_name_expr()
|
||||
.map(|name| name.id.as_str())
|
||||
.unwrap_or("<type alias>"),
|
||||
NodeWithScopeKind::Lambda(_) => "<lambda>",
|
||||
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
|
||||
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
|
||||
@@ -204,7 +201,6 @@ pub enum ScopeKind {
|
||||
Class,
|
||||
Function,
|
||||
Comprehension,
|
||||
TypeAlias,
|
||||
}
|
||||
|
||||
impl ScopeKind {
|
||||
@@ -330,8 +326,6 @@ pub(crate) enum NodeWithScopeRef<'a> {
|
||||
Lambda(&'a ast::ExprLambda),
|
||||
FunctionTypeParameters(&'a ast::StmtFunctionDef),
|
||||
ClassTypeParameters(&'a ast::StmtClassDef),
|
||||
TypeAlias(&'a ast::StmtTypeAlias),
|
||||
TypeAliasTypeParameters(&'a ast::StmtTypeAlias),
|
||||
ListComprehension(&'a ast::ExprListComp),
|
||||
SetComprehension(&'a ast::ExprSetComp),
|
||||
DictComprehension(&'a ast::ExprDictComp),
|
||||
@@ -353,12 +347,6 @@ impl NodeWithScopeRef<'_> {
|
||||
NodeWithScopeRef::Function(function) => {
|
||||
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias))
|
||||
}
|
||||
NodeWithScopeRef::Lambda(lambda) => {
|
||||
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
|
||||
}
|
||||
@@ -399,12 +387,6 @@ impl NodeWithScopeRef<'_> {
|
||||
NodeWithScopeRef::ClassTypeParameters(class) => {
|
||||
NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class))
|
||||
}
|
||||
NodeWithScopeRef::TypeAlias(type_alias) => {
|
||||
NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
|
||||
NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias))
|
||||
}
|
||||
NodeWithScopeRef::ListComprehension(comprehension) => {
|
||||
NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension))
|
||||
}
|
||||
@@ -429,8 +411,6 @@ pub enum NodeWithScopeKind {
|
||||
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
|
||||
Function(AstNodeRef<ast::StmtFunctionDef>),
|
||||
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
|
||||
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
|
||||
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
|
||||
Lambda(AstNodeRef<ast::ExprLambda>),
|
||||
ListComprehension(AstNodeRef<ast::ExprListComp>),
|
||||
SetComprehension(AstNodeRef<ast::ExprSetComp>),
|
||||
@@ -443,11 +423,9 @@ impl NodeWithScopeKind {
|
||||
match self {
|
||||
Self::Module => ScopeKind::Module,
|
||||
Self::Class(_) => ScopeKind::Class,
|
||||
Self::Function(_) | Self::Lambda(_) => ScopeKind::Function,
|
||||
Self::FunctionTypeParameters(_)
|
||||
| Self::ClassTypeParameters(_)
|
||||
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
|
||||
Self::TypeAlias(_) => ScopeKind::TypeAlias,
|
||||
Self::Function(_) => ScopeKind::Function,
|
||||
Self::Lambda(_) => ScopeKind::Function,
|
||||
Self::FunctionTypeParameters(_) | Self::ClassTypeParameters(_) => ScopeKind::Annotation,
|
||||
Self::ListComprehension(_)
|
||||
| Self::SetComprehension(_)
|
||||
| Self::DictComprehension(_)
|
||||
@@ -468,13 +446,6 @@ impl NodeWithScopeKind {
|
||||
_ => panic!("expected function"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
|
||||
match self {
|
||||
Self::TypeAlias(type_alias) => type_alias.node(),
|
||||
_ => panic!("expected type alias"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
@@ -484,8 +455,6 @@ pub(crate) enum NodeWithScopeKey {
|
||||
ClassTypeParameters(NodeKey),
|
||||
Function(NodeKey),
|
||||
FunctionTypeParameters(NodeKey),
|
||||
TypeAlias(NodeKey),
|
||||
TypeAliasTypeParameters(NodeKey),
|
||||
Lambda(NodeKey),
|
||||
ListComprehension(NodeKey),
|
||||
SetComprehension(NodeKey),
|
||||
|
||||
@@ -221,8 +221,6 @@
|
||||
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
|
||||
//! visits a `StmtIf` node.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use self::symbol_state::{
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
@@ -270,109 +268,6 @@ pub(crate) struct UseDefMap<'db> {
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn print(&self, db: &dyn crate::db::Db) {
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
|
||||
println!("all_definitions:");
|
||||
println!("================");
|
||||
|
||||
for (id, d) in self.all_definitions.iter_enumerated() {
|
||||
println!(
|
||||
"{:?}: {:?} {:?} {:?}",
|
||||
id,
|
||||
d.category(db),
|
||||
d.scope(db),
|
||||
d.symbol(db),
|
||||
);
|
||||
println!(" {:?}", d.kind(db));
|
||||
println!();
|
||||
}
|
||||
|
||||
println!("all_constraints:");
|
||||
println!("================");
|
||||
|
||||
for (id, c) in self.all_constraints.iter_enumerated() {
|
||||
println!("{:?}: {:?}", id, c.node);
|
||||
if let ConstraintNode::Expression(e) = c.node {
|
||||
println!(" {:?}", e.node_ref(db));
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("bindings_by_use:");
|
||||
println!("================");
|
||||
|
||||
for (id, bindings) in self.bindings_by_use.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
for binding in bindings.iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
let mut active_constraint_ids =
|
||||
binding.constraints_active_at_binding_ids.peekable();
|
||||
|
||||
println!(" * {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
if active_constraint_ids.peek().is_some() {
|
||||
println!(" Active constraints at binding:");
|
||||
for constraint_id in active_constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No active constraints at binding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
println!("public_symbols:");
|
||||
println!("================");
|
||||
|
||||
for (id, symbol) in self.public_symbols.iter_enumerated() {
|
||||
println!("{:?}:", id);
|
||||
println!(" * Bindings:");
|
||||
for binding in symbol.bindings().iter() {
|
||||
let definition = self.all_definitions[binding.definition];
|
||||
let mut constraint_ids = binding.constraint_ids.peekable();
|
||||
|
||||
println!(" {:?}", definition);
|
||||
|
||||
if constraint_ids.peek().is_some() {
|
||||
println!(" Constraints:");
|
||||
for constraint_id in constraint_ids {
|
||||
println!(" {:?}", self.all_constraints[constraint_id]);
|
||||
}
|
||||
} else {
|
||||
println!(" No constraints");
|
||||
}
|
||||
}
|
||||
|
||||
println!(" * Declarations:");
|
||||
for (declaration, _) in symbol.declarations().iter() {
|
||||
let definition = self.all_definitions[declaration];
|
||||
println!(" {:?}", definition);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
println!();
|
||||
println!();
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_use(
|
||||
&self,
|
||||
use_id: ScopedUseId,
|
||||
@@ -457,7 +352,6 @@ impl<'db> UseDefMap<'db> {
|
||||
) -> DeclarationsIterator<'a, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: declarations.iter(),
|
||||
may_be_undeclared: declarations.may_be_undeclared(),
|
||||
}
|
||||
@@ -471,7 +365,7 @@ enum SymbolDefinitions {
|
||||
Declarations(SymbolDeclarations),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
@@ -490,10 +384,6 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
},
|
||||
constraints_active_at_binding: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraints_active_at_binding_ids,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -503,10 +393,8 @@ impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Definition<'db>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
pub(crate) constraints_active_at_binding: ConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ConstraintsIterator<'map, 'db> {
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
constraint_ids: ConstraintIdIterator<'map>,
|
||||
@@ -526,7 +414,6 @@ impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -538,18 +425,10 @@ impl DeclarationsIterator<'_, '_> {
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = (Definition<'db>, ConstraintsIterator<'map, 'db>);
|
||||
type Item = Definition<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|(def_id, constraints)| {
|
||||
(
|
||||
self.all_definitions[def_id],
|
||||
ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: constraints,
|
||||
},
|
||||
)
|
||||
})
|
||||
self.inner.next().map(|def_id| self.all_definitions[def_id])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,9 +440,6 @@ pub(super) struct FlowSnapshot {
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct ActiveConstraintsSnapshot(HashSet<ScopedConstraintId>);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`].
|
||||
@@ -572,8 +448,6 @@ pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
active_constraints: HashSet<ScopedConstraintId>,
|
||||
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
@@ -597,7 +471,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
binding,
|
||||
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
|
||||
);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
symbol_state.record_binding(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
@@ -605,7 +479,6 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
self.active_constraints.insert(constraint_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
@@ -619,7 +492,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
declaration,
|
||||
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
|
||||
);
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
symbol_state.record_declaration(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
@@ -630,8 +503,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id, &self.active_constraints);
|
||||
symbol_state.record_binding(def_id, &self.active_constraints);
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
@@ -650,10 +523,6 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn constraints_snapshot(&self) -> ActiveConstraintsSnapshot {
|
||||
ActiveConstraintsSnapshot(self.active_constraints.clone())
|
||||
}
|
||||
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
@@ -672,10 +541,6 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
.resize(num_symbols, SymbolState::undefined());
|
||||
}
|
||||
|
||||
pub(super) fn restore_constraints(&mut self, snapshot: ActiveConstraintsSnapshot) {
|
||||
self.active_constraints = snapshot.0;
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
|
||||
@@ -122,7 +122,7 @@ impl<const B: usize> BitSet<B> {
|
||||
}
|
||||
|
||||
/// Iterator over values in a [`BitSet`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BitSetIterator<'a, const B: usize> {
|
||||
/// The blocks we are iterating over.
|
||||
blocks: &'a [u64],
|
||||
|
||||
@@ -43,8 +43,6 @@
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use smallvec::SmallVec;
|
||||
@@ -89,8 +87,6 @@ pub(super) struct SymbolDeclarations {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_declarations: Declarations,
|
||||
|
||||
constraints_active_at_declaration: Constraints, // TODO: rename to constraints_active_at_declaration
|
||||
|
||||
/// Could the symbol be un-declared at this point?
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
@@ -99,27 +95,14 @@ impl SymbolDeclarations {
|
||||
fn undeclared() -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::default(),
|
||||
constraints_active_at_declaration: Constraints::default(),
|
||||
may_be_undeclared: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
self.may_be_undeclared = false;
|
||||
|
||||
// TODO: unify code with below
|
||||
self.constraints_active_at_declaration = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_declaration
|
||||
.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_declaration[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
@@ -131,7 +114,6 @@ impl SymbolDeclarations {
|
||||
pub(super) fn iter(&self) -> DeclarationIdIterator {
|
||||
DeclarationIdIterator {
|
||||
inner: self.live_declarations.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_declaration.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,8 +138,6 @@ pub(super) struct SymbolBindings {
|
||||
/// binding in `live_bindings`.
|
||||
constraints: Constraints,
|
||||
|
||||
constraints_active_at_binding: Constraints,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
@@ -167,7 +147,6 @@ impl SymbolBindings {
|
||||
Self {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
@@ -178,21 +157,12 @@ impl SymbolBindings {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
// constraints.
|
||||
self.live_bindings = Bindings::with(binding_id.into());
|
||||
self.constraints = Constraints::with_capacity(1);
|
||||
self.constraints.push(BitSet::default());
|
||||
self.constraints_active_at_binding = Constraints::with_capacity(1);
|
||||
self.constraints_active_at_binding.push(BitSet::default());
|
||||
for active_constraint_id in active_constraints {
|
||||
self.constraints_active_at_binding[0].insert(active_constraint_id.as_u32());
|
||||
}
|
||||
self.may_be_unbound = false;
|
||||
}
|
||||
|
||||
@@ -208,7 +178,6 @@ impl SymbolBindings {
|
||||
BindingIdWithConstraintsIterator {
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
constraints_active_at_binding: self.constraints_active_at_binding.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,12 +207,8 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
binding_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.bindings.record_binding(binding_id, active_constraints);
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
self.bindings.record_binding(binding_id);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
@@ -257,13 +222,8 @@ impl SymbolState {
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
declaration_id: ScopedDefinitionId,
|
||||
active_constraints: &HashSet<ScopedConstraintId>,
|
||||
) {
|
||||
self.declarations
|
||||
.record_declaration(declaration_id, active_constraints);
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
@@ -272,93 +232,24 @@ impl SymbolState {
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
constraints_active_at_binding: Constraints::default(), // TODO
|
||||
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
|
||||
},
|
||||
declarations: SymbolDeclarations {
|
||||
live_declarations: self.declarations.live_declarations.clone(),
|
||||
constraints_active_at_declaration: Constraints::default(), // TODO
|
||||
may_be_undeclared: self.declarations.may_be_undeclared
|
||||
|| b.declarations.may_be_undeclared,
|
||||
},
|
||||
};
|
||||
|
||||
// let mut constraints_active_at_binding = BitSet::default();
|
||||
// for active_constraint_id in active_constraints.0 {
|
||||
// constraints_active_at_binding.insert(active_constraint_id.as_u32());
|
||||
// }
|
||||
|
||||
std::mem::swap(&mut a, self);
|
||||
// self.declarations
|
||||
// .live_declarations
|
||||
// .union(&b.declarations.live_declarations);
|
||||
|
||||
let mut a_decls_iter = a.declarations.live_declarations.iter();
|
||||
let mut b_decls_iter = b.declarations.live_declarations.iter();
|
||||
let mut a_constraints_active_at_declaration_iter =
|
||||
a.declarations.constraints_active_at_declaration.into_iter();
|
||||
let mut b_constraints_active_at_declaration_iter =
|
||||
b.declarations.constraints_active_at_declaration.into_iter();
|
||||
|
||||
let mut opt_a_decl: Option<u32> = a_decls_iter.next();
|
||||
let mut opt_b_decl: Option<u32> = b_decls_iter.next();
|
||||
|
||||
let push = |decl,
|
||||
constraints_active_at_declaration_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
merged.declarations.live_declarations.insert(decl);
|
||||
let constraints_active_at_binding = constraints_active_at_declaration_iter
|
||||
.next()
|
||||
.expect("declarations and constraints_active_at_binding length mismatch");
|
||||
merged
|
||||
.declarations
|
||||
.constraints_active_at_declaration
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
match (opt_a_decl, opt_b_decl) {
|
||||
(Some(a_decl), Some(b_decl)) => match a_decl.cmp(&b_decl) {
|
||||
std::cmp::Ordering::Less => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
push(a_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
self.declarations
|
||||
.constraints_active_at_declaration
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints_active_at_declaration_iter.next().unwrap());
|
||||
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_decl), None) => {
|
||||
push(a_decl, &mut a_constraints_active_at_declaration_iter, self);
|
||||
opt_a_decl = a_decls_iter.next();
|
||||
}
|
||||
(None, Some(b_decl)) => {
|
||||
push(b_decl, &mut b_constraints_active_at_declaration_iter, self);
|
||||
opt_b_decl = b_decls_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
}
|
||||
}
|
||||
self.declarations
|
||||
.live_declarations
|
||||
.union(&b.declarations.live_declarations);
|
||||
|
||||
let mut a_defs_iter = a.bindings.live_bindings.iter();
|
||||
let mut b_defs_iter = b.bindings.live_bindings.iter();
|
||||
let mut a_constraints_iter = a.bindings.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.bindings.constraints.into_iter();
|
||||
let mut a_constraints_active_at_binding_iter =
|
||||
a.bindings.constraints_active_at_binding.into_iter();
|
||||
let mut b_constraints_active_at_binding_iter =
|
||||
b.bindings.constraints_active_at_binding.into_iter();
|
||||
|
||||
let mut opt_a_def: Option<u32> = a_defs_iter.next();
|
||||
let mut opt_b_def: Option<u32> = b_defs_iter.next();
|
||||
@@ -370,10 +261,7 @@ impl SymbolState {
|
||||
// path is irrelevant.
|
||||
|
||||
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
|
||||
let push = |def,
|
||||
constraints_iter: &mut ConstraintsIntoIterator,
|
||||
constraints_active_at_binding_iter: &mut ConstraintsIntoIterator,
|
||||
merged: &mut Self| {
|
||||
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
|
||||
merged.bindings.live_bindings.insert(def);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
@@ -383,14 +271,7 @@ impl SymbolState {
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
let constraints_active_at_binding = constraints_active_at_binding_iter
|
||||
.next()
|
||||
.expect("definitions and constraints_active_at_binding length mismatch");
|
||||
merged.bindings.constraints.push(constraints);
|
||||
merged
|
||||
.bindings
|
||||
.constraints_active_at_binding
|
||||
.push(constraints_active_at_binding);
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -398,32 +279,17 @@ impl SymbolState {
|
||||
(Some(a_def), Some(b_def)) => match a_def.cmp(&b_def) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// Next definition ID is only in `a`, push it to `self` and advance `a`.
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// Next definition ID is only in `b`, push it to `self` and advance `b`.
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// Next definition is in both; push to `self` and intersect constraints.
|
||||
push(
|
||||
a_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
push(a_def, &mut b_constraints_iter, self);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and
|
||||
// no constraint bitsets (`::unbound`) or one definition and one constraint
|
||||
// bitset (`::with`), and `::merge` always pushes one definition and one
|
||||
@@ -432,11 +298,6 @@ impl SymbolState {
|
||||
let a_constraints = a_constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
// let _a_constraints_active_at_binding =
|
||||
// a_constraints_active_at_binding_iter.next().expect(
|
||||
// "definitions and constraints_active_at_binding length mismatch",
|
||||
// ); // TODO: perform check that we see the same constraints in both paths
|
||||
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
@@ -445,29 +306,18 @@ impl SymbolState {
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
|
||||
opt_a_def = a_defs_iter.next();
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
},
|
||||
(Some(a_def), None) => {
|
||||
// We've exhausted `b`, just push the def from `a` and move on to the next.
|
||||
push(
|
||||
a_def,
|
||||
&mut a_constraints_iter,
|
||||
&mut a_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
push(a_def, &mut a_constraints_iter, self);
|
||||
opt_a_def = a_defs_iter.next();
|
||||
}
|
||||
(None, Some(b_def)) => {
|
||||
// We've exhausted `a`, just push the def from `b` and move on to the next.
|
||||
push(
|
||||
b_def,
|
||||
&mut b_constraints_iter,
|
||||
&mut b_constraints_active_at_binding_iter,
|
||||
self,
|
||||
);
|
||||
push(b_def, &mut b_constraints_iter, self);
|
||||
opt_b_def = b_defs_iter.next();
|
||||
}
|
||||
(None, None) => break,
|
||||
@@ -503,37 +353,26 @@ impl Default for SymbolState {
|
||||
pub(super) struct BindingIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
pub(super) constraints_active_at_binding_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'a> {
|
||||
definitions: BindingsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
type Item = BindingIdWithConstraints<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (
|
||||
self.definitions.next(),
|
||||
self.constraints.next(),
|
||||
self.constraints_active_at_binding.next(),
|
||||
) {
|
||||
(None, None, None) => None,
|
||||
(Some(def), Some(constraints), Some(constraints_active_at_binding)) => {
|
||||
Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
constraints_active_at_binding_ids: ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
})
|
||||
}
|
||||
match (self.definitions.next(), self.constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(def), Some(constraints)) => Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
},
|
||||
}),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("definitions and constraints length mismatch"),
|
||||
}
|
||||
@@ -542,7 +381,7 @@ impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
wrapped: BitSetIterator<'a, INLINE_CONSTRAINT_BLOCKS>,
|
||||
}
|
||||
@@ -560,25 +399,13 @@ impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdIterator<'a> {
|
||||
inner: DeclarationsIterator<'a>,
|
||||
constraints_active_at_binding: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DeclarationIdIterator<'a> {
|
||||
type Item = (ScopedDefinitionId, ConstraintIdIterator<'a>);
|
||||
type Item = ScopedDefinitionId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
match (self.inner.next(), self.constraints_active_at_binding.next()) {
|
||||
(None, None) => None,
|
||||
(Some(declaration), Some(constraints_active_at_binding)) => Some((
|
||||
ScopedDefinitionId::from_u32(declaration),
|
||||
ConstraintIdIterator {
|
||||
wrapped: constraints_active_at_binding.iter(),
|
||||
},
|
||||
)),
|
||||
// SAFETY: see above.
|
||||
_ => unreachable!("declarations and constraints_active_at_binding length mismatch"),
|
||||
}
|
||||
self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -586,7 +413,7 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, SymbolState};
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
|
||||
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
|
||||
@@ -618,7 +445,7 @@ mod tests {
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(|(d, _)| d.as_u32()) // TODO: constraints
|
||||
.map(ScopedDefinitionId::as_u32)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
@@ -630,76 +457,76 @@ mod tests {
|
||||
assert_bindings(&sym, true, &[]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn with() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
#[test]
|
||||
fn with() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
|
||||
// assert_bindings(&sym, false, &["0<>"]);
|
||||
// }
|
||||
assert_bindings(&sym, false, &["0<>"]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn set_may_be_unbound() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_unbound();
|
||||
#[test]
|
||||
fn set_may_be_unbound() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_unbound();
|
||||
|
||||
// assert_bindings(&sym, true, &["0<>"]);
|
||||
// }
|
||||
assert_bindings(&sym, true, &["0<>"]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_constraint() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
#[test]
|
||||
fn record_constraint() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
// assert_bindings(&sym, false, &["0<0>"]);
|
||||
// }
|
||||
assert_bindings(&sym, false, &["0<0>"]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn merge() {
|
||||
// // merging the same definition with the same constraint keeps the constraint
|
||||
// let mut sym0a = SymbolState::undefined();
|
||||
// sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
#[test]
|
||||
fn merge() {
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut sym0a = SymbolState::undefined();
|
||||
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
// let mut sym0b = SymbolState::undefined();
|
||||
// sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
// sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
let mut sym0b = SymbolState::undefined();
|
||||
sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
// sym0a.merge(sym0b);
|
||||
// let mut sym0 = sym0a;
|
||||
// assert_bindings(&sym0, false, &["0<0>"]);
|
||||
sym0a.merge(sym0b);
|
||||
let mut sym0 = sym0a;
|
||||
assert_bindings(&sym0, false, &["0<0>"]);
|
||||
|
||||
// // merging the same definition with differing constraints drops all constraints
|
||||
// let mut sym1a = SymbolState::undefined();
|
||||
// sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut sym1a = SymbolState::undefined();
|
||||
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
// let mut sym1b = SymbolState::undefined();
|
||||
// sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
// sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
let mut sym1b = SymbolState::undefined();
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
// sym1a.merge(sym1b);
|
||||
// let sym1 = sym1a;
|
||||
// assert_bindings(&sym1, false, &["1<>"]);
|
||||
sym1a.merge(sym1b);
|
||||
let sym1 = sym1a;
|
||||
assert_bindings(&sym1, false, &["1<>"]);
|
||||
|
||||
// // merging a constrained definition with unbound keeps both
|
||||
// let mut sym2a = SymbolState::undefined();
|
||||
// sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
// sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut sym2a = SymbolState::undefined();
|
||||
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
// let sym2b = SymbolState::undefined();
|
||||
let sym2b = SymbolState::undefined();
|
||||
|
||||
// sym2a.merge(sym2b);
|
||||
// let sym2 = sym2a;
|
||||
// assert_bindings(&sym2, true, &["2<3>"]);
|
||||
sym2a.merge(sym2b);
|
||||
let sym2 = sym2a;
|
||||
assert_bindings(&sym2, true, &["2<3>"]);
|
||||
|
||||
// // merging different definitions keeps them each with their existing constraints
|
||||
// sym0.merge(sym2);
|
||||
// let sym = sym0;
|
||||
// assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
// }
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
sym0.merge(sym2);
|
||||
let sym = sym0;
|
||||
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
@@ -708,54 +535,54 @@ mod tests {
|
||||
assert_declarations(&sym, true, &[]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_declaration() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
// assert_declarations(&sym, false, &[1]);
|
||||
// }
|
||||
assert_declarations(&sym, false, &[1]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_declaration_override() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
// assert_declarations(&sym, false, &[2]);
|
||||
// }
|
||||
assert_declarations(&sym, false, &[2]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_declaration_merge() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
#[test]
|
||||
fn record_declaration_merge() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
// let mut sym2 = SymbolState::undefined();
|
||||
// sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
let mut sym2 = SymbolState::undefined();
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
// sym.merge(sym2);
|
||||
sym.merge(sym2);
|
||||
|
||||
// assert_declarations(&sym, false, &[1, 2]);
|
||||
// }
|
||||
assert_declarations(&sym, false, &[1, 2]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn record_declaration_merge_partial_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
#[test]
|
||||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
// let sym2 = SymbolState::undefined();
|
||||
let sym2 = SymbolState::undefined();
|
||||
|
||||
// sym.merge(sym2);
|
||||
sym.merge(sym2);
|
||||
|
||||
// assert_declarations(&sym, true, &[1]);
|
||||
// }
|
||||
assert_declarations(&sym, true, &[1]);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn set_may_be_undeclared() {
|
||||
// let mut sym = SymbolState::undefined();
|
||||
// sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
// sym.set_may_be_undeclared();
|
||||
#[test]
|
||||
fn set_may_be_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_undeclared();
|
||||
|
||||
// assert_declarations(&sym, true, &[0]);
|
||||
// }
|
||||
assert_declarations(&sym, true, &[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ pub(crate) use self::infer::{
|
||||
pub(crate) use self::signatures::Signature;
|
||||
use crate::module_resolver::file_to_module;
|
||||
use crate::semantic_index::ast_ids::HasScopedExpressionId;
|
||||
use crate::semantic_index::constraint::ConstraintNode;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::{self as symbol, ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
@@ -223,12 +222,6 @@ fn definition_expression_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum UnconditionallyVisible {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
|
||||
/// Infer the combined type of an iterator of bindings.
|
||||
///
|
||||
/// Will return a union if there is more than one binding.
|
||||
@@ -236,88 +229,29 @@ fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
let def_types = bindings_with_constraints.map(
|
||||
let mut def_types = bindings_with_constraints.map(
|
||||
|BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
constraints_active_at_binding,
|
||||
}| {
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_binding.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference
|
||||
.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
// TODO: do we need to call binding_ty(…) even if we don't need the result?
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build()
|
||||
} else {
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
let unconditionally_visible = if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
}) {
|
||||
UnconditionallyVisible::Yes
|
||||
} else {
|
||||
UnconditionallyVisible::No
|
||||
};
|
||||
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
|
||||
.peekable();
|
||||
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if constraint_tys.peek().is_some() {
|
||||
let intersection_ty = constraint_tys
|
||||
.fold(
|
||||
IntersectionBuilder::new(db).add_positive(binding_ty),
|
||||
IntersectionBuilder::add_positive,
|
||||
)
|
||||
.build();
|
||||
(intersection_ty, unconditionally_visible)
|
||||
} else {
|
||||
(binding_ty, unconditionally_visible)
|
||||
}
|
||||
binding_ty
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let def_types: Vec<_> = def_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let def_types: Vec<_> = def_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let mut def_types = def_types.into_iter().rev();
|
||||
|
||||
if let Some(first) = def_types.next() {
|
||||
if let Some(second) = def_types.next() {
|
||||
Some(UnionType::from_elements(
|
||||
@@ -353,63 +287,7 @@ fn declarations_ty<'db>(
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
undeclared_ty: Option<Type<'db>>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let decl_types = declarations.map(|(declaration, constraints_active_at_declaration)| {
|
||||
let test_expr_tys = || {
|
||||
constraints_active_at_declaration.clone().map(|c| {
|
||||
let ty = if let ConstraintNode::Expression(test_expr) = c.node {
|
||||
let inference = infer_expression_types(db, test_expr);
|
||||
let scope = test_expr.scope(db);
|
||||
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope))
|
||||
} else {
|
||||
// TODO: handle other constraint nodes
|
||||
todo_type!()
|
||||
};
|
||||
|
||||
(c, ty)
|
||||
})
|
||||
};
|
||||
|
||||
if test_expr_tys().any(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
}
|
||||
}) {
|
||||
(Type::Never, UnconditionallyVisible::No)
|
||||
} else {
|
||||
let mut test_expr_tys_iter = test_expr_tys().peekable();
|
||||
|
||||
if test_expr_tys_iter.peek().is_some()
|
||||
&& test_expr_tys_iter.all(|(c, test_expr_ty)| {
|
||||
if c.is_positive {
|
||||
test_expr_ty.bool(db).is_always_true()
|
||||
} else {
|
||||
test_expr_ty.bool(db).is_always_false()
|
||||
}
|
||||
})
|
||||
{
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::Yes)
|
||||
} else {
|
||||
(declaration_ty(db, declaration), UnconditionallyVisible::No)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: get rid of all the collects and clean up, obviously
|
||||
let decl_types: Vec<_> = decl_types.collect();
|
||||
|
||||
// shrink the vector to only include everything from the last unconditionally visible binding
|
||||
let decl_types: Vec<_> = decl_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while_inclusive(|(_, unconditionally_visible)| {
|
||||
*unconditionally_visible != UnconditionallyVisible::Yes
|
||||
})
|
||||
.map(|(ty, _)| *ty)
|
||||
.collect();
|
||||
|
||||
let decl_types = decl_types.into_iter().rev();
|
||||
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
|
||||
let mut all_types = undeclared_ty.into_iter().chain(decl_types);
|
||||
|
||||
@@ -446,61 +324,6 @@ fn declarations_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Meta data for `Type::Todo`, which represents a known limitation in red-knot.
|
||||
#[cfg(debug_assertions)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TodoType {
|
||||
FileAndLine(&'static str, u32),
|
||||
Message(&'static str),
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
impl std::fmt::Display for TodoType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TodoType::FileAndLine(file, line) => write!(f, "[{file}:{line}]"),
|
||||
TodoType::Message(msg) => write!(f, "({msg})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TodoType;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
impl std::fmt::Display for TodoType {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Type::Todo` variant to represent a known limitation in the type system.
|
||||
///
|
||||
/// It can be used with a custom message (preferred): `todo_type!("PEP 604 not supported")`,
|
||||
/// or simply using `todo_type!()`, which will include information about the file and line.
|
||||
#[cfg(debug_assertions)]
|
||||
macro_rules! todo_type {
|
||||
() => {
|
||||
Type::Todo(crate::types::TodoType::FileAndLine(file!(), line!()))
|
||||
};
|
||||
($message:literal) => {
|
||||
Type::Todo(crate::types::TodoType::Message($message))
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
macro_rules! todo_type {
|
||||
() => {
|
||||
Type::Todo(crate::types::TodoType)
|
||||
};
|
||||
($message:literal) => {
|
||||
Type::Todo(crate::types::TodoType)
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use todo_type;
|
||||
|
||||
/// Representation of a type: a set of possible values at runtime.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum Type<'db> {
|
||||
@@ -517,9 +340,7 @@ pub enum Type<'db> {
|
||||
/// General rule: `Todo` should only propagate when the presence of the input `Todo` caused the
|
||||
/// output to be unknown. An output should only be `Todo` if fixing all `Todo` inputs to be not
|
||||
/// `Todo` would change the output type.
|
||||
///
|
||||
/// This variant should be created with the `todo_type!` macro.
|
||||
Todo(TodoType),
|
||||
Todo,
|
||||
/// The empty set of values
|
||||
Never,
|
||||
/// A specific function object
|
||||
@@ -563,7 +384,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub const fn is_todo(&self) -> bool {
|
||||
matches!(self, Type::Todo(_))
|
||||
matches!(self, Type::Todo)
|
||||
}
|
||||
|
||||
pub const fn class_literal(class: Class<'db>) -> Self {
|
||||
@@ -659,19 +480,6 @@ impl<'db> Type<'db> {
|
||||
.expect("Expected a Type::IntLiteral variant")
|
||||
}
|
||||
|
||||
pub const fn into_known_instance(self) -> Option<KnownInstanceType<'db>> {
|
||||
match self {
|
||||
Type::KnownInstance(known_instance) => Some(known_instance),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn expect_known_instance(self) -> KnownInstanceType<'db> {
|
||||
self.into_known_instance()
|
||||
.expect("Expected a Type::KnownInstance variant")
|
||||
}
|
||||
|
||||
pub const fn is_boolean_literal(&self) -> bool {
|
||||
matches!(self, Type::BooleanLiteral(..))
|
||||
}
|
||||
@@ -722,8 +530,8 @@ impl<'db> Type<'db> {
|
||||
return true;
|
||||
}
|
||||
match (self, target) {
|
||||
(Type::Unknown | Type::Any | Type::Todo(_), _) => false,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo(_)) => false,
|
||||
(Type::Unknown | Type::Any | Type::Todo, _) => false,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo) => false,
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
(_, Type::Instance(InstanceType { class }))
|
||||
@@ -858,8 +666,8 @@ impl<'db> Type<'db> {
|
||||
return true;
|
||||
}
|
||||
match (self, target) {
|
||||
(Type::Unknown | Type::Any | Type::Todo(_), _) => true,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo(_)) => true,
|
||||
(Type::Unknown | Type::Any | Type::Todo, _) => true,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo) => true,
|
||||
(Type::Union(union), ty) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
@@ -890,7 +698,22 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: Once we have support for final classes, we can establish that
|
||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
||||
self == other || matches!((self, other), (Type::Todo(_), Type::Todo(_)))
|
||||
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
self == other
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if {
|
||||
let self_known = self_class.known(db);
|
||||
matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType))
|
||||
&& self_known == target_class.known(db)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Return true if this type and `other` have no common elements.
|
||||
@@ -903,7 +726,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::Any, _) | (_, Type::Any) => false,
|
||||
(Type::Unknown, _) | (_, Type::Unknown) => false,
|
||||
(Type::Todo(_), _) | (_, Type::Todo(_)) => false,
|
||||
(Type::Todo, _) | (_, Type::Todo) => false,
|
||||
|
||||
(Type::Union(union), other) | (other, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
@@ -1108,7 +931,7 @@ impl<'db> Type<'db> {
|
||||
Type::Any
|
||||
| Type::Never
|
||||
| Type::Unknown
|
||||
| Type::Todo(_)
|
||||
| Type::Todo
|
||||
| Type::IntLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
@@ -1184,10 +1007,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||
Some(
|
||||
KnownClass::NoneType
|
||||
| KnownClass::NoDefaultType
|
||||
| KnownClass::VersionInfo
|
||||
| KnownClass::TypeAliasType,
|
||||
KnownClass::NoneType | KnownClass::NoDefaultType | KnownClass::VersionInfo,
|
||||
) => true,
|
||||
Some(
|
||||
KnownClass::Bool
|
||||
@@ -1214,7 +1034,7 @@ impl<'db> Type<'db> {
|
||||
Type::Any
|
||||
| Type::Never
|
||||
| Type::Unknown
|
||||
| Type::Todo(_)
|
||||
| Type::Todo
|
||||
| Type::Union(..)
|
||||
| Type::Intersection(..)
|
||||
| Type::LiteralString => false,
|
||||
@@ -1232,12 +1052,12 @@ impl<'db> Type<'db> {
|
||||
Type::Any => Type::Any.into(),
|
||||
Type::Never => {
|
||||
// TODO: attribute lookup on Never type
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::Unknown => Type::Unknown.into(),
|
||||
Type::FunctionLiteral(_) => {
|
||||
// TODO: attribute lookup on function type
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::ModuleLiteral(file) => {
|
||||
// `__dict__` is a very special member that is never overridden by module globals;
|
||||
@@ -1287,7 +1107,7 @@ impl<'db> Type<'db> {
|
||||
Type::IntLiteral(Program::get(db).target_version(db).minor.into())
|
||||
}
|
||||
// TODO MRO? get_own_instance_member, get_instance_member
|
||||
_ => todo_type!("instance attributes"),
|
||||
_ => Type::Todo,
|
||||
};
|
||||
ty.into()
|
||||
}
|
||||
@@ -1329,36 +1149,36 @@ impl<'db> Type<'db> {
|
||||
Type::Intersection(_) => {
|
||||
// TODO perform the get_member on each type in the intersection
|
||||
// TODO return the intersection of those results
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::IntLiteral(_) => {
|
||||
// TODO raise error
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::BooleanLiteral(_) => todo_type!().into(),
|
||||
Type::BooleanLiteral(_) => Type::Todo.into(),
|
||||
Type::StringLiteral(_) => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::LiteralString => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::BytesLiteral(_) => {
|
||||
// TODO defer to Type::Instance(<bytes from typeshed>).member
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::SliceLiteral(_) => {
|
||||
// TODO defer to `builtins.slice` methods
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
Type::Tuple(_) => {
|
||||
// TODO: implement tuple methods
|
||||
todo_type!().into()
|
||||
Type::Todo.into()
|
||||
}
|
||||
&todo @ Type::Todo(_) => todo.into(),
|
||||
Type::Todo => Type::Todo.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1368,7 +1188,7 @@ impl<'db> Type<'db> {
|
||||
/// when `bool(x)` is called on an object `x`.
|
||||
fn bool(&self, db: &'db dyn Db) -> Truthiness {
|
||||
match self {
|
||||
Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
||||
Type::Any | Type::Todo | Type::Never | Type::Unknown => Truthiness::Ambiguous,
|
||||
Type::FunctionLiteral(_) => Truthiness::AlwaysTrue,
|
||||
Type::ModuleLiteral(_) => Truthiness::AlwaysTrue,
|
||||
Type::ClassLiteral(_) => {
|
||||
@@ -1509,7 +1329,7 @@ impl<'db> Type<'db> {
|
||||
// `Any` is callable, and its return type is also `Any`.
|
||||
Type::Any => CallOutcome::callable(Type::Any),
|
||||
|
||||
Type::Todo(_) => CallOutcome::callable(todo_type!()),
|
||||
Type::Todo => CallOutcome::callable(Type::Todo),
|
||||
|
||||
Type::Unknown => CallOutcome::callable(Type::Unknown),
|
||||
|
||||
@@ -1522,7 +1342,7 @@ impl<'db> Type<'db> {
|
||||
),
|
||||
|
||||
// TODO: intersection types
|
||||
Type::Intersection(_) => CallOutcome::callable(todo_type!()),
|
||||
Type::Intersection(_) => CallOutcome::callable(Type::Todo),
|
||||
|
||||
_ => CallOutcome::not_callable(self),
|
||||
}
|
||||
@@ -1561,7 +1381,7 @@ impl<'db> Type<'db> {
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(self, Type::Unknown | Type::Any | Type::Todo(_)) {
|
||||
if matches!(self, Type::Unknown | Type::Any | Type::Todo) {
|
||||
// Explicit handling of `Unknown` and `Any` necessary until `type[Unknown]` and
|
||||
// `type[Any]` are not defined as `Todo` anymore.
|
||||
return IterationOutcome::Iterable { element_ty: self };
|
||||
@@ -1620,14 +1440,14 @@ impl<'db> Type<'db> {
|
||||
pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Type::Any => Type::Any,
|
||||
todo @ Type::Todo(_) => *todo,
|
||||
Type::Todo => Type::Todo,
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Never => Type::Never,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
|
||||
Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class),
|
||||
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
||||
// TODO: we can probably do better here: --Alex
|
||||
Type::Intersection(_) => todo_type!(),
|
||||
Type::Intersection(_) => Type::Todo,
|
||||
// TODO: calling `.to_instance()` on any of these should result in a diagnostic,
|
||||
// since they already indicate that the object is an instance of some kind:
|
||||
Type::BooleanLiteral(_)
|
||||
@@ -1658,11 +1478,7 @@ impl<'db> Type<'db> {
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO map this to a new `Type::TypeVar` variant
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
|
||||
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
|
||||
Type::Never
|
||||
}
|
||||
_ => todo_type!(),
|
||||
_ => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1737,8 +1553,8 @@ impl<'db> Type<'db> {
|
||||
// TODO: `type[Unknown]`?
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO intersections
|
||||
Type::Intersection(_) => todo_type!(),
|
||||
todo @ Type::Todo(_) => *todo,
|
||||
Type::Intersection(_) => Type::Todo,
|
||||
Type::Todo => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1826,7 +1642,6 @@ pub enum KnownClass {
|
||||
// Typing
|
||||
SpecialForm,
|
||||
TypeVar,
|
||||
TypeAliasType,
|
||||
NoDefaultType,
|
||||
// sys
|
||||
VersionInfo,
|
||||
@@ -1853,7 +1668,6 @@ impl<'db> KnownClass {
|
||||
Self::NoneType => "NoneType",
|
||||
Self::SpecialForm => "_SpecialForm",
|
||||
Self::TypeVar => "TypeVar",
|
||||
Self::TypeAliasType => "TypeAliasType",
|
||||
Self::NoDefaultType => "_NoDefaultType",
|
||||
// This is the name the type of `sys.version_info` has in typeshed,
|
||||
// which is different to what `type(sys.version_info).__name__` is at runtime.
|
||||
@@ -1869,13 +1683,13 @@ impl<'db> KnownClass {
|
||||
}
|
||||
|
||||
pub fn to_class_literal(self, db: &'db dyn Db) -> Type<'db> {
|
||||
core_module_symbol(db, self.canonical_module(db), self.as_str())
|
||||
core_module_symbol(db, self.canonical_module(), self.as_str())
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
pub(crate) fn canonical_module(self, db: &'db dyn Db) -> CoreStdlibModule {
|
||||
pub(crate) const fn canonical_module(self) -> CoreStdlibModule {
|
||||
match self {
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
@@ -1892,19 +1706,11 @@ impl<'db> KnownClass {
|
||||
Self::VersionInfo => CoreStdlibModule::Sys,
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
||||
Self::NoneType => CoreStdlibModule::Typeshed,
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType => CoreStdlibModule::Typing,
|
||||
Self::NoDefaultType => {
|
||||
let python_version = Program::get(db).target_version(db);
|
||||
|
||||
// typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`. So we need to switch
|
||||
// to `typing.NoDefault` for newer versions:
|
||||
if python_version.major >= 3 && python_version.minor >= 13 {
|
||||
CoreStdlibModule::Typing
|
||||
} else {
|
||||
CoreStdlibModule::TypingExtensions
|
||||
}
|
||||
}
|
||||
Self::SpecialForm | Self::TypeVar => CoreStdlibModule::Typing,
|
||||
// TODO when we understand sys.version_info, we will need an explicit fallback here,
|
||||
// because typing_extensions has a 3.13+ re-export for the `typing.NoDefault`
|
||||
// singleton, but not for `typing._NoDefaultType`
|
||||
Self::NoDefaultType => CoreStdlibModule::TypingExtensions,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1914,7 +1720,7 @@ impl<'db> KnownClass {
|
||||
const fn is_singleton(self) -> bool {
|
||||
// TODO there are other singleton types (EllipsisType, NotImplementedType)
|
||||
match self {
|
||||
Self::NoneType | Self::NoDefaultType | Self::VersionInfo | Self::TypeAliasType => true,
|
||||
Self::NoneType | Self::NoDefaultType | Self::VersionInfo => true,
|
||||
Self::Bool
|
||||
| Self::Object
|
||||
| Self::Bytes
|
||||
@@ -1956,7 +1762,6 @@ impl<'db> KnownClass {
|
||||
"NoneType" => Self::NoneType,
|
||||
"ModuleType" => Self::ModuleType,
|
||||
"FunctionType" => Self::FunctionType,
|
||||
"TypeAliasType" => Self::TypeAliasType,
|
||||
"_SpecialForm" => Self::SpecialForm,
|
||||
"_NoDefaultType" => Self::NoDefaultType,
|
||||
"_version_info" => Self::VersionInfo,
|
||||
@@ -1964,11 +1769,11 @@ impl<'db> KnownClass {
|
||||
};
|
||||
|
||||
let module = file_to_module(db, file)?;
|
||||
candidate.check_module(db, &module).then_some(candidate)
|
||||
candidate.check_module(&module).then_some(candidate)
|
||||
}
|
||||
|
||||
/// Return `true` if the module of `self` matches `module_name`
|
||||
fn check_module(self, db: &dyn Db, module: &Module) -> bool {
|
||||
fn check_module(self, module: &Module) -> bool {
|
||||
if !module.search_path().is_standard_library() {
|
||||
return false;
|
||||
}
|
||||
@@ -1988,9 +1793,9 @@ impl<'db> KnownClass {
|
||||
| Self::GenericAlias
|
||||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
| Self::FunctionType => module.name() == self.canonical_module(db).as_str(),
|
||||
| Self::FunctionType => module.name() == self.canonical_module().as_str(),
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
||||
Self::SpecialForm | Self::TypeVar | Self::NoDefaultType => {
|
||||
matches!(module.name().as_str(), "typing" | "typing_extensions")
|
||||
}
|
||||
}
|
||||
@@ -2004,42 +1809,24 @@ pub enum KnownInstanceType<'db> {
|
||||
Literal,
|
||||
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
|
||||
Optional,
|
||||
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
|
||||
Union,
|
||||
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
|
||||
NoReturn,
|
||||
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
|
||||
Never,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
||||
TypeAliasType(TypeAliasType<'db>),
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
|
||||
impl<'db> KnownInstanceType<'db> {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Literal => "Literal",
|
||||
Self::Optional => "Optional",
|
||||
Self::Union => "Union",
|
||||
Self::TypeVar(_) => "TypeVar",
|
||||
Self::NoReturn => "NoReturn",
|
||||
Self::Never => "Never",
|
||||
Self::TypeAliasType(_) => "TypeAliasType",
|
||||
KnownInstanceType::Literal => "Literal",
|
||||
KnownInstanceType::Optional => "Optional",
|
||||
KnownInstanceType::TypeVar(_) => "TypeVar",
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate the known instance in boolean context
|
||||
pub const fn bool(self) -> Truthiness {
|
||||
match self {
|
||||
Self::Literal
|
||||
| Self::Optional
|
||||
| Self::TypeVar(_)
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
| Self::Never
|
||||
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
|
||||
Self::Literal | Self::Optional | Self::TypeVar(_) => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2048,11 +1835,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||
match self {
|
||||
Self::Literal => "typing.Literal",
|
||||
Self::Optional => "typing.Optional",
|
||||
Self::Union => "typing.Union",
|
||||
Self::NoReturn => "typing.NoReturn",
|
||||
Self::Never => "typing.Never",
|
||||
Self::TypeVar(typevar) => typevar.name(db),
|
||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2061,11 +1844,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||
match self {
|
||||
Self::Literal => KnownClass::SpecialForm,
|
||||
Self::Optional => KnownClass::SpecialForm,
|
||||
Self::Union => KnownClass::SpecialForm,
|
||||
Self::NoReturn => KnownClass::SpecialForm,
|
||||
Self::Never => KnownClass::SpecialForm,
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2085,9 +1864,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
match (module.name().as_str(), instance_name) {
|
||||
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
|
||||
("typing" | "typing_extensions", "Optional") => Some(Self::Optional),
|
||||
("typing" | "typing_extensions", "Union") => Some(Self::Union),
|
||||
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
|
||||
("typing" | "typing_extensions", "Never") => Some(Self::Never),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -2095,7 +1871,23 @@ impl<'db> KnownInstanceType<'db> {
|
||||
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
||||
let ty = match (self, name) {
|
||||
(Self::TypeVar(typevar), "__name__") => Type::string_literal(db, typevar.name(db)),
|
||||
(Self::TypeAliasType(alias), "__name__") => Type::string_literal(db, alias.name(db)),
|
||||
(Self::TypeVar(typevar), "__bound__") => typevar
|
||||
.upper_bound(db)
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.unwrap_or_else(|| KnownClass::NoneType.to_instance(db)),
|
||||
(Self::TypeVar(typevar), "__constraints__") => {
|
||||
let tuple_elements: Vec<Type<'db>> = typevar
|
||||
.constraints(db)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.collect();
|
||||
Type::tuple(db, &tuple_elements)
|
||||
}
|
||||
(Self::TypeVar(typevar), "__default__") => typevar
|
||||
.default_ty(db)
|
||||
.map(|ty| ty.to_meta_type(db))
|
||||
.unwrap_or_else(|| KnownClass::NoDefaultType.to_instance(db)),
|
||||
_ => return self.instance_fallback(db).member(db, name),
|
||||
};
|
||||
ty.into()
|
||||
@@ -2127,7 +1919,6 @@ pub struct TypeVarInstance<'db> {
|
||||
}
|
||||
|
||||
impl<'db> TypeVarInstance<'db> {
|
||||
#[allow(unused)]
|
||||
pub(crate) fn upper_bound(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
if let Some(TypeVarBoundOrConstraints::UpperBound(ty)) = self.bound_or_constraints(db) {
|
||||
Some(ty)
|
||||
@@ -2136,7 +1927,6 @@ impl<'db> TypeVarInstance<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn constraints(self, db: &'db dyn Db) -> Option<&[Type<'db>]> {
|
||||
if let Some(TypeVarBoundOrConstraints::Constraints(tuple)) = self.bound_or_constraints(db) {
|
||||
Some(tuple.elements(db))
|
||||
@@ -2510,14 +2300,6 @@ impl Truthiness {
|
||||
matches!(self, Truthiness::Ambiguous)
|
||||
}
|
||||
|
||||
const fn is_always_false(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysFalse)
|
||||
}
|
||||
|
||||
const fn is_always_true(self) -> bool {
|
||||
matches!(self, Truthiness::AlwaysTrue)
|
||||
}
|
||||
|
||||
const fn negate(self) -> Self {
|
||||
match self {
|
||||
Self::AlwaysTrue => Self::AlwaysFalse,
|
||||
@@ -2817,7 +2599,7 @@ impl<'db> Class<'db> {
|
||||
// TODO: If the metaclass is not a class, we should verify that it's a callable
|
||||
// which accepts the same arguments as `type.__new__` (otherwise error), and return
|
||||
// the meta-type of its return type. (And validate that is a class type?)
|
||||
return Ok(todo_type!("metaclass not a class"));
|
||||
return Ok(Type::Todo);
|
||||
};
|
||||
|
||||
// Reconcile all base classes' metaclasses with the candidate metaclass.
|
||||
@@ -2931,27 +2713,6 @@ impl<'db> Class<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
pub struct TypeAliasType<'db> {
|
||||
#[return_ref]
|
||||
pub name: ast::name::Name,
|
||||
|
||||
rhs_scope: ScopeId<'db>,
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> TypeAliasType<'db> {
|
||||
#[salsa::tracked]
|
||||
pub fn value_ty(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let scope = self.rhs_scope(db);
|
||||
|
||||
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||
let definition = semantic_index(db, scope.file(db)).definition(type_alias_stmt_node);
|
||||
|
||||
definition_expression_ty(db, definition, &type_alias_stmt_node.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct MetaclassCandidate<'db> {
|
||||
@@ -3138,11 +2899,6 @@ impl<'db> TupleType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that the `Type` enum does not grow unexpectedly.
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
static_assertions::assert_eq_size!(Type, [u8; 16]);
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
@@ -3158,7 +2914,14 @@ pub(crate) mod tests {
|
||||
use ruff_python_ast as ast;
|
||||
use test_case::test_case;
|
||||
|
||||
pub(crate) fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[test]
|
||||
fn no_bloat_enum_sizes() {
|
||||
use std::mem::size_of;
|
||||
assert_eq!(size_of::<Type>(), 16);
|
||||
}
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -3169,7 +2932,7 @@ pub(crate) mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: python_version,
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -3178,10 +2941,6 @@ pub(crate) mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
/// A test representation of a type that can be transformed unambiguously into a real Type,
|
||||
/// given a db.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -3215,7 +2974,7 @@ pub(crate) mod tests {
|
||||
Ty::Unknown => Type::Unknown,
|
||||
Ty::None => Type::none(db),
|
||||
Ty::Any => Type::Any,
|
||||
Ty::Todo => todo_type!("Ty::Todo"),
|
||||
Ty::Todo => Type::Todo,
|
||||
Ty::IntLiteral(n) => Type::IntLiteral(n),
|
||||
Ty::StringLiteral(s) => Type::string_literal(db, s),
|
||||
Ty::BooleanLiteral(b) => Type::BooleanLiteral(b),
|
||||
@@ -3629,23 +3388,13 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::BooleanLiteral(false))]
|
||||
#[test_case(Ty::KnownClassInstance(KnownClass::NoDefaultType))]
|
||||
fn is_singleton(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
assert!(from.into_type(&db).is_singleton(&db));
|
||||
}
|
||||
|
||||
/// TODO: test documentation
|
||||
#[test_case(PythonVersion::PY312)]
|
||||
#[test_case(PythonVersion::PY313)]
|
||||
fn no_default_type_is_singleton(python_version: PythonVersion) {
|
||||
let db = setup_db_with_python_version(python_version);
|
||||
|
||||
let no_default = Ty::KnownClassInstance(KnownClass::NoDefaultType).into_type(&db);
|
||||
|
||||
assert!(no_default.is_singleton(&db));
|
||||
}
|
||||
|
||||
#[test_case(Ty::None)]
|
||||
#[test_case(Ty::BooleanLiteral(true))]
|
||||
#[test_case(Ty::IntLiteral(1))]
|
||||
@@ -3816,95 +3565,4 @@ pub(crate) mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_alias_types() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/mod.py",
|
||||
r#"
|
||||
type Alias1 = int
|
||||
type Alias2 = int
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let mod_py = system_path_to_file(&db, "src/mod.py")?;
|
||||
let ty_alias1 = global_symbol(&db, mod_py, "Alias1").expect_type();
|
||||
let ty_alias2 = global_symbol(&db, mod_py, "Alias2").expect_type();
|
||||
|
||||
let Type::KnownInstance(KnownInstanceType::TypeAliasType(alias1)) = ty_alias1 else {
|
||||
panic!("Expected TypeAliasType, got {ty_alias1:?}");
|
||||
};
|
||||
assert_eq!(alias1.name(&db), "Alias1");
|
||||
assert_eq!(alias1.value_ty(&db), KnownClass::Int.to_instance(&db));
|
||||
|
||||
// Two type aliases are distinct and disjoint, even if they refer to the same type
|
||||
assert!(!ty_alias1.is_equivalent_to(&db, ty_alias2));
|
||||
assert!(ty_alias1.is_disjoint_from(&db, ty_alias2));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// All other tests also make sure that `Type::Todo` works as expected. This particular
|
||||
/// test makes sure that we handle `Todo` types correctly, even if they originate from
|
||||
/// different sources.
|
||||
#[test]
|
||||
fn todo_types() {
|
||||
let db = setup_db();
|
||||
|
||||
let todo1 = todo_type!("1");
|
||||
let todo2 = todo_type!("2");
|
||||
let todo3 = todo_type!();
|
||||
let todo4 = todo_type!();
|
||||
|
||||
assert!(todo1.is_equivalent_to(&db, todo2));
|
||||
assert!(todo3.is_equivalent_to(&db, todo4));
|
||||
assert!(todo1.is_equivalent_to(&db, todo3));
|
||||
|
||||
assert!(todo1.is_subtype_of(&db, todo2));
|
||||
assert!(todo2.is_subtype_of(&db, todo1));
|
||||
|
||||
assert!(todo3.is_subtype_of(&db, todo4));
|
||||
assert!(todo4.is_subtype_of(&db, todo3));
|
||||
|
||||
assert!(todo1.is_subtype_of(&db, todo3));
|
||||
assert!(todo3.is_subtype_of(&db, todo1));
|
||||
|
||||
let int = KnownClass::Int.to_instance(&db);
|
||||
|
||||
assert!(int.is_assignable_to(&db, todo1));
|
||||
assert!(int.is_assignable_to(&db, todo3));
|
||||
|
||||
assert!(todo1.is_assignable_to(&db, int));
|
||||
assert!(todo3.is_assignable_to(&db, int));
|
||||
|
||||
// We lose information when combining several `Todo` types. This is an
|
||||
// acknowledged limitation of the current implementation. We can not
|
||||
// easily store the meta information of several `Todo`s in a single
|
||||
// variant, as `TodoType` needs to implement `Copy`, meaning it can't
|
||||
// contain `Vec`/`Box`/etc., and can't be boxed itself.
|
||||
//
|
||||
// Lifting this restriction would require us to intern `TodoType` in
|
||||
// salsa, but that would mean we would have to pass in `db` everywhere.
|
||||
|
||||
// A union of several `Todo` types collapses to a single `Todo` type:
|
||||
assert!(UnionType::from_elements(&db, vec![todo1, todo2, todo3, todo4]).is_todo());
|
||||
|
||||
// And similar for intersection types:
|
||||
assert!(IntersectionBuilder::new(&db)
|
||||
.add_positive(todo1)
|
||||
.add_positive(todo2)
|
||||
.add_positive(todo3)
|
||||
.add_positive(todo4)
|
||||
.build()
|
||||
.is_todo());
|
||||
assert!(IntersectionBuilder::new(&db)
|
||||
.add_positive(todo1)
|
||||
.add_negative(todo2)
|
||||
.add_positive(todo3)
|
||||
.add_negative(todo4)
|
||||
.build()
|
||||
.is_todo());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +309,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
self.add_positive(db, *neg);
|
||||
}
|
||||
}
|
||||
ty @ (Type::Any | Type::Unknown | Type::Todo(_)) => {
|
||||
ty @ (Type::Any | Type::Unknown | Type::Todo) => {
|
||||
// Adding any of these types to the negative side of an intersection
|
||||
// is equivalent to adding it to the positive side. We do this to
|
||||
// simplify the representation.
|
||||
@@ -379,7 +379,7 @@ mod tests {
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::stdlib::typing_symbol;
|
||||
use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder};
|
||||
use crate::types::{global_symbol, KnownClass, UnionBuilder};
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
@@ -987,7 +987,7 @@ mod tests {
|
||||
|
||||
#[test_case(Type::Any)]
|
||||
#[test_case(Type::Unknown)]
|
||||
#[test_case(todo_type!())]
|
||||
#[test_case(Type::Todo)]
|
||||
fn build_intersection_t_and_negative_t_does_not_simplify(ty: Type) {
|
||||
let db = setup_db();
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||
}
|
||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
Type::Todo(todo) => write!(f, "@Todo{todo}"),
|
||||
Type::Todo => f.write_str("@Todo"),
|
||||
Type::ModuleLiteral(file) => {
|
||||
write!(f, "<module '{:?}'>", file.path(self.db))
|
||||
}
|
||||
|
||||
@@ -52,12 +52,11 @@ use crate::types::diagnostic::{TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type,
|
||||
typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType,
|
||||
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
|
||||
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol,
|
||||
Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, UnionBuilder, UnionType,
|
||||
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol,
|
||||
Boundness, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder,
|
||||
IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType,
|
||||
MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, Truthiness, TupleType, Type,
|
||||
TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::unpack::Unpack;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
@@ -439,12 +438,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
NodeWithScopeKind::FunctionTypeParameters(function) => {
|
||||
self.infer_function_type_params(function.node());
|
||||
}
|
||||
NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => {
|
||||
self.infer_type_alias_type_params(type_alias.node());
|
||||
}
|
||||
NodeWithScopeKind::TypeAlias(type_alias) => {
|
||||
self.infer_type_alias(type_alias.node());
|
||||
}
|
||||
NodeWithScopeKind::ListComprehension(comprehension) => {
|
||||
self.infer_list_comprehension_expression_scope(comprehension.node());
|
||||
}
|
||||
@@ -612,9 +605,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_function_definition(function.node(), definition);
|
||||
}
|
||||
DefinitionKind::Class(class) => self.infer_class_definition(class.node(), definition),
|
||||
DefinitionKind::TypeAlias(type_alias) => {
|
||||
self.infer_type_alias_definition(type_alias.node(), definition);
|
||||
}
|
||||
DefinitionKind::Import(import) => {
|
||||
self.infer_import_definition(import.node(), definition);
|
||||
}
|
||||
@@ -857,19 +847,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_parameters(&function.parameters);
|
||||
}
|
||||
|
||||
fn infer_type_alias_type_params(&mut self, type_alias: &ast::StmtTypeAlias) {
|
||||
let type_params = type_alias
|
||||
.type_params
|
||||
.as_ref()
|
||||
.expect("type alias type params scope without type params");
|
||||
|
||||
self.infer_type_parameters(type_params);
|
||||
}
|
||||
|
||||
fn infer_type_alias(&mut self, type_alias: &ast::StmtTypeAlias) {
|
||||
self.infer_annotation_expression(&type_alias.value, DeferredExpressionState::Deferred);
|
||||
}
|
||||
|
||||
fn infer_function_body(&mut self, function: &ast::StmtFunctionDef) {
|
||||
self.infer_body(&function.body);
|
||||
}
|
||||
@@ -1050,7 +1027,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
) {
|
||||
// TODO(dhruvmanila): Annotation expression is resolved at the enclosing scope, infer the
|
||||
// parameter type from there
|
||||
let annotated_ty = todo_type!("function parameter type");
|
||||
let annotated_ty = Type::Todo;
|
||||
if parameter.annotation.is_some() {
|
||||
self.add_declaration_with_binding(
|
||||
parameter.into(),
|
||||
@@ -1130,33 +1107,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_type_alias_definition(
|
||||
&mut self,
|
||||
type_alias: &ast::StmtTypeAlias,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
self.infer_expression(&type_alias.name);
|
||||
|
||||
let rhs_scope = self
|
||||
.index
|
||||
.node_scope(NodeWithScopeRef::TypeAlias(type_alias))
|
||||
.to_scope_id(self.db, self.file);
|
||||
|
||||
let type_alias_ty =
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::new(
|
||||
self.db,
|
||||
&type_alias.name.as_name_expr().unwrap().id,
|
||||
rhs_scope,
|
||||
)));
|
||||
|
||||
self.add_declaration_with_binding(
|
||||
type_alias.into(),
|
||||
definition,
|
||||
type_alias_ty,
|
||||
type_alias_ty,
|
||||
);
|
||||
}
|
||||
|
||||
fn infer_if_statement(&mut self, if_statement: &ast::StmtIf) {
|
||||
let ast::StmtIf {
|
||||
range: _,
|
||||
@@ -1286,7 +1236,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
) -> Type<'db> {
|
||||
// TODO: Handle async with statements (they use `aenter` and `aexit`)
|
||||
if is_async {
|
||||
return todo_type!("async with statement");
|
||||
return Type::Todo;
|
||||
}
|
||||
|
||||
let context_manager_ty = context_expression_ty.to_meta_type(self.db);
|
||||
@@ -1436,12 +1386,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.db,
|
||||
tuple.elements(self.db).iter().map(|ty| {
|
||||
ty.into_class_literal()
|
||||
.map_or(todo_type!(), |ClassLiteralType { class }| {
|
||||
.map_or(Type::Todo, |ClassLiteralType { class }| {
|
||||
Type::instance(class)
|
||||
})
|
||||
}),
|
||||
),
|
||||
_ => todo_type!("exception type"),
|
||||
_ => Type::Todo,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1511,7 +1461,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
default,
|
||||
} = node;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!());
|
||||
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_typevartuple_definition(
|
||||
@@ -1525,7 +1475,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
default,
|
||||
} = node;
|
||||
self.infer_optional_expression(default.as_deref());
|
||||
self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!());
|
||||
self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
|
||||
@@ -1560,7 +1510,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// against the subject expression type (which we can query via `infer_expression_types`)
|
||||
// and extract the type at the `index` position if the pattern matches. This will be
|
||||
// similar to the logic in `self.infer_assignment_definition`.
|
||||
self.add_binding(pattern.into(), definition, todo_type!());
|
||||
self.add_binding(pattern.into(), definition, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
|
||||
@@ -1879,8 +1829,17 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_augmented_op(assignment, target_type, value_type)
|
||||
}
|
||||
|
||||
fn infer_type_alias_statement(&mut self, node: &ast::StmtTypeAlias) {
|
||||
self.infer_definition(node);
|
||||
fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) {
|
||||
let ast::StmtTypeAlias {
|
||||
range: _,
|
||||
name,
|
||||
type_params: _,
|
||||
value,
|
||||
} = type_alias_statement;
|
||||
self.infer_expression(value);
|
||||
self.infer_expression(name);
|
||||
|
||||
// TODO: properly handle generic type aliases, which need their own annotation scope
|
||||
}
|
||||
|
||||
fn infer_for_statement(&mut self, for_statement: &ast::StmtFor) {
|
||||
@@ -1915,7 +1874,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let iterable_ty = self.infer_standalone_expression(iterable);
|
||||
|
||||
let loop_var_value_ty = if is_async {
|
||||
todo_type!("async iterables/iterators")
|
||||
// TODO(Alex): async iterables/iterators!
|
||||
Type::Todo
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
@@ -2243,7 +2203,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression),
|
||||
ast::Expr::IpyEscapeCommand(_) => {
|
||||
// TODO Implement Ipy escape command support
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2437,7 +2397,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_first_comprehension_iter(generators);
|
||||
|
||||
// TODO generator type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> {
|
||||
@@ -2450,7 +2410,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_first_comprehension_iter(generators);
|
||||
|
||||
// TODO list type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> {
|
||||
@@ -2464,7 +2424,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_first_comprehension_iter(generators);
|
||||
|
||||
// TODO dict type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> {
|
||||
@@ -2477,7 +2437,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_first_comprehension_iter(generators);
|
||||
|
||||
// TODO set type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) {
|
||||
@@ -2592,7 +2552,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
let target_ty = if is_async {
|
||||
// TODO: async iterables/iterators! -- Alex
|
||||
todo_type!("async iterables/iterators")
|
||||
Type::Todo
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
@@ -2682,7 +2642,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
// TODO function type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
|
||||
@@ -2713,7 +2673,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
|
||||
|
||||
// TODO
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
|
||||
@@ -2722,7 +2682,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_optional_expression(value.as_deref());
|
||||
|
||||
// TODO awaitable type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
|
||||
@@ -2734,7 +2694,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics);
|
||||
|
||||
// TODO get type from `ReturnType` of generator
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> {
|
||||
@@ -2743,7 +2703,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_expression(value);
|
||||
|
||||
// TODO awaitable type
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
/// Look up a name reference that isn't bound in the local scope.
|
||||
@@ -3019,7 +2979,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
_ => todo_type!(), // TODO other unary op types
|
||||
_ => Type::Todo, // TODO other unary op types
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3267,7 +3227,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
(left, Type::BooleanLiteral(bool_value), op) => {
|
||||
self.infer_binary_expression_type(left, Type::IntLiteral(i64::from(bool_value)), op)
|
||||
}
|
||||
_ => Some(todo_type!()), // TODO
|
||||
_ => Some(Type::Todo), // TODO
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3683,16 +3643,16 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let lhs_elements = lhs.elements(self.db);
|
||||
let rhs_elements = rhs.elements(self.db);
|
||||
|
||||
let mut tuple_rich_comparison =
|
||||
|op| self.infer_tuple_rich_comparison(lhs_elements, op, rhs_elements);
|
||||
let mut lexicographic_type_comparison =
|
||||
|op| self.infer_lexicographic_type_comparison(lhs_elements, op, rhs_elements);
|
||||
|
||||
match op {
|
||||
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
|
||||
ast::CmpOp::NotEq => tuple_rich_comparison(RichCompareOperator::Ne),
|
||||
ast::CmpOp::Lt => tuple_rich_comparison(RichCompareOperator::Lt),
|
||||
ast::CmpOp::LtE => tuple_rich_comparison(RichCompareOperator::Le),
|
||||
ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt),
|
||||
ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge),
|
||||
ast::CmpOp::Eq => lexicographic_type_comparison(RichCompareOperator::Eq),
|
||||
ast::CmpOp::NotEq => lexicographic_type_comparison(RichCompareOperator::Ne),
|
||||
ast::CmpOp::Lt => lexicographic_type_comparison(RichCompareOperator::Lt),
|
||||
ast::CmpOp::LtE => lexicographic_type_comparison(RichCompareOperator::Le),
|
||||
ast::CmpOp::Gt => lexicographic_type_comparison(RichCompareOperator::Gt),
|
||||
ast::CmpOp::GtE => lexicographic_type_comparison(RichCompareOperator::Ge),
|
||||
ast::CmpOp::In | ast::CmpOp::NotIn => {
|
||||
let mut eq_count = 0usize;
|
||||
let mut not_eq_count = 0usize;
|
||||
@@ -3705,7 +3665,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
|
||||
|
||||
match eq_result {
|
||||
todo @ Type::Todo(_) => return Ok(todo),
|
||||
Type::Todo => return Ok(Type::Todo),
|
||||
ty => match ty.bool(self.db) {
|
||||
Truthiness::AlwaysTrue => eq_count += 1,
|
||||
Truthiness::AlwaysFalse => not_eq_count += 1,
|
||||
@@ -3725,12 +3685,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
ast::CmpOp::Is | ast::CmpOp::IsNot => {
|
||||
// - `[ast::CmpOp::Is]`: returns `false` if the elements are definitely unequal, otherwise `bool`
|
||||
// - `[ast::CmpOp::IsNot]`: returns `true` if the elements are definitely unequal, otherwise `bool`
|
||||
let eq_result = tuple_rich_comparison(RichCompareOperator::Eq).expect(
|
||||
let eq_result = lexicographic_type_comparison(RichCompareOperator::Eq)
|
||||
.expect(
|
||||
"infer_binary_type_comparison should never return None for `CmpOp::Eq`",
|
||||
);
|
||||
|
||||
Ok(match eq_result {
|
||||
todo @ Type::Todo(_) => todo,
|
||||
Type::Todo => Type::Todo,
|
||||
ty => match ty.bool(self.db) {
|
||||
Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()),
|
||||
_ => KnownClass::Bool.to_instance(self.db),
|
||||
@@ -3785,85 +3746,58 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO: handle more types
|
||||
_ => match op {
|
||||
ast::CmpOp::Is | ast::CmpOp::IsNot => Ok(KnownClass::Bool.to_instance(self.db)),
|
||||
_ => Ok(todo_type!()),
|
||||
_ => Ok(Type::Todo),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Simulates rich comparison between tuples and returns the inferred result.
|
||||
/// This performs a lexicographic comparison, returning a union of all possible return types that could result from the comparison.
|
||||
/// Performs lexicographic comparison between two slices of types.
|
||||
///
|
||||
/// basically it's based on cpython's `tuple_richcompare`
|
||||
/// see `<https://github.com/python/cpython/blob/9d6366b60d01305fc5e45100e0cd13e358aa397d/Objects/tupleobject.c#L637>`
|
||||
fn infer_tuple_rich_comparison(
|
||||
/// For lexicographic comparison, elements from both slices are compared pairwise using
|
||||
/// `infer_binary_type_comparison`. If a conclusive result cannot be determined as a `BooleanLiteral`,
|
||||
/// it returns `bool`. Returns `None` if the comparison is not supported.
|
||||
fn infer_lexicographic_type_comparison(
|
||||
&mut self,
|
||||
left: &[Type<'db>],
|
||||
op: RichCompareOperator,
|
||||
right: &[Type<'db>],
|
||||
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
|
||||
let left_iter = left.iter().copied();
|
||||
let right_iter = right.iter().copied();
|
||||
|
||||
let mut builder = UnionBuilder::new(self.db);
|
||||
|
||||
for (l_ty, r_ty) in left_iter.zip(right_iter) {
|
||||
let pairwise_eq_result = self
|
||||
// Compare paired elements from left and right slices
|
||||
for (l_ty, r_ty) in left.iter().copied().zip(right.iter().copied()) {
|
||||
let eq_result = self
|
||||
.infer_binary_type_comparison(l_ty, ast::CmpOp::Eq, r_ty)
|
||||
.expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`");
|
||||
|
||||
match pairwise_eq_result {
|
||||
match eq_result {
|
||||
// If propagation is required, return the result as is
|
||||
todo @ Type::Todo(_) => return Ok(todo),
|
||||
Type::Todo => return Ok(Type::Todo),
|
||||
ty => match ty.bool(self.db) {
|
||||
// - AlwaysTrue : Continue to the next pair for lexicographic comparison
|
||||
// Types are equal, continue to the next pair
|
||||
Truthiness::AlwaysTrue => continue,
|
||||
// - AlwaysFalse:
|
||||
// Lexicographic comparisons will always terminate with this pair.
|
||||
// Complete the comparison and return the result.
|
||||
// - Ambiguous:
|
||||
// Lexicographic comparisons might continue to the next pair (if eq_result is true),
|
||||
// or terminate here (if eq_result is false).
|
||||
// To account for cases where the comparison terminates here, add the pairwise comparison result to the union builder.
|
||||
eq_truthiness @ (Truthiness::AlwaysFalse | Truthiness::Ambiguous) => {
|
||||
let pairwise_compare_result = match op {
|
||||
RichCompareOperator::Lt
|
||||
| RichCompareOperator::Le
|
||||
| RichCompareOperator::Gt
|
||||
| RichCompareOperator::Ge => {
|
||||
self.infer_binary_type_comparison(l_ty, op.into(), r_ty)?
|
||||
}
|
||||
// For `==` and `!=`, we already figure out the result from `pairwise_eq_result`
|
||||
// NOTE: The CPython implementation does not account for non-boolean return types
|
||||
// or cases where `!=` is not the negation of `==`, we also do not consider these cases.
|
||||
RichCompareOperator::Eq => Type::BooleanLiteral(false),
|
||||
RichCompareOperator::Ne => Type::BooleanLiteral(true),
|
||||
};
|
||||
|
||||
builder = builder.add(pairwise_compare_result);
|
||||
|
||||
if eq_truthiness.is_ambiguous() {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(builder.build());
|
||||
// Types are not equal, perform the specified comparison and return the result
|
||||
Truthiness::AlwaysFalse => {
|
||||
return self.infer_binary_type_comparison(l_ty, op.into(), r_ty)
|
||||
}
|
||||
// If the intermediate result is ambiguous, we cannot determine the final result as BooleanLiteral.
|
||||
// In this case, we simply return a bool instance.
|
||||
Truthiness::Ambiguous => return Ok(KnownClass::Bool.to_instance(self.db)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// if no more items to compare, we just compare sizes
|
||||
// At this point, the lengths of the two slices may be different, but the prefix of
|
||||
// left and right slices is entirely identical.
|
||||
// We return a comparison of the slice lengths based on the operator.
|
||||
let (left_len, right_len) = (left.len(), right.len());
|
||||
|
||||
builder = builder.add(Type::BooleanLiteral(match op {
|
||||
Ok(Type::BooleanLiteral(match op {
|
||||
RichCompareOperator::Eq => left_len == right_len,
|
||||
RichCompareOperator::Ne => left_len != right_len,
|
||||
RichCompareOperator::Lt => left_len < right_len,
|
||||
RichCompareOperator::Le => left_len <= right_len,
|
||||
RichCompareOperator::Gt => left_len > right_len,
|
||||
RichCompareOperator::Ge => left_len >= right_len,
|
||||
}));
|
||||
|
||||
Ok(builder.build())
|
||||
}))
|
||||
}
|
||||
|
||||
fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
|
||||
@@ -4229,7 +4163,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
"annotation-f-string",
|
||||
format_args!("Type expressions cannot use f-strings"),
|
||||
);
|
||||
self.infer_fstring_expression(fstring);
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
@@ -4304,7 +4237,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_name_expression(name).in_type_expression(self.db)
|
||||
}
|
||||
ast::ExprContext::Invalid => Type::Unknown,
|
||||
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(),
|
||||
ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo,
|
||||
},
|
||||
|
||||
ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx {
|
||||
@@ -4312,7 +4245,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
.infer_attribute_expression(attribute_expression)
|
||||
.in_type_expression(self.db),
|
||||
ast::ExprContext::Invalid => Type::Unknown,
|
||||
ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(),
|
||||
ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo,
|
||||
},
|
||||
|
||||
ast::Expr::NoneLiteral(_literal) => Type::none(self.db),
|
||||
@@ -4322,14 +4255,14 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
// TODO: an Ellipsis literal *on its own* does not have any meaning in annotation
|
||||
// expressions, but is meaningful in the context of a number of special forms.
|
||||
ast::Expr::EllipsisLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::EllipsisLiteral(_literal) => Type::Todo,
|
||||
|
||||
// Other literals do not have meaningful values in the annotation expression context.
|
||||
// However, we will we want to handle these differently when working with special forms,
|
||||
// since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
|
||||
ast::Expr::BytesLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::NumberLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::BooleanLiteral(_literal) => todo_type!(),
|
||||
ast::Expr::BytesLiteral(_literal) => Type::Todo,
|
||||
ast::Expr::NumberLiteral(_literal) => Type::Todo,
|
||||
ast::Expr::BooleanLiteral(_literal) => Type::Todo,
|
||||
|
||||
ast::Expr::Subscript(subscript) => {
|
||||
let ast::ExprSubscript {
|
||||
@@ -4372,13 +4305,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
// TODO PEP 646
|
||||
ast::Expr::Starred(starred) => {
|
||||
self.infer_starred_expression(starred);
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
// Avoid inferring the types of invalid type expressions that have been parsed from a
|
||||
// string annotation, as they are not present in the semantic index.
|
||||
_ if self.deferred_state.in_string_annotation() => Type::Unknown,
|
||||
|
||||
// Forms which are invalid in the context of annotation expressions: we infer their
|
||||
// nested expressions as normal expressions, but the type of the top-level expression is
|
||||
// always `Type::Unknown` in these cases.
|
||||
@@ -4462,6 +4391,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
self.infer_slice_expression(slice);
|
||||
Type::Unknown
|
||||
}
|
||||
|
||||
ast::Expr::IpyEscapeCommand(_) => todo!("Implement Ipy escape command support"),
|
||||
}
|
||||
}
|
||||
@@ -4520,7 +4450,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
}
|
||||
|
||||
let ty = if return_todo {
|
||||
todo_type!("full tuple[...] support")
|
||||
Type::Todo
|
||||
} else {
|
||||
Type::tuple(self.db, &element_types)
|
||||
};
|
||||
@@ -4535,7 +4465,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
single_element => {
|
||||
let single_element_ty = self.infer_type_expression(single_element);
|
||||
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) {
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
} else {
|
||||
Type::tuple(self.db, &[single_element_ty])
|
||||
}
|
||||
@@ -4551,13 +4481,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
|
||||
Type::subclass_of(class)
|
||||
} else {
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
}
|
||||
// TODO: attributes, unions, subscripts, etc.
|
||||
_ => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!()
|
||||
Type::Todo
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4576,21 +4506,20 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
match value_ty {
|
||||
Type::KnownInstance(known_instance) => {
|
||||
self.infer_parameterized_known_instance_type_expression(subscript, known_instance)
|
||||
self.infer_parameterized_known_instance_type_expression(known_instance, slice)
|
||||
}
|
||||
_ => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("generics")
|
||||
Type::Todo // TODO: generics
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_parameterized_known_instance_type_expression(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
known_instance: KnownInstanceType,
|
||||
parameters: &ast::Expr,
|
||||
) -> Type<'db> {
|
||||
let parameters = &*subscript.slice;
|
||||
match known_instance {
|
||||
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
|
||||
Ok(ty) => ty,
|
||||
@@ -4612,36 +4541,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
let param_type = self.infer_type_expression(parameters);
|
||||
UnionType::from_elements(self.db, [param_type, Type::none(self.db)])
|
||||
}
|
||||
KnownInstanceType::Union => match parameters {
|
||||
ast::Expr::Tuple(t) => {
|
||||
let union_ty = UnionType::from_elements(
|
||||
self.db,
|
||||
t.iter().map(|elt| self.infer_type_expression(elt)),
|
||||
);
|
||||
self.store_expression_type(parameters, union_ty);
|
||||
union_ty
|
||||
}
|
||||
_ => self.infer_type_expression(parameters),
|
||||
},
|
||||
KnownInstanceType::TypeVar(_) => {
|
||||
self.infer_type_expression(parameters);
|
||||
todo_type!()
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(_) => {
|
||||
self.infer_type_expression(parameters);
|
||||
todo_type!("generic type alias")
|
||||
}
|
||||
KnownInstanceType::NoReturn | KnownInstanceType::Never => {
|
||||
self.diagnostics.add(
|
||||
subscript.into(),
|
||||
"invalid-type-parameter",
|
||||
format_args!(
|
||||
"Type `{}` expected no type parameter",
|
||||
known_instance.repr(self.db)
|
||||
),
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
KnownInstanceType::TypeVar(_) => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5009,8 +4909,8 @@ fn perform_membership_test_comparison<'db>(
|
||||
|
||||
compare_result_opt
|
||||
.map(|ty| {
|
||||
if matches!(ty, Type::Todo(_)) {
|
||||
return ty;
|
||||
if matches!(ty, Type::Todo) {
|
||||
return Type::Todo;
|
||||
}
|
||||
|
||||
match op {
|
||||
@@ -5031,7 +4931,7 @@ mod tests {
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::{self, PythonVersion};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::FileScopeId;
|
||||
use crate::semantic_index::{global_scope, semantic_index, symbol_table, use_def_map};
|
||||
@@ -5041,11 +4941,10 @@ mod tests {
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use test_case::test_case;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup_db_with_python_version(python_version: PythonVersion) -> TestDb {
|
||||
fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
@@ -5056,7 +4955,7 @@ mod tests {
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: python_version,
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
@@ -5065,10 +4964,6 @@ mod tests {
|
||||
db
|
||||
}
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
setup_db_with_python_version(PythonVersion::default())
|
||||
}
|
||||
|
||||
fn setup_db_with_custom_typeshed<'a>(
|
||||
typeshed: &str,
|
||||
files: impl IntoIterator<Item = (&'a str, &'a str)>,
|
||||
@@ -5340,10 +5235,9 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(PythonVersion::PY39, "ellipsis")]
|
||||
#[test_case(PythonVersion::PY310, "EllipsisType")]
|
||||
fn ellipsis_type(version: PythonVersion, expected_type: &str) -> anyhow::Result<()> {
|
||||
let mut db = setup_db_with_python_version(version);
|
||||
#[test]
|
||||
fn ellipsis_type() -> anyhow::Result<()> {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
@@ -5352,7 +5246,8 @@ mod tests {
|
||||
",
|
||||
)?;
|
||||
|
||||
assert_public_ty(&db, "src/a.py", "x", expected_type);
|
||||
// TODO: sys.version_info
|
||||
assert_public_ty(&db, "src/a.py", "x", "EllipsisType | ellipsis");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5986,17 +5881,7 @@ mod tests {
|
||||
|
||||
// We currently return `Todo` for all async comprehensions,
|
||||
// including comprehensions that have invalid syntax
|
||||
assert_scope_ty(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&["foo", "<listcomp>"],
|
||||
"x",
|
||||
if cfg!(debug_assertions) {
|
||||
"@Todo(async iterables/iterators)"
|
||||
} else {
|
||||
"@Todo"
|
||||
},
|
||||
);
|
||||
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -6020,17 +5905,7 @@ mod tests {
|
||||
)?;
|
||||
|
||||
// TODO async iterables/iterators! --Alex
|
||||
assert_scope_ty(
|
||||
&db,
|
||||
"src/a.py",
|
||||
&["foo", "<listcomp>"],
|
||||
"x",
|
||||
if cfg!(debug_assertions) {
|
||||
"@Todo(async iterables/iterators)"
|
||||
} else {
|
||||
"@Todo"
|
||||
},
|
||||
);
|
||||
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -6064,72 +5939,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pep695_type_params() {
|
||||
let mut db = setup_db();
|
||||
|
||||
db.write_dedented(
|
||||
"src/a.py",
|
||||
"
|
||||
def f[T, U: A, V: (A, B), W = A, X: A = A1, Y: (int,)]():
|
||||
pass
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class A1(A): ...
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let check_typevar = |var: &'static str,
|
||||
upper_bound: Option<&'static str>,
|
||||
constraints: Option<&[&'static str]>,
|
||||
default: Option<&'static str>| {
|
||||
let var_ty = get_symbol(&db, "src/a.py", &["f"], var).expect_type();
|
||||
assert_eq!(var_ty.display(&db).to_string(), var);
|
||||
|
||||
let expected_name_ty = format!(r#"Literal["{var}"]"#);
|
||||
let name_ty = var_ty.member(&db, "__name__").expect_type();
|
||||
assert_eq!(name_ty.display(&db).to_string(), expected_name_ty);
|
||||
|
||||
let KnownInstanceType::TypeVar(typevar) = var_ty.expect_known_instance() else {
|
||||
panic!("expected TypeVar");
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
typevar
|
||||
.upper_bound(&db)
|
||||
.map(|ty| ty.display(&db).to_string()),
|
||||
upper_bound.map(std::borrow::ToOwned::to_owned)
|
||||
);
|
||||
assert_eq!(
|
||||
typevar.constraints(&db).map(|tys| tys
|
||||
.iter()
|
||||
.map(|ty| ty.display(&db).to_string())
|
||||
.collect::<Vec<_>>()),
|
||||
constraints.map(|strings| strings
|
||||
.iter()
|
||||
.map(std::string::ToString::to_string)
|
||||
.collect::<Vec<_>>())
|
||||
);
|
||||
assert_eq!(
|
||||
typevar
|
||||
.default_ty(&db)
|
||||
.map(|ty| ty.display(&db).to_string()),
|
||||
default.map(std::borrow::ToOwned::to_owned)
|
||||
);
|
||||
};
|
||||
|
||||
check_typevar("T", None, None, None);
|
||||
check_typevar("U", Some("A"), None, None);
|
||||
check_typevar("V", None, Some(&["A", "B"]), None);
|
||||
check_typevar("W", None, None, Some("A"));
|
||||
check_typevar("X", Some("A"), None, Some("A1"));
|
||||
|
||||
// a typevar with less than two constraints is treated as unconstrained
|
||||
check_typevar("Y", None, None, None);
|
||||
}
|
||||
|
||||
// Incremental inference tests
|
||||
|
||||
fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
|
||||
|
||||
@@ -5,7 +5,7 @@ use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
|
||||
use crate::{types::todo_type, Db};
|
||||
use crate::Db;
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
///
|
||||
@@ -354,7 +354,7 @@ impl<'db> ClassBase<'db> {
|
||||
match ty {
|
||||
Type::Any => Some(Self::Any),
|
||||
Type::Unknown => Some(Self::Unknown),
|
||||
Type::Todo(_) => Some(Self::Todo),
|
||||
Type::Todo => Some(Self::Todo),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)),
|
||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
||||
Type::Intersection(_) => None, // TODO -- probably incorrect?
|
||||
@@ -372,11 +372,7 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::SubclassOf(_) => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::TypeVar(_)
|
||||
| KnownInstanceType::TypeAliasType(_)
|
||||
| KnownInstanceType::Literal
|
||||
| KnownInstanceType::Union
|
||||
| KnownInstanceType::NoReturn
|
||||
| KnownInstanceType::Never
|
||||
| KnownInstanceType::Optional => None,
|
||||
},
|
||||
}
|
||||
@@ -409,7 +405,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
fn from(value: ClassBase<'db>) -> Self {
|
||||
match value {
|
||||
ClassBase::Any => Type::Any,
|
||||
ClassBase::Todo => todo_type!(),
|
||||
ClassBase::Todo => Type::Todo,
|
||||
ClassBase::Unknown => Type::Unknown,
|
||||
ClassBase::Class(class) => Type::class_literal(class),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
use super::{definition_expression_ty, Type};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::Db;
|
||||
use crate::{semantic_index::definition::Definition, types::todo_type};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
||||
/// A typed callable signature.
|
||||
@@ -18,7 +18,7 @@ impl<'db> Signature<'db> {
|
||||
pub(crate) fn todo() -> Self {
|
||||
Self {
|
||||
parameters: Parameters::todo(),
|
||||
return_ty: todo_type!("return type"),
|
||||
return_ty: Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ impl<'db> Signature<'db> {
|
||||
.as_ref()
|
||||
.map(|returns| {
|
||||
if function_node.is_async {
|
||||
todo_type!("generic types.CoroutineType")
|
||||
// TODO: generic `types.CoroutineType`!
|
||||
Type::Todo
|
||||
} else {
|
||||
definition_expression_ty(db, definition, returns.as_ref())
|
||||
}
|
||||
@@ -80,11 +81,11 @@ impl<'db> Parameters<'db> {
|
||||
Self {
|
||||
variadic: Some(Parameter {
|
||||
name: Some(Name::new_static("args")),
|
||||
annotated_ty: todo_type!(),
|
||||
annotated_ty: Type::Todo,
|
||||
}),
|
||||
keywords: Some(Parameter {
|
||||
name: Some(Name::new_static("kwargs")),
|
||||
annotated_ty: todo_type!(),
|
||||
annotated_ty: Type::Todo,
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{todo_type, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::types::{Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::Db;
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
@@ -59,7 +59,7 @@ impl<'db> Unpacker<'db> {
|
||||
// TODO: Combine the types into a list type. If the
|
||||
// starred_element_types is empty, then it should be `List[Any]`.
|
||||
// combine_types(starred_element_types);
|
||||
element_types.push(todo_type!("starred unpacking"));
|
||||
element_types.push(Type::Todo);
|
||||
|
||||
element_types.extend_from_slice(
|
||||
// SAFETY: Safe because of the length check above.
|
||||
@@ -72,7 +72,7 @@ impl<'db> Unpacker<'db> {
|
||||
// index.
|
||||
element_types.resize(elts.len() - 1, Type::Unknown);
|
||||
// TODO: This should be `list[Unknown]`
|
||||
element_types.insert(starred_index, todo_type!("starred unpacking"));
|
||||
element_types.insert(starred_index, Type::Todo);
|
||||
Cow::Owned(element_types)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -180,16 +180,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Discard `@Todo`-type metadata from expected types, which is not available
|
||||
/// when running in release mode.
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn discard_todo_metadata(ty: &str) -> std::borrow::Cow<'_, str> {
|
||||
static TODO_METADATA_REGEX: std::sync::LazyLock<regex::Regex> =
|
||||
std::sync::LazyLock::new(|| regex::Regex::new(r"@Todo\([^)]*\)").unwrap());
|
||||
|
||||
TODO_METADATA_REGEX.replace_all(ty, "@Todo")
|
||||
}
|
||||
|
||||
struct Matcher {
|
||||
line_index: LineIndex,
|
||||
source: SourceText,
|
||||
@@ -286,9 +276,6 @@ impl Matcher {
|
||||
}
|
||||
}
|
||||
Assertion::Revealed(expected_type) => {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let expected_type = discard_todo_metadata(&expected_type);
|
||||
|
||||
let mut matched_revealed_type = None;
|
||||
let mut matched_undefined_reveal = None;
|
||||
let expected_reveal_type_message = format!("Revealed type is `{expected_type}`");
|
||||
|
||||
@@ -185,11 +185,11 @@ impl Settings {
|
||||
pub enum TargetVersion {
|
||||
Py37,
|
||||
Py38,
|
||||
#[default]
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
Py312,
|
||||
#[default]
|
||||
Py313,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
while True:
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
break
|
||||
@@ -1,6 +0,0 @@
|
||||
while True:
|
||||
|
||||
def b():
|
||||
x: int
|
||||
|
||||
break
|
||||
@@ -1,6 +0,0 @@
|
||||
for _ in range(1):
|
||||
|
||||
class A:
|
||||
x: int
|
||||
|
||||
break
|
||||
@@ -1,6 +0,0 @@
|
||||
for _ in range(1):
|
||||
|
||||
def b():
|
||||
x: int
|
||||
|
||||
break
|
||||
@@ -1 +0,0 @@
|
||||
../../../../ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py
|
||||
@@ -1 +0,0 @@
|
||||
x: f"Literal[{1 + 2}]" = 3
|
||||
@@ -1,3 +0,0 @@
|
||||
from typing import Union
|
||||
|
||||
x: Union[int, str] = 1
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: "&workspace"
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -23,7 +24,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -23,7 +24,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -23,7 +24,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -23,7 +24,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -36,7 +37,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
source: crates/red_knot_workspace/src/workspace/metadata.rs
|
||||
expression: workspace
|
||||
snapshot_kind: text
|
||||
---
|
||||
WorkspaceMetadata(
|
||||
root: "/app",
|
||||
@@ -49,7 +50,7 @@ WorkspaceMetadata(
|
||||
program: ProgramSettings(
|
||||
target_version: PythonVersion(
|
||||
major: 3,
|
||||
minor: 13,
|
||||
minor: 9,
|
||||
),
|
||||
search_paths: SearchPathSettings(
|
||||
extra_paths: [],
|
||||
|
||||
@@ -264,15 +264,26 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
}
|
||||
|
||||
/// Whether or not the .py/.pyi version of this file is expected to fail
|
||||
#[rustfmt::skip]
|
||||
const KNOWN_FAILURES: &[(&str, bool, bool)] = &[
|
||||
// related to circular references in class definitions
|
||||
// Probably related to missing support for type aliases / type params:
|
||||
("crates/ruff_python_parser/resources/inline/err/type_param_invalid_bound_expr.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/err/type_param_param_spec_invalid_default_expr.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_invalid_default_expr.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_missing_default.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/err/type_param_type_var_tuple_invalid_default_expr.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/ok/type_param_param_spec.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var_tuple.py", true, true),
|
||||
("crates/ruff_python_parser/resources/inline/ok/type_param_type_var.py", true, true),
|
||||
("crates/ruff_python_parser/resources/valid/statement/type.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TCH004_15.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F401_19.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_17.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_20.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_26.py", true, false),
|
||||
// Fails for unknown reasons:
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F632.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F811_19.py", true, false),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyupgrade/UP039.py", true, false),
|
||||
// related to circular references in type aliases (salsa cycle panic):
|
||||
("crates/ruff_python_parser/resources/inline/err/type_alias_invalid_value_expr.py", true, true),
|
||||
// related to circular references in f-string annotations (invalid syntax)
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_15.py", true, true),
|
||||
("crates/ruff_linter/resources/test/fixtures/pyflakes/F821_14.py", false, true),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -840,7 +840,7 @@ fn stdin_multiple_parse_error() {
|
||||
|
||||
#[test]
|
||||
fn parse_error_not_included() {
|
||||
// Parse errors are always shown
|
||||
// Select any rule except for `E999`, syntax error should still be shown.
|
||||
let mut cmd = RuffCheck::default().args(["--select=I"]).build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("foo =\n"), @r"
|
||||
@@ -859,6 +859,27 @@ fn parse_error_not_included() {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deprecated_parse_error_selection() {
|
||||
let mut cmd = RuffCheck::default().args(["--select=E999"]).build();
|
||||
assert_cmd_snapshot!(cmd
|
||||
.pass_stdin("foo =\n"), @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
-:1:6: SyntaxError: Expected an expression
|
||||
|
|
||||
1 | foo =
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 error.
|
||||
|
||||
----- stderr -----
|
||||
warning: Rule `E999` is deprecated and will be removed in a future release. Syntax errors will always be shown regardless of whether this rule is selected or not.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_output_preview() {
|
||||
let mut cmd = RuffCheck::default().args(["--preview"]).build();
|
||||
@@ -1229,68 +1250,6 @@ fn removed_indirect() {
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_ignore_direct() {
|
||||
let mut cmd = RuffCheck::default().args(["--ignore", "UP027"]).build();
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules have been removed and ignoring them has no effect:
|
||||
- UP027
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_ignore_multiple_direct() {
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--ignore", "UP027", "--ignore", "PLR1706"])
|
||||
.build();
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules have been removed and ignoring them has no effect:
|
||||
- PLR1706
|
||||
- UP027
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_ignore_remapped_direct() {
|
||||
let mut cmd = RuffCheck::default().args(["--ignore", "PGH001"]).build();
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `PGH001` has been remapped to `S307`.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_ignore_indirect() {
|
||||
// `PLR170` includes removed rules but should not select or warn
|
||||
// since it is not a "direct" selection
|
||||
let mut cmd = RuffCheck::default().args(["--ignore", "PLR170"]).build();
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redirect_direct() {
|
||||
// Selection of a redirected rule directly should use the new rule and warn
|
||||
|
||||
@@ -1966,75 +1966,3 @@ fn nested_implicit_namespace_package() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_import_convention_invalid_aliases_config_alias_name() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.flake8-import-conventions.aliases]
|
||||
"module.name" = "invalid.alias"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Failed to parse [TMP]/ruff.toml
|
||||
Cause: TOML parse error at line 2, column 2
|
||||
|
|
||||
2 | [lint.flake8-import-conventions.aliases]
|
||||
| ^^^^
|
||||
invalid value: string "invalid.alias", expected a Python identifier
|
||||
"###);});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flake8_import_convention_invalid_aliases_config_module_name() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint.flake8-import-conventions.aliases]
|
||||
"module..invalid" = "alias"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
insta::with_settings!({
|
||||
filters => vec![(tempdir_filter(&tempdir).as_str(), "[TMP]/")]
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--config")
|
||||
.arg(&ruff_toml)
|
||||
, @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
ruff failed
|
||||
Cause: Failed to parse [TMP]/ruff.toml
|
||||
Cause: TOML parse error at line 2, column 2
|
||||
|
|
||||
2 | [lint.flake8-import-conventions.aliases]
|
||||
| ^^^^
|
||||
invalid value: string "module..invalid", expected a sequence of Python identifiers delimited by periods
|
||||
"###);});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ linter.rules.enabled = [
|
||||
ambiguous-class-name (E742),
|
||||
ambiguous-function-name (E743),
|
||||
io-error (E902),
|
||||
syntax-error (E999),
|
||||
unused-import (F401),
|
||||
import-shadowed-by-loop-var (F402),
|
||||
undefined-local-with-import-star (F403),
|
||||
@@ -149,6 +150,7 @@ linter.rules.should_fix = [
|
||||
ambiguous-class-name (E742),
|
||||
ambiguous-function-name (E743),
|
||||
io-error (E902),
|
||||
syntax-error (E999),
|
||||
unused-import (F401),
|
||||
import-shadowed-by-loop-var (F402),
|
||||
undefined-local-with-import-star (F403),
|
||||
|
||||
@@ -2,7 +2,7 @@ pub use diagnostic::{Diagnostic, DiagnosticKind};
|
||||
pub use edit::Edit;
|
||||
pub use fix::{Applicability, Fix, IsolationLevel};
|
||||
pub use source_map::{SourceMap, SourceMarker};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata};
|
||||
pub use violation::{AlwaysFixableViolation, FixAvailability, Violation};
|
||||
|
||||
mod diagnostic;
|
||||
mod edit;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::DiagnosticKind;
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
@@ -18,16 +17,7 @@ impl Display for FixAvailability {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ViolationMetadata {
|
||||
/// Returns the rule name of this violation
|
||||
fn rule_name() -> &'static str;
|
||||
|
||||
/// Returns an explanation of what this violation catches,
|
||||
/// why it's bad, and what users should do instead.
|
||||
fn explain() -> Option<&'static str>;
|
||||
}
|
||||
|
||||
pub trait Violation: ViolationMetadata {
|
||||
pub trait Violation: Debug + PartialEq + Eq {
|
||||
/// `None` in the case a fix is never available or otherwise Some
|
||||
/// [`FixAvailability`] describing the available fix.
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
|
||||
@@ -51,7 +41,7 @@ pub trait Violation: ViolationMetadata {
|
||||
|
||||
/// This trait exists just to make implementing the [`Violation`] trait more
|
||||
/// convenient for violations that can always be fixed.
|
||||
pub trait AlwaysFixableViolation: ViolationMetadata {
|
||||
pub trait AlwaysFixableViolation: Debug + PartialEq + Eq {
|
||||
/// The message used to describe the violation.
|
||||
fn message(&self) -> String;
|
||||
|
||||
@@ -79,16 +69,3 @@ impl<V: AlwaysFixableViolation> Violation for V {
|
||||
<Self as AlwaysFixableViolation>::message_formats()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for DiagnosticKind
|
||||
where
|
||||
T: Violation,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
body: Violation::message(&value),
|
||||
suggestion: Violation::fix_title(&value),
|
||||
name: T::rule_name().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::{write, Arguments, FormatElement};
|
||||
use crate::format_element::Interned;
|
||||
use crate::prelude::{LineMode, Tag};
|
||||
use crate::prelude::LineMode;
|
||||
use crate::{FormatResult, FormatState};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// A trait for writing or formatting into [`FormatElement`]-accepting buffers or streams.
|
||||
@@ -295,11 +294,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A Buffer that removes any soft line breaks or [`if_group_breaks`](crate::builders::if_group_breaks) elements.
|
||||
/// A Buffer that removes any soft line breaks.
|
||||
///
|
||||
/// - Removes [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::Soft).
|
||||
/// - Replaces [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::SoftOrSpace) with a [`Space`](FormatElement::Space)
|
||||
/// - Removes [`if_group_breaks`](crate::builders::if_group_breaks) elements.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@@ -352,8 +350,6 @@ pub struct RemoveSoftLinesBuffer<'a, Context> {
|
||||
/// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements
|
||||
/// that are now unused. But there's little harm in that and the cache is cleaned when dropping the buffer.
|
||||
interned_cache: FxHashMap<Interned, Interned>,
|
||||
|
||||
state: RemoveSoftLineBreaksState,
|
||||
}
|
||||
|
||||
impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
|
||||
@@ -361,7 +357,6 @@ impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> {
|
||||
pub fn new(inner: &'a mut dyn Buffer<Context = Context>) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
state: RemoveSoftLineBreaksState::default(),
|
||||
interned_cache: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
@@ -380,8 +375,6 @@ fn clean_interned(
|
||||
if let Some(cleaned) = interned_cache.get(interned) {
|
||||
cleaned.clone()
|
||||
} else {
|
||||
let mut state = RemoveSoftLineBreaksState::default();
|
||||
|
||||
// Find the first soft line break element or interned element that must be changed
|
||||
let result = interned
|
||||
.iter()
|
||||
@@ -389,9 +382,8 @@ fn clean_interned(
|
||||
.find_map(|(index, element)| match element {
|
||||
FormatElement::Line(LineMode::Soft | LineMode::SoftOrSpace) => {
|
||||
let mut cleaned = Vec::new();
|
||||
let (before, after) = interned.split_at(index);
|
||||
cleaned.extend_from_slice(before);
|
||||
Some((cleaned, &after[1..]))
|
||||
cleaned.extend_from_slice(&interned[..index]);
|
||||
Some((cleaned, &interned[index..]))
|
||||
}
|
||||
FormatElement::Interned(inner) => {
|
||||
let cleaned_inner = clean_interned(inner, interned_cache);
|
||||
@@ -406,33 +398,19 @@ fn clean_interned(
|
||||
}
|
||||
}
|
||||
|
||||
element => {
|
||||
if state.should_drop(element) {
|
||||
let mut cleaned = Vec::new();
|
||||
let (before, after) = interned.split_at(index);
|
||||
cleaned.extend_from_slice(before);
|
||||
Some((cleaned, &after[1..]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let result = match result {
|
||||
// Copy the whole interned buffer so that becomes possible to change the necessary elements.
|
||||
Some((mut cleaned, rest)) => {
|
||||
for element in rest {
|
||||
if state.should_drop(element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let element = match element {
|
||||
FormatElement::Line(LineMode::Soft) => continue,
|
||||
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
|
||||
FormatElement::Interned(interned) => {
|
||||
FormatElement::Interned(clean_interned(interned, interned_cache))
|
||||
}
|
||||
|
||||
element => element.clone(),
|
||||
};
|
||||
cleaned.push(element);
|
||||
@@ -453,17 +431,12 @@ impl<Context> Buffer for RemoveSoftLinesBuffer<'_, Context> {
|
||||
type Context = Context;
|
||||
|
||||
fn write_element(&mut self, element: FormatElement) {
|
||||
if self.state.should_drop(&element) {
|
||||
return;
|
||||
}
|
||||
|
||||
let element = match element {
|
||||
FormatElement::Line(LineMode::Soft) => return,
|
||||
FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space,
|
||||
FormatElement::Interned(interned) => {
|
||||
FormatElement::Interned(self.clean_interned(&interned))
|
||||
}
|
||||
|
||||
element => element,
|
||||
};
|
||||
|
||||
@@ -483,77 +456,14 @@ impl<Context> Buffer for RemoveSoftLinesBuffer<'_, Context> {
|
||||
}
|
||||
|
||||
fn snapshot(&self) -> BufferSnapshot {
|
||||
BufferSnapshot::Any(Box::new(RemoveSoftLinebreaksSnapshot {
|
||||
inner: self.inner.snapshot(),
|
||||
state: self.state,
|
||||
}))
|
||||
self.inner.snapshot()
|
||||
}
|
||||
|
||||
fn restore_snapshot(&mut self, snapshot: BufferSnapshot) {
|
||||
let RemoveSoftLinebreaksSnapshot { inner, state } = snapshot.unwrap_any();
|
||||
self.inner.restore_snapshot(inner);
|
||||
self.state = state;
|
||||
self.inner.restore_snapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
enum RemoveSoftLineBreaksState {
|
||||
#[default]
|
||||
Default,
|
||||
InIfGroupBreaks {
|
||||
conditional_content_level: NonZeroUsize,
|
||||
},
|
||||
}
|
||||
|
||||
impl RemoveSoftLineBreaksState {
|
||||
fn should_drop(&mut self, element: &FormatElement) -> bool {
|
||||
match self {
|
||||
Self::Default => {
|
||||
// Entered the start of an `if_group_breaks`
|
||||
if let FormatElement::Tag(Tag::StartConditionalContent(condition)) = element {
|
||||
if condition.mode.is_expanded() {
|
||||
*self = Self::InIfGroupBreaks {
|
||||
conditional_content_level: NonZeroUsize::new(1).unwrap(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Self::InIfGroupBreaks {
|
||||
conditional_content_level,
|
||||
} => {
|
||||
match element {
|
||||
// A nested `if_group_breaks` or `if_group_fits`
|
||||
FormatElement::Tag(Tag::StartConditionalContent(_)) => {
|
||||
*conditional_content_level = conditional_content_level.saturating_add(1);
|
||||
}
|
||||
// The end of an `if_group_breaks` or `if_group_fits`.
|
||||
FormatElement::Tag(Tag::EndConditionalContent) => {
|
||||
if let Some(level) = NonZeroUsize::new(conditional_content_level.get() - 1)
|
||||
{
|
||||
*conditional_content_level = level;
|
||||
} else {
|
||||
// Found the end tag of the initial `if_group_breaks`. Skip this element but retain
|
||||
// the elements coming after
|
||||
*self = RemoveSoftLineBreaksState::Default;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RemoveSoftLinebreaksSnapshot {
|
||||
inner: BufferSnapshot,
|
||||
state: RemoveSoftLineBreaksState,
|
||||
}
|
||||
|
||||
pub trait BufferExtensions: Buffer + Sized {
|
||||
/// Returns a new buffer that calls the passed inspector for every element that gets written to the output
|
||||
#[must_use]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.1"
|
||||
version = "0.7.4"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from airflow import DAG, dag
|
||||
|
||||
DAG(dag_id="class_default_schedule")
|
||||
|
||||
DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
|
||||
|
||||
@dag()
|
||||
def decorator_default_schedule():
|
||||
pass
|
||||
|
||||
|
||||
@dag(schedule="0 * * * *")
|
||||
def decorator_schedule():
|
||||
pass
|
||||
@@ -71,7 +71,7 @@ class Foo:
|
||||
def foo(self: "Foo", a: int, b: int) -> int:
|
||||
pass
|
||||
|
||||
# OK
|
||||
# ANN101
|
||||
def foo(self, a: int, b: int) -> int:
|
||||
pass
|
||||
|
||||
@@ -125,12 +125,12 @@ class Foo:
|
||||
def foo(cls: Type["Foo"], a: int, b: int) -> int:
|
||||
pass
|
||||
|
||||
# OK
|
||||
# ANN102
|
||||
@classmethod
|
||||
def foo(cls, a: int, b: int) -> int:
|
||||
pass
|
||||
|
||||
# OK
|
||||
# ANN101
|
||||
def foo(self, /, a: int, b: int) -> int:
|
||||
pass
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ ContextVar("cv", default=frozenset())
|
||||
ContextVar("cv", default=MappingProxyType({}))
|
||||
ContextVar("cv", default=re.compile("foo"))
|
||||
ContextVar("cv", default=float(1))
|
||||
ContextVar("cv", default=frozenset[str]())
|
||||
ContextVar[frozenset[str]]("cv", default=frozenset[str]())
|
||||
|
||||
# Bad
|
||||
ContextVar("cv", default=[])
|
||||
@@ -27,8 +25,6 @@ ContextVar("cv", default=[char for char in "foo"])
|
||||
ContextVar("cv", default={char for char in "foo"})
|
||||
ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
|
||||
ContextVar("cv", default=collections.deque())
|
||||
ContextVar("cv", default=set[str]())
|
||||
ContextVar[set[str]]("cv", default=set[str]())
|
||||
|
||||
def bar() -> list[int]:
|
||||
return [1, 2, 3]
|
||||
|
||||
@@ -84,27 +84,3 @@ field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union me
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with mixed `typing.Union` and `|`
|
||||
field28: typing.Union[int | int] # Error
|
||||
|
||||
# Should emit twice in cases with multiple nested `typing.Union`
|
||||
field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error
|
||||
|
||||
# Should emit once in cases with multiple nested `typing.Union`
|
||||
field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error
|
||||
|
||||
# Should emit once, and fix to `typing.Union[float, int]`
|
||||
field31: typing.Union[float, typing.Union[int | int]] # Error
|
||||
|
||||
# Should emit once, and fix to `typing.Union[float, int]`
|
||||
field32: typing.Union[float, typing.Union[int | int | int]] # Error
|
||||
|
||||
# Test case for mixed union type fix
|
||||
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
|
||||
|
||||
@@ -84,27 +84,3 @@ field24: typing.Union[int, typing.Union[int, int]] # PYI016: Duplicate union me
|
||||
# duplicates of the outer `int`), but not three times (which would indicate that
|
||||
# we incorrectly re-checked the nested union).
|
||||
field25: typing.Union[int, int | int] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field26: typing.Union[typing.Union[int, int]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with nested `typing.Union`
|
||||
field27: typing.Union[typing.Union[typing.Union[int, int]]] # PYI016: Duplicate union member `int`
|
||||
|
||||
# Should emit in cases with mixed `typing.Union` and `|`
|
||||
field28: typing.Union[int | int] # Error
|
||||
|
||||
# Should emit twice in cases with multiple nested `typing.Union`
|
||||
field29: typing.Union[int, typing.Union[typing.Union[int, int]]] # Error
|
||||
|
||||
# Should emit once in cases with multiple nested `typing.Union`
|
||||
field30: typing.Union[int, typing.Union[typing.Union[int, str]]] # Error
|
||||
|
||||
# Should emit once, and fix to `typing.Union[float, int]`
|
||||
field31: typing.Union[float, typing.Union[int | int]] # Error
|
||||
|
||||
# Should emit once, and fix to `typing.Union[float, int]`
|
||||
field32: typing.Union[float, typing.Union[int | int | int]] # Error
|
||||
|
||||
# Test case for mixed union type fix
|
||||
field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
|
||||
@@ -39,30 +39,14 @@ async def f4(**kwargs: int | int | float) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f5(arg1: int, *args: Union[int, int, float]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None:
|
||||
...
|
||||
|
||||
|
||||
def f9(
|
||||
def f5(
|
||||
arg: Union[ # comment
|
||||
float, # another
|
||||
complex, int]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
def f10(
|
||||
def f6(
|
||||
arg: (
|
||||
int | # comment
|
||||
float | # another
|
||||
|
||||
@@ -46,18 +46,6 @@ def f6(
|
||||
)
|
||||
) -> None: ... # PYI041
|
||||
|
||||
def f5(arg1: int, *args: Union[int, int, float]) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f6(arg1: int, *args: Union[Union[int, int, float]]) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f7(arg1: int, *args: Union[Union[Union[int, int, float]]]) -> None: ... # PYI041
|
||||
|
||||
|
||||
def f8(arg1: int, *args: Union[Union[Union[int | int | float]]]) -> None: ... # PYI041
|
||||
|
||||
|
||||
class Foo:
|
||||
def good(self, arg: int) -> None: ...
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
|
||||
F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
||||
G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ A: str | Literal["foo"]
|
||||
B: TypeAlias = typing.Union[Literal[b"bar", b"foo"], bytes, str]
|
||||
C: TypeAlias = typing.Union[Literal[5], int, typing.Union[Literal["foo"], str]]
|
||||
D: TypeAlias = typing.Union[Literal[b"str_bytes", 42], bytes, int]
|
||||
E: TypeAlias = typing.Union[typing.Union[typing.Union[typing.Union[Literal["foo"], str]]]]
|
||||
F: TypeAlias = typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
||||
G: typing.Union[str, typing.Union[typing.Union[typing.Union[Literal["foo"], int]]]]
|
||||
|
||||
def func(x: complex | Literal[1J], y: Union[Literal[3.14], float]): ...
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
t: type[int] | type[str] | type[float]
|
||||
u: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
v: Union[type[float], type[complex]]
|
||||
w: Union[type[float, int], type[complex]]
|
||||
x: Union[Union[type[float, int], type[complex]]]
|
||||
y: Union[Union[Union[type[float, int], type[complex]]]]
|
||||
z: Union[type[complex], Union[Union[type[float, int]]]]
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None:
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import builtins
|
||||
from typing import Union
|
||||
|
||||
s: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
t: type[int] | type[str] | type[float]
|
||||
u: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
v: Union[type[float], type[complex]]
|
||||
w: Union[type[float, int], type[complex]]
|
||||
x: Union[Union[type[float, int], type[complex]]]
|
||||
y: Union[Union[Union[type[float, int], type[complex]]]]
|
||||
z: Union[type[complex], Union[Union[type[float, int]]]]
|
||||
w: builtins.type[int] | builtins.type[str] | builtins.type[complex]
|
||||
x: type[int] | type[str] | type[float]
|
||||
y: builtins.type[int] | type[str] | builtins.type[complex]
|
||||
z: Union[type[float], type[complex]]
|
||||
z: Union[type[float, int], type[complex]]
|
||||
|
||||
def func(arg: type[int] | str | type[float]) -> None: ...
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Literal, Union
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def func1(arg1: Literal[None]):
|
||||
@@ -17,7 +17,7 @@ def func4(arg1: Literal[int, None, float]):
|
||||
...
|
||||
|
||||
|
||||
def func5(arg1: Literal[None, None]):
|
||||
def func5(arg1: Literal[None, None]):
|
||||
...
|
||||
|
||||
|
||||
@@ -25,21 +25,13 @@ def func6(arg1: Literal[
|
||||
"hello",
|
||||
None # Comment 1
|
||||
, "world"
|
||||
]):
|
||||
]):
|
||||
...
|
||||
|
||||
|
||||
def func7(arg1: Literal[
|
||||
None # Comment 1
|
||||
]):
|
||||
...
|
||||
|
||||
|
||||
def func8(arg1: Literal[None] | None):
|
||||
...
|
||||
|
||||
|
||||
def func9(arg1: Union[Literal[None], None]):
|
||||
]):
|
||||
...
|
||||
|
||||
|
||||
@@ -66,16 +58,3 @@ Literal[1, None, "foo", None] # Y061 None inside "Literal[]" expression. Replac
|
||||
# and there are no None members in the Literal[] slice,
|
||||
# only emit Y062:
|
||||
Literal[None, True, None, True] # Y062 Duplicate "Literal[]" member "True"
|
||||
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/14567
|
||||
x: Literal[None] | None
|
||||
y: None | Literal[None]
|
||||
z: Union[Literal[None], None]
|
||||
|
||||
a: int | Literal[None] | None
|
||||
b: None | Literal[None] | None
|
||||
c: (None | Literal[None]) | None
|
||||
d: None | (Literal[None] | None)
|
||||
e: None | ((None | Literal[None]) | None) | None
|
||||
f: Literal[None] | Literal[None]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Literal, Union
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def func1(arg1: Literal[None]): ...
|
||||
@@ -28,12 +28,6 @@ def func7(arg1: Literal[
|
||||
]): ...
|
||||
|
||||
|
||||
def func8(arg1: Literal[None] | None):...
|
||||
|
||||
|
||||
def func9(arg1: Union[Literal[None], None]): ...
|
||||
|
||||
|
||||
# OK
|
||||
def good_func(arg1: Literal[int] | None): ...
|
||||
|
||||
@@ -41,16 +35,3 @@ def good_func(arg1: Literal[int] | None): ...
|
||||
# From flake8-pyi
|
||||
Literal[None] # PYI061 None inside "Literal[]" expression. Replace with "None"
|
||||
Literal[True, None] # PYI061 None inside "Literal[]" expression. Replace with "Literal[True] | None"
|
||||
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/14567
|
||||
x: Literal[None] | None
|
||||
y: None | Literal[None]
|
||||
z: Union[Literal[None], None]
|
||||
|
||||
a: int | Literal[None] | None
|
||||
b: None | Literal[None] | None
|
||||
c: (None | Literal[None]) | None
|
||||
d: None | (Literal[None] | None)
|
||||
e: None | ((None | Literal[None]) | None) | None
|
||||
f: Literal[None] | Literal[None]
|
||||
|
||||
@@ -25,9 +25,3 @@ Literal[
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
||||
|
||||
# nested literals, all equivalent to `Literal[1]`
|
||||
Literal[Literal[1]] # no duplicate
|
||||
Literal[Literal[Literal[1], Literal[1]]] # once
|
||||
Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
|
||||
|
||||
@@ -25,9 +25,3 @@ Literal[
|
||||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
||||
|
||||
# nested literals, all equivalent to `Literal[1]`
|
||||
Literal[Literal[1]] # no duplicate
|
||||
Literal[Literal[Literal[1], Literal[1]]] # once
|
||||
Literal[Literal[1], Literal[Literal[Literal[1]]]] # once
|
||||
|
||||
58
crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT004.py
vendored
Normal file
58
crates/ruff_linter/resources/test/fixtures/flake8_pytest_style/PT004.py
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import abc
|
||||
from abc import abstractmethod
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def _patch_something(mocker): # OK simple
|
||||
mocker.patch("some.thing")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def _patch_something(mocker): # OK with return
|
||||
if something:
|
||||
return
|
||||
mocker.patch("some.thing")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def _activate_context(): # OK with yield
|
||||
with context:
|
||||
yield
|
||||
|
||||
|
||||
class BaseTest:
|
||||
@pytest.fixture()
|
||||
@abc.abstractmethod
|
||||
def my_fixture(): # OK abstract with import abc
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class BaseTest:
|
||||
@pytest.fixture()
|
||||
@abstractmethod
|
||||
def my_fixture(): # OK abstract with from import
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def my_fixture(): # OK ignoring yield from
|
||||
yield from some_generator()
|
||||
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def my_fixture(): # OK ignoring yield value
|
||||
yield 1
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def patch_something(mocker): # Error simple
|
||||
mocker.patch("some.thing")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def activate_context(): # Error with yield
|
||||
with context:
|
||||
yield
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user