Compare commits

..

4 Commits

Author SHA1 Message Date
Carl Meyer
d91bb060f1 BDD 2024-12-20 09:00:43 -08:00
Carl Meyer
bf0918d72f dot file 2024-12-19 11:13:28 -08:00
Carl Meyer
6870d2c53e use published version of crate 2024-12-18 09:05:47 -08:00
Carl Meyer
54561192dd testing it out 2024-12-17 17:19:48 -08:00
385 changed files with 6429 additions and 23377 deletions

View File

@@ -349,7 +349,7 @@ jobs:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: astral-sh/setup-uv@v5
- uses: astral-sh/setup-uv@v4
- uses: actions/download-artifact@v4
name: Download Ruff binary to test
id: download-cached-binary
@@ -585,8 +585,7 @@ jobs:
run: |
echo '```console' > "$GITHUB_STEP_SUMMARY"
# Enable color output for pre-commit and remove it for the summary
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
SKIP=cargo-fmt,clippy,dev-generate-all pre-commit run --all-files --show-diff-on-failure --color=always | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
exit_code="${PIPESTATUS[0]}"
echo '```' >> "$GITHUB_STEP_SUMMARY"
@@ -613,7 +612,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v4
- uses: Swatinem/rust-cache@v2
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: astral-sh/setup-uv@v5
- uses: astral-sh/setup-uv@v4
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v4
- uses: actions/download-artifact@v4
with:
pattern: wheels-*

View File

@@ -2,7 +2,6 @@ fail_fast: false
exclude: |
(?x)^(
.github/workflows/release.yml|
crates/red_knot_vendored/vendor/.*|
crates/red_knot_workspace/resources/.*|
crates/ruff_linter/resources/.*|
@@ -23,7 +22,7 @@ repos:
- id: validate-pyproject
- repo: https://github.com/executablebooks/mdformat
rev: 0.7.21
rev: 0.7.19
hooks:
- id: mdformat
additional_dependencies:
@@ -59,7 +58,7 @@ repos:
- black==24.10.0
- repo: https://github.com/crate-ci/typos
rev: v1.28.4
rev: v1.28.3
hooks:
- id: typos
@@ -73,7 +72,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
rev: v0.8.3
hooks:
- id: ruff-format
- id: ruff
@@ -91,9 +90,12 @@ repos:
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v0.10.0
rev: v0.9.2
hooks:
- id: zizmor
# `release.yml` is autogenerated by `dist`; security issues need to be fixed there
# (https://opensource.axo.dev/cargo-dist/)
exclude: .github/workflows/release.yml
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.30.0
@@ -103,14 +105,12 @@ repos:
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
- repo: https://github.com/rhysd/actionlint
rev: v1.7.5
rev: v1.7.4
hooks:
- id: actionlint
stages:
# This hook is disabled by default, since it's quite slow.
# To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
- manual
# `release.yml` is autogenerated by `dist`; issues need to be fixed there
# (https://opensource.axo.dev/cargo-dist/)
exclude: .github/workflows/release.yml
args:
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck
- "-ignore=SC2016" # another shellcheck lint: seems to have false positives?

View File

@@ -1,74 +1,5 @@
# Changelog
## 0.8.5
### Preview features
- \[`airflow`\] Extend names moved from core to provider (`AIR303`) ([#15145](https://github.com/astral-sh/ruff/pull/15145), [#15159](https://github.com/astral-sh/ruff/pull/15159), [#15196](https://github.com/astral-sh/ruff/pull/15196), [#15216](https://github.com/astral-sh/ruff/pull/15216))
- \[`airflow`\] Extend rule to check class attributes, methods, arguments (`AIR302`) ([#15054](https://github.com/astral-sh/ruff/pull/15054), [#15083](https://github.com/astral-sh/ruff/pull/15083))
- \[`fastapi`\] Update `FAST002` to check keyword-only arguments ([#15119](https://github.com/astral-sh/ruff/pull/15119))
- \[`flake8-type-checking`\] Disable `TC006` and `TC007` in stub files ([#15179](https://github.com/astral-sh/ruff/pull/15179))
- \[`pylint`\] Detect nested methods correctly (`PLW1641`) ([#15032](https://github.com/astral-sh/ruff/pull/15032))
- \[`ruff`\] Detect more strict-integer expressions (`RUF046`) ([#14833](https://github.com/astral-sh/ruff/pull/14833))
- \[`ruff`\] Implement `falsy-dict-get-fallback` (`RUF056`) ([#15160](https://github.com/astral-sh/ruff/pull/15160))
- \[`ruff`\] Implement `unnecessary-round` (`RUF057`) ([#14828](https://github.com/astral-sh/ruff/pull/14828))
### Rule changes
- Visit PEP 764 inline `TypedDict` keys as non-type-expressions ([#15073](https://github.com/astral-sh/ruff/pull/15073))
- \[`flake8-comprehensions`\] Skip `C416` if comprehension contains unpacking ([#14909](https://github.com/astral-sh/ruff/pull/14909))
- \[`flake8-pie`\] Allow `cast(SomeType, ...)` (`PIE796`) ([#15141](https://github.com/astral-sh/ruff/pull/15141))
- \[`flake8-simplify`\] More precise inference for dictionaries (`SIM300`) ([#15164](https://github.com/astral-sh/ruff/pull/15164))
- \[`flake8-use-pathlib`\] Catch redundant joins in `PTH201` and avoid syntax errors ([#15177](https://github.com/astral-sh/ruff/pull/15177))
- \[`pycodestyle`\] Preserve original value format (`E731`) ([#15097](https://github.com/astral-sh/ruff/pull/15097))
- \[`pydocstyle`\] Split on first whitespace character (`D403`) ([#15082](https://github.com/astral-sh/ruff/pull/15082))
- \[`pyupgrade`\] Add all PEP-585 names to `UP006` rule ([#5454](https://github.com/astral-sh/ruff/pull/5454))
### Configuration
- \[`flake8-type-checking`\] Improve flexibility of `runtime-evaluated-decorators` ([#15204](https://github.com/astral-sh/ruff/pull/15204))
- \[`pydocstyle`\] Add setting to ignore missing documentation for `*args` and `**kwargs` parameters (`D417`) ([#15210](https://github.com/astral-sh/ruff/pull/15210))
- \[`ruff`\] Add an allowlist for `unsafe-markup-use` (`RUF035`) ([#15076](https://github.com/astral-sh/ruff/pull/15076))
### Bug fixes
- Fix type subscript on older python versions ([#15090](https://github.com/astral-sh/ruff/pull/15090))
- Use `TypeChecker` for detecting `fastapi` routes ([#15093](https://github.com/astral-sh/ruff/pull/15093))
- \[`pycodestyle`\] Avoid false positives and negatives related to type parameter default syntax (`E225`, `E251`) ([#15214](https://github.com/astral-sh/ruff/pull/15214))
### Documentation
- Fix incorrect doc in `shebang-not-executable` (`EXE001`) and add git+windows solution to executable bit ([#15208](https://github.com/astral-sh/ruff/pull/15208))
- Rename rules currently not conforming to naming convention ([#15102](https://github.com/astral-sh/ruff/pull/15102))
## 0.8.4
### Preview features
- \[`airflow`\] Extend `AIR302` with additional functions and classes ([#15015](https://github.com/astral-sh/ruff/pull/15015))
- \[`airflow`\] Implement `moved-to-provider-in-3` for modules that has been moved to Airflow providers (`AIR303`) ([#14764](https://github.com/astral-sh/ruff/pull/14764))
- \[`flake8-use-pathlib`\] Extend check for invalid path suffix to include the case `"."` (`PTH210`) ([#14902](https://github.com/astral-sh/ruff/pull/14902))
- \[`perflint`\] Fix panic in `PERF401` when list variable is after the `for` loop ([#14971](https://github.com/astral-sh/ruff/pull/14971))
- \[`perflint`\] Simplify finding the loop target in `PERF401` ([#15025](https://github.com/astral-sh/ruff/pull/15025))
- \[`pylint`\] Preserve original value format (`PLR6104`) ([#14978](https://github.com/astral-sh/ruff/pull/14978))
- \[`ruff`\] Avoid false positives for `RUF027` for typing context bindings ([#15037](https://github.com/astral-sh/ruff/pull/15037))
- \[`ruff`\] Check for ambiguous pattern passed to `pytest.raises()` (`RUF043`) ([#14966](https://github.com/astral-sh/ruff/pull/14966))
### Rule changes
- \[`flake8-bandit`\] Check `S105` for annotated assignment ([#15059](https://github.com/astral-sh/ruff/pull/15059))
- \[`flake8-pyi`\] More autofixes for `redundant-none-literal` (`PYI061`) ([#14872](https://github.com/astral-sh/ruff/pull/14872))
- \[`pydocstyle`\] Skip leading whitespace for `D403` ([#14963](https://github.com/astral-sh/ruff/pull/14963))
- \[`ruff`\] Skip `SQLModel` base classes for `mutable-class-default` (`RUF012`) ([#14949](https://github.com/astral-sh/ruff/pull/14949))
### Bug
- \[`perflint`\] Parenthesize walrus expressions in autofix for `manual-list-comprehension` (`PERF401`) ([#15050](https://github.com/astral-sh/ruff/pull/15050))
### Server
- Check diagnostic refresh support from client capability which enables dynamic configuration for various editors ([#15014](https://github.com/astral-sh/ruff/pull/15014))
## 0.8.3
### Preview features

View File

@@ -467,7 +467,7 @@ cargo build --release && hyperfine --warmup 10 \
"./target/release/ruff check ./crates/ruff_linter/resources/test/cpython/ --no-cache -e --select W505,E501"
```
You can run `uv venv --project ./scripts/benchmarks`, activate the venv and then run `uv sync --project ./scripts/benchmarks` to create a working environment for the
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
above. All reported benchmarks were computed using the versions specified by
`./scripts/benchmarks/pyproject.toml` on Python 3.11.

434
Cargo.lock generated
View File

@@ -30,6 +30,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -117,9 +123,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.95"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]]
name = "append-only-vec"
@@ -203,6 +209,18 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -413,7 +431,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -470,14 +488,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "compact_str"
version = "0.8.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644"
dependencies = [
"castaway",
"cfg-if",
@@ -693,7 +711,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -704,7 +722,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -774,7 +792,7 @@ dependencies = [
"glob",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -826,7 +844,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -835,6 +853,15 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "document-features"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
dependencies = [
"litrs",
]
[[package]]
name = "drop_bomb"
version = "0.1.5"
@@ -877,9 +904,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.6"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
@@ -952,6 +979,15 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"spin",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -985,6 +1021,12 @@ dependencies = [
"libc",
]
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -1019,9 +1061,9 @@ dependencies = [
[[package]]
name = "glob"
version = "0.3.2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
@@ -1102,6 +1144,17 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "hugealloc"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "542733215a252c27c674e5d875a910d7de509cb74123d0bdbaa050f871ec84c2"
dependencies = [
"allocator-api2",
"libc",
"sptr",
]
[[package]]
name = "humantime"
version = "2.1.0"
@@ -1246,7 +1299,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -1420,7 +1473,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -1444,6 +1497,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "is_sorted"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357376465c37db3372ef6a00585d336ed3d0f11d4345eef77ebcb05865392b21"
[[package]]
name = "itertools"
version = "0.10.5"
@@ -1521,9 +1580,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.169"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "libcst"
@@ -1547,7 +1606,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ae40017ac09cd2c6a53504cb3c871c7f2b41466eac5bc66ba63f39073b467b"
dependencies = [
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -1571,6 +1630,12 @@ dependencies = [
"redox_syscall 0.5.3",
]
[[package]]
name = "linear-hashtbl"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e252bb7e9b36739fb2482438638e862241d111c76fae3ebee272454477c63a4f"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -1589,6 +1654,12 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
[[package]]
name = "lock_api"
version = "0.4.11"
@@ -1607,14 +1678,13 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lsp-server"
version = "0.7.8"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9462c4dc73e17f971ec1f171d44bfffb72e65a130117233388a0ebc7ec5656f9"
checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9"
dependencies = [
"crossbeam-channel",
"log",
"serde",
"serde_derive",
"serde_json",
]
@@ -1704,6 +1774,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
[[package]]
name = "natord"
version = "1.0.9"
@@ -1857,6 +1933,153 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "oxidd"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22f7c2dbbc26b8d2e1ae861401a4bfa1d2e229e39da77ff8f0d64f2b4793e6da"
dependencies = [
"cfg-if",
"document-features",
"oxidd-cache",
"oxidd-core",
"oxidd-derive",
"oxidd-dump",
"oxidd-manager-index",
"oxidd-reorder",
"oxidd-rules-bdd",
"oxidd-rules-mtbdd",
"oxidd-rules-tdd",
"oxidd-rules-zbdd",
"rustc-hash 1.1.0",
]
[[package]]
name = "oxidd-cache"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efcc7923864f2a2525e76e26a3645b65a82e29ac3d3118d6906bad0117eb704e"
dependencies = [
"allocator-api2",
"document-features",
"hugealloc",
"oxidd-core",
"parking_lot",
]
[[package]]
name = "oxidd-core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b47e2c527b7aba7a7c789c7e5300df5a338002b33284c823fd08e1c661d176d6"
dependencies = [
"nanorand",
]
[[package]]
name = "oxidd-derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ccd72606db214bdf9b3880a6dedb50990fafdac23a05b65f982b65ac22862ac"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "oxidd-dump"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ef9fed6a604b8199c988efce9231de372b058cf9501d1b60fa53ed0ea1d43a"
dependencies = [
"bitvec",
"document-features",
"is_sorted",
"memchr",
"oxidd-core",
"rustc-hash 1.1.0",
]
[[package]]
name = "oxidd-manager-index"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd41278c0cd6f0418f78e8ed5a3a062fa31449a929b3a0d03d4c1056af620cdc"
dependencies = [
"bitvec",
"crossbeam-utils",
"linear-hashtbl",
"oxidd-core",
"parking_lot",
"rayon",
"rustc-hash 1.1.0",
]
[[package]]
name = "oxidd-reorder"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b3b3c71f9c4a5f875c01fda840e25d40d4fa86a5f0636938c17f7adf6c068"
dependencies = [
"flume",
"is_sorted",
"oxidd-core",
"rayon",
"smallvec",
]
[[package]]
name = "oxidd-rules-bdd"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ab7d000b0fb53ddcc282296929f51b2a5215591f6c6aa134a9fca9f133f61b"
dependencies = [
"bitvec",
"document-features",
"oxidd-core",
"oxidd-derive",
"oxidd-dump",
]
[[package]]
name = "oxidd-rules-mtbdd"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9bd51a1a297d98cefd4843cf065aba5cb04b2e78b0623857f0f8babe9f3d421"
dependencies = [
"document-features",
"oxidd-core",
"oxidd-derive",
"oxidd-dump",
]
[[package]]
name = "oxidd-rules-tdd"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "753f378e1d76f1f23afd1a06bbe4f23025ac95d19fb3c2b3ff052c0d3fcd9af3"
dependencies = [
"document-features",
"oxidd-core",
"oxidd-derive",
"oxidd-dump",
]
[[package]]
name = "oxidd-rules-zbdd"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "821482c23511e87b1a9bd7f0454971f4a73b01a7ed16611179d79c5872690dce"
dependencies = [
"bitvec",
"document-features",
"oxidd-core",
"oxidd-derive",
"oxidd-dump",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -2015,7 +2238,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -2128,6 +2351,29 @@ dependencies = [
"yansi",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
@@ -2162,7 +2408,7 @@ dependencies = [
"newtype-uuid",
"quick-xml",
"strip-ansi-escapes",
"thiserror 2.0.9",
"thiserror 2.0.7",
"uuid",
]
@@ -2197,13 +2443,19 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.38"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
@@ -2266,6 +2518,9 @@ dependencies = [
"crossbeam",
"ctrlc",
"filetime",
"oxidd",
"oxidd-core",
"oxidd-dump",
"rayon",
"red_knot_python_semantic",
"red_knot_server",
@@ -2289,7 +2544,6 @@ dependencies = [
"compact_str",
"countme",
"dir-test",
"drop_bomb",
"hashbrown 0.15.2",
"indexmap",
"insta",
@@ -2307,7 +2561,6 @@ dependencies = [
"ruff_python_literal",
"ruff_python_parser",
"ruff_python_stdlib",
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
"rustc-hash 2.1.0",
@@ -2317,7 +2570,7 @@ dependencies = [
"static_assertions",
"tempfile",
"test-case",
"thiserror 2.0.9",
"thiserror 2.0.7",
"tracing",
]
@@ -2414,7 +2667,7 @@ dependencies = [
"rustc-hash 2.1.0",
"salsa",
"serde",
"thiserror 2.0.9",
"thiserror 2.0.7",
"toml",
"tracing",
]
@@ -2520,7 +2773,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.8.5"
version = "0.8.3"
dependencies = [
"anyhow",
"argfile",
@@ -2567,7 +2820,7 @@ dependencies = [
"strum",
"tempfile",
"test-case",
"thiserror 2.0.9",
"thiserror 2.0.7",
"tikv-jemallocator",
"toml",
"tracing",
@@ -2637,7 +2890,7 @@ dependencies = [
"salsa",
"serde",
"tempfile",
"thiserror 2.0.9",
"thiserror 2.0.7",
"tracing",
"tracing-subscriber",
"tracing-tree",
@@ -2739,7 +2992,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.8.5"
version = "0.8.3"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2789,7 +3042,7 @@ dependencies = [
"strum",
"strum_macros",
"test-case",
"thiserror 2.0.9",
"thiserror 2.0.7",
"toml",
"typed-arena",
"unicode-normalization",
@@ -2806,7 +3059,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -2823,7 +3076,7 @@ dependencies = [
"serde_json",
"serde_with",
"test-case",
"thiserror 2.0.9",
"thiserror 2.0.7",
"uuid",
]
@@ -2895,7 +3148,7 @@ dependencies = [
"similar",
"smallvec",
"static_assertions",
"thiserror 2.0.9",
"thiserror 2.0.7",
"tracing",
]
@@ -2969,7 +3222,6 @@ dependencies = [
"rustc-hash 2.1.0",
"schemars",
"serde",
"smallvec",
]
[[package]]
@@ -3029,7 +3281,7 @@ dependencies = [
"serde",
"serde_json",
"shellexpand",
"thiserror 2.0.9",
"thiserror 2.0.7",
"tracing",
"tracing-subscriber",
]
@@ -3055,7 +3307,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.8.5"
version = "0.8.3"
dependencies = [
"console_error_panic_hook",
"console_log",
@@ -3228,7 +3480,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"synstructure",
]
@@ -3262,7 +3514,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3285,9 +3537,9 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
@@ -3305,13 +3557,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3322,14 +3574,14 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
name = "serde_json"
version = "1.0.134"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
@@ -3345,7 +3597,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3368,9 +3620,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.12.0"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817"
dependencies = [
"serde",
"serde_derive",
@@ -3379,14 +3631,14 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.12.0"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3447,6 +3699,15 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "sptr"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
[[package]]
name = "stable_deref_trait"
@@ -3500,7 +3761,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3522,9 +3783,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.93"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@@ -3539,9 +3800,15 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.14.0"
@@ -3602,7 +3869,7 @@ dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3613,7 +3880,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"test-case-core",
]
@@ -3628,11 +3895,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.9"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
dependencies = [
"thiserror-impl 2.0.9",
"thiserror-impl 2.0.7",
]
[[package]]
@@ -3643,18 +3910,18 @@ checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
name = "thiserror-impl"
version = "2.0.9"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -3776,7 +4043,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -4046,7 +4313,7 @@ checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -4141,7 +4408,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"wasm-bindgen-shared",
]
@@ -4176,7 +4443,7 @@ checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -4210,7 +4477,7 @@ checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -4478,6 +4745,15 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "yansi"
version = "1.0.1"
@@ -4513,7 +4789,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"synstructure",
]
@@ -4534,7 +4810,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]
@@ -4554,7 +4830,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
"synstructure",
]
@@ -4583,7 +4859,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.93",
"syn 2.0.90",
]
[[package]]

View File

@@ -105,6 +105,7 @@ mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "7.0.0" }
ordermap = { version = "0.5.0" }
oxidd = { version = "0.9.0", features = ["tdd"] }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }

View File

@@ -140,8 +140,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.5/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.8.5/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.8.3/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.8.3/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -174,7 +174,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.5
rev: v0.8.3
hooks:
# Run the linter.
- id: ruff
@@ -196,7 +196,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v3
- uses: astral-sh/ruff-action@v1
```
### Configuration<a id="configuration"></a>

View File

@@ -24,6 +24,9 @@ colored = { workspace = true }
countme = { workspace = true, features = ["enable"] }
crossbeam = { workspace = true }
ctrlc = { version = "3.4.4" }
oxidd = { workspace = true }
oxidd-core = { version = "0.9.0" }
oxidd-dump = { version = "0.4.0" }
rayon = { workspace = true }
salsa = { workspace = true }
tracing = { workspace = true, features = ["release_max_level_debug"] }

View File

@@ -105,8 +105,44 @@ pub enum Command {
Server,
}
use oxidd::bdd::BDDFunction;
use oxidd::ManagerRef;
use oxidd::{BooleanFunction, BooleanFunctionQuant};
use oxidd_core::Manager;
use oxidd_dump::dot::dump_all;
#[allow(clippy::print_stdout, clippy::unnecessary_wraps, clippy::print_stderr)]
pub fn main() -> ExitStatus {
let mgr = oxidd::bdd::new_manager(24, 24, 1);
let (x, y, z) = mgr.with_manager_exclusive(|mgr| {
(
BDDFunction::new_var(mgr).unwrap(),
BDDFunction::new_var(mgr).unwrap(),
BDDFunction::new_var(mgr).unwrap(),
)
});
mgr.with_manager_shared(|manager| {
let inner_func = x
.or(&y.and(&x.not().unwrap()).unwrap())
.unwrap()
.or(&y.not().unwrap().and(&x.not().unwrap()).unwrap())
.unwrap();
let func = z.and(&inner_func).unwrap();
let func = func.restrict(&z).unwrap();
manager.gc();
let file = std::fs::File::create("bdd.dot").expect("could not create `bdd.dot`");
dump_all(
file,
manager,
[(&x, "x"), (&y, "y"), (&z, "z")],
[(&func, "z ^ (x (y ∧ ~x) (~y ∧ ~x))")],
)
.expect("dot export failed");
});
panic!("FOO");
run().unwrap_or_else(|error| {
use std::io::Write;

View File

@@ -20,14 +20,12 @@ ruff_python_stdlib = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }
ruff_python_literal = { workspace = true }
ruff_python_trivia = { workspace = true }
anyhow = { workspace = true }
bitflags = { workspace = true }
camino = { workspace = true }
compact_str = { workspace = true }
countme = { workspace = true }
drop_bomb = { workspace = true }
indexmap = { workspace = true }
itertools = { workspace = true }
ordermap = { workspace = true }
@@ -60,3 +58,4 @@ serde = ["ruff_db/serde", "dep:serde"]
[lints]
workspace = true

View File

@@ -73,12 +73,12 @@ qux = (foo, bar)
reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]]
# TODO: Infer "LiteralString"
reveal_type(foo.join(qux)) # revealed: @Todo(Attribute access on `StringLiteral` types)
reveal_type(foo.join(qux)) # revealed: @Todo(call todo)
template: LiteralString = "{}, {}"
reveal_type(template) # revealed: Literal["{}, {}"]
# TODO: Infer `LiteralString`
reveal_type(template.format(foo, bar)) # revealed: @Todo(Attribute access on `StringLiteral` types)
reveal_type(template.format(foo, bar)) # revealed: @Todo(call todo)
```
### Assignability

View File

@@ -47,9 +47,7 @@ def f():
## `typing.Never`
`typing.Never` is only available in Python 3.11 and later.
### Python 3.11
`typing.Never` is only available in Python 3.11 and later:
```toml
[environment]
@@ -59,17 +57,8 @@ python-version = "3.11"
```py
from typing import Never
reveal_type(Never) # revealed: typing.Never
```
x: Never
### Python 3.10
```toml
[environment]
python-version = "3.10"
```
```py
# error: [unresolved-import]
from typing import Never
def f():
reveal_type(x) # revealed: Never
```

View File

@@ -33,6 +33,8 @@ b: tuple[int] = (42,)
c: tuple[str, int] = ("42", 42)
d: tuple[tuple[str, str], tuple[int, int]] = (("foo", "foo"), (42, 42))
e: tuple[str, ...] = ()
# TODO: we should not emit this error
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
f: tuple[str, *tuple[int, ...], bytes] = ("42", b"42")
g: tuple[str, Unpack[tuple[int, ...]], bytes] = ("42", b"42")
h: tuple[list[int], list[int]] = ([], [])

View File

@@ -1,6 +1,4 @@
# Attributes
Tests for attribute access on various kinds of types.
# Class attributes
## Union of attributes
@@ -157,67 +155,3 @@ class Foo: ...
reveal_type(Foo.__class__) # revealed: Literal[type]
```
## Function-literal attributes
Most attribute accesses on function-literal types are delegated to `types.FunctionType`, since all
functions are instances of that class:
```py path=a.py
def f(): ...
reveal_type(f.__defaults__) # revealed: @Todo(instance attributes)
reveal_type(f.__kwdefaults__) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
def f(): ...
reveal_type(f.__get__) # revealed: @Todo(`__get__` method on functions)
reveal_type(f.__call__) # revealed: @Todo(`__call__` method on functions)
```
## Int-literal attributes
Most attribute accesses on int-literal types are delegated to `builtins.int`, since all literal
integers are instances of that class:
```py path=a.py
reveal_type((2).bit_length) # revealed: @Todo(instance attributes)
reveal_type((2).denominator) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
reveal_type((2).numerator) # revealed: Literal[2]
reveal_type((2).real) # revealed: Literal[2]
```
## Literal `bool` attributes
Most attribute accesses on bool-literal types are delegated to `builtins.bool`, since all literal
bols are instances of that class:
```py path=a.py
reveal_type(True.__and__) # revealed: @Todo(instance attributes)
reveal_type(False.__or__) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
reveal_type(True.numerator) # revealed: Literal[1]
reveal_type(False.real) # revealed: Literal[0]
```
## Bytes-literal attributes
All attribute access on literal `bytes` types is currently delegated to `buitins.bytes`:
```py
reveal_type(b"foo".join) # revealed: @Todo(instance attributes)
reveal_type(b"foo".endswith) # revealed: @Todo(instance attributes)
```

View File

@@ -32,10 +32,13 @@ def _(flag: bool):
```py
if True or (x := 1):
# error: [unresolved-reference]
reveal_type(x) # revealed: Unknown
# TODO: infer that the second arm is never executed, and raise `unresolved-reference`.
# error: [possibly-unresolved-reference]
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
# error: [possibly-unresolved-reference]
reveal_type(x) # revealed: Literal[1]
```

View File

@@ -67,6 +67,6 @@ def _(flag: bool):
def __call__(self) -> int: ...
a = NonCallable()
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
reveal_type(a()) # revealed: Unknown | int
# error: "Object of type `Literal[__call__] | Literal[1]` is not callable (due to union element `Literal[1]`)"
reveal_type(a()) # revealed: int | Unknown
```

View File

@@ -31,10 +31,10 @@ class C:
def __lt__(self, other) -> C: ...
x = A() < B() < C()
reveal_type(x) # revealed: A & ~AlwaysTruthy | B
reveal_type(x) # revealed: A | B
y = 0 < 1 < A() < 3
reveal_type(y) # revealed: Literal[False] | A
reveal_type(y) # revealed: bool | A
z = 10 < 0 < A() < B() < C()
reveal_type(z) # revealed: Literal[False]

View File

@@ -90,83 +90,3 @@ def foo(
# TODO: should emit a diagnostic here:
reveal_type(g) # revealed: @Todo(full tuple[...] support)
```
## Object raised is not an exception
```py
try:
raise AttributeError() # fine
except:
...
try:
raise FloatingPointError # fine
except:
...
try:
raise 1 # error: [invalid-raise]
except:
...
try:
raise int # error: [invalid-raise]
except:
...
def _(e: Exception | type[Exception]):
raise e # fine
def _(e: Exception | type[Exception] | None):
raise e # error: [invalid-raise]
```
## Exception cause is not an exception
```py
try:
raise EOFError() from GeneratorExit # fine
except:
...
try:
raise StopIteration from MemoryError() # fine
except:
...
try:
raise BufferError() from None # fine
except:
...
try:
raise ZeroDivisionError from False # error: [invalid-raise]
except:
...
try:
raise SystemExit from bool() # error: [invalid-raise]
except:
...
try:
raise
except KeyboardInterrupt as e: # fine
reveal_type(e) # revealed: KeyboardInterrupt
raise LookupError from e # fine
try:
raise
except int as e: # error: [invalid-exception-caught]
reveal_type(e) # revealed: Unknown
raise KeyError from e
def _(e: Exception | type[Exception]):
raise ModuleNotFoundError from e # fine
def _(e: Exception | type[Exception] | None):
raise IndexError from e # fine
def _(e: int | None):
raise IndexError from e # error: [invalid-raise]
```

View File

@@ -10,8 +10,8 @@ def _(foo: str):
reveal_type(False or "z") # revealed: Literal["z"]
reveal_type(False or True) # revealed: Literal[True]
reveal_type(False or False) # revealed: Literal[False]
reveal_type(foo or False) # revealed: str & ~AlwaysFalsy | Literal[False]
reveal_type(foo or True) # revealed: str & ~AlwaysFalsy | Literal[True]
reveal_type(foo or False) # revealed: str | Literal[False]
reveal_type(foo or True) # revealed: str | Literal[True]
```
## AND
@@ -20,8 +20,8 @@ def _(foo: str):
def _(foo: str):
reveal_type(True and False) # revealed: Literal[False]
reveal_type(False and True) # revealed: Literal[False]
reveal_type(foo and False) # revealed: str & ~AlwaysTruthy | Literal[False]
reveal_type(foo and True) # revealed: str & ~AlwaysTruthy | Literal[True]
reveal_type(foo and False) # revealed: str | Literal[False]
reveal_type(foo and True) # revealed: str | Literal[True]
reveal_type("x" and "y" and "z") # revealed: Literal["z"]
reveal_type("x" and "y" and "") # revealed: Literal[""]
reveal_type("" and "y") # revealed: Literal[""]

View File

@@ -7,7 +7,7 @@ def _(flag: bool):
reveal_type(1 if flag else 2) # revealed: Literal[1, 2]
```
## Statically known conditions in if-expressions
## Statically known branches
```py
reveal_type(1 if True else 2) # revealed: Literal[1]

View File

@@ -1,31 +0,0 @@
# Tests for the `@typing(_extensions).final` decorator
## Cannot subclass
Don't do this:
```py
import typing_extensions
from typing import final
@final
class A: ...
class B(A): ... # error: 9 [subclass-of-final-class] "Class `B` cannot inherit from final class `A`"
@typing_extensions.final
class C: ...
class D(C): ... # error: [subclass-of-final-class]
class E: ...
class F: ...
class G: ...
# fmt: off
class H(
E,
F,
A, # error: [subclass-of-final-class]
G,
): ...
```

View File

@@ -1,75 +0,0 @@
# Conflicting attributes and submodules
## Via import
```py
import a.b
reveal_type(a.b) # revealed: <module 'a.b'>
```
```py path=a/__init__.py
b = 42
```
```py path=a/b.py
```
## Via from/import
```py
from a import b
reveal_type(b) # revealed: Literal[42]
```
```py path=a/__init__.py
b = 42
```
```py path=a/b.py
```
## Via both
```py
import a.b
from a import b
reveal_type(b) # revealed: <module 'a.b'>
reveal_type(a.b) # revealed: <module 'a.b'>
```
```py path=a/__init__.py
b = 42
```
```py path=a/b.py
```
## Via both (backwards)
In this test, we infer a different type for `b` than the runtime behavior of the Python interpreter.
The interpreter will not load the submodule `a.b` during the `from a import b` statement, since `a`
contains a non-module attribute named `b`. (See the [definition][from-import] of a `from...import`
statement for details.) However, because our import tracking is flow-insensitive, we will see that
`a.b` is imported somewhere in the file, and therefore assume that the `from...import` statement
sees the submodule as the value of `b` instead of the integer.
```py
from a import b
import a.b
# Python would say `Literal[42]` for `b`
reveal_type(b) # revealed: <module 'a.b'>
reveal_type(a.b) # revealed: <module 'a.b'>
```
```py path=a/__init__.py
b = 42
```
```py path=a/b.py
```
[from-import]: https://docs.python.org/3/reference/simple_stmts.html#the-import-statement

View File

@@ -1,23 +1,7 @@
# Ellipsis literals
## Python 3.9
```toml
[environment]
python-version = "3.9"
```
## Simple
```py
reveal_type(...) # revealed: ellipsis
```
## Python 3.10
```toml
[environment]
python-version = "3.10"
```
```py
reveal_type(...) # revealed: EllipsisType
reveal_type(...) # revealed: EllipsisType | ellipsis
```

View File

@@ -95,14 +95,10 @@ def _(t: type[object]):
### Handling of `None`
`types.NoneType` is only available in Python 3.10 and later:
```toml
[environment]
python-version = "3.10"
```
```py
# TODO: this error should ideally go away once we (1) understand `sys.version_info` branches,
# and (2) set the target Python version for this test to 3.10.
# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound"
from types import NoneType
def _(flag: bool):

View File

@@ -219,97 +219,3 @@ else:
# TODO: It should be A. We should improve UnionBuilder or IntersectionBuilder. (issue #15023)
reveal_type(y) # revealed: A & ~AlwaysTruthy | A & ~AlwaysFalsy
```
## Truthiness of classes
```py
class MetaAmbiguous(type):
def __bool__(self) -> bool: ...
class MetaFalsy(type):
def __bool__(self) -> Literal[False]: ...
class MetaTruthy(type):
def __bool__(self) -> Literal[True]: ...
class MetaDeferred(type):
def __bool__(self) -> MetaAmbiguous: ...
class AmbiguousClass(metaclass=MetaAmbiguous): ...
class FalsyClass(metaclass=MetaFalsy): ...
class TruthyClass(metaclass=MetaTruthy): ...
class DeferredClass(metaclass=MetaDeferred): ...
def _(
a: type[AmbiguousClass],
t: type[TruthyClass],
f: type[FalsyClass],
d: type[DeferredClass],
ta: type[TruthyClass | AmbiguousClass],
af: type[AmbiguousClass] | type[FalsyClass],
flag: bool,
):
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass]
if ta:
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass] & ~AlwaysFalsy
reveal_type(af) # revealed: type[AmbiguousClass] | type[FalsyClass]
if af:
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
# TODO: Emit a diagnostic (`d` is not valid in boolean context)
if d:
# TODO: Should be `Unknown`
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy
tf = TruthyClass if flag else FalsyClass
reveal_type(tf) # revealed: Literal[TruthyClass, FalsyClass]
if tf:
reveal_type(tf) # revealed: Literal[TruthyClass]
else:
reveal_type(tf) # revealed: Literal[FalsyClass]
```
## Narrowing in chained boolean expressions
```py
from typing import Literal
class A: ...
def _(x: Literal[0, 1]):
reveal_type(x or A()) # revealed: Literal[1] | A
reveal_type(x and A()) # revealed: Literal[0] | A
def _(x: str):
reveal_type(x or A()) # revealed: str & ~AlwaysFalsy | A
reveal_type(x and A()) # revealed: str & ~AlwaysTruthy | A
def _(x: bool | str):
reveal_type(x or A()) # revealed: Literal[True] | str & ~AlwaysFalsy | A
reveal_type(x and A()) # revealed: Literal[False] | str & ~AlwaysTruthy | A
class Falsy:
def __bool__(self) -> Literal[False]: ...
class Truthy:
def __bool__(self) -> Literal[True]: ...
def _(x: Falsy | Truthy):
reveal_type(x or A()) # revealed: Truthy | A
reveal_type(x and A()) # revealed: Falsy | A
class MetaFalsy(type):
def __bool__(self) -> Literal[False]: ...
class MetaTruthy(type):
def __bool__(self) -> Literal[True]: ...
class FalsyClass(metaclass=MetaFalsy): ...
class TruthyClass(metaclass=MetaTruthy): ...
def _(x: type[FalsyClass] | type[TruthyClass]):
reveal_type(x or A()) # revealed: type[TruthyClass] | A
reveal_type(x and A()) # revealed: type[FalsyClass] | A
```

View File

@@ -25,29 +25,3 @@ def f(): ...
f: int = 1
```
## Explicit shadowing involving `def` statements
Since a `def` statement is a declaration, one `def` can shadow another `def`, or shadow a previous
non-`def` declaration, without error.
```py
f = 1
reveal_type(f) # revealed: Literal[1]
def f(): ...
reveal_type(f) # revealed: Literal[f]
def f(x: int) -> int:
raise NotImplementedError
reveal_type(f) # revealed: Literal[f]
f: int = 1
reveal_type(f) # revealed: Literal[1]
def f(): ...
reveal_type(f) # revealed: Literal[f]
```

View File

@@ -1,184 +0,0 @@
# `__slots__`
## Not specified and empty
```py
class A: ...
class B:
__slots__ = ()
class C:
__slots__ = ("lorem", "ipsum")
class AB(A, B): ... # fine
class AC(A, C): ... # fine
class BC(B, C): ... # fine
class ABC(A, B, C): ... # fine
```
## Incompatible tuples
```py
class A:
__slots__ = ("a", "b")
class B:
__slots__ = ("c", "d")
class C(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
```
## Same value
```py
class A:
__slots__ = ("a", "b")
class B:
__slots__ = ("a", "b")
class C(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
```
## Strings
```py
class A:
__slots__ = "abc"
class B:
__slots__ = ("abc",)
class AB(
A, # error: [incompatible-slots]
B, # error: [incompatible-slots]
): ...
```
## Invalid
TODO: Emit diagnostics
```py
class NonString1:
__slots__ = 42
class NonString2:
__slots__ = b"ar"
class NonIdentifier1:
__slots__ = "42"
class NonIdentifier2:
__slots__ = ("lorem", "42")
class NonIdentifier3:
__slots__ = (e for e in ("lorem", "42"))
```
## Inheritance
```py
class A:
__slots__ = ("a", "b")
class B(A): ...
class C:
__slots__ = ("c", "d")
class D(C): ...
class E(
B, # error: [incompatible-slots]
D, # error: [incompatible-slots]
): ...
```
## Single solid base
```py
class A:
__slots__ = ("a", "b")
class B(A): ...
class C(A): ...
class D(B, A): ... # fine
class E(B, C, A): ... # fine
```
## False negatives
### Possibly unbound
```py
def _(flag: bool):
class A:
if flag:
__slots__ = ("a", "b")
class B:
__slots__ = ("c", "d")
# Might or might not be fine at runtime
class C(A, B): ...
```
### Bound but with different types
```py
def _(flag: bool):
class A:
if flag:
__slots__ = ("a", "b")
else:
__slots__ = ()
class B:
__slots__ = ("c", "d")
# Might or might not be fine at runtime
class C(A, B): ...
```
### Non-tuples
```py
class A:
__slots__ = ["a", "b"] # This is treated as "dynamic"
class B:
__slots__ = ("c", "d")
# False negative: [incompatible-slots]
class C(A, B): ...
```
### Post-hoc modifications
```py
class A:
__slots__ = ()
__slots__ += ("a", "b")
reveal_type(A.__slots__) # revealed: @Todo(Support for more binary expressions)
class B:
__slots__ = ("c", "d")
# False negative: [incompatible-slots]
class C(A, B): ...
```
### Built-ins with implicit layouts
```py
# False negative: [incompatible-slots]
class A(int, str): ...
```

View File

@@ -81,7 +81,10 @@ python-version = "3.9"
```
```py
# TODO: `tuple[int, str]` is a valid base (generics)
# TODO:
# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`)
# * `tuple[int, str]` is a valid base (generics)
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
class A(tuple[int, str]): ...

View File

@@ -1,182 +0,0 @@
# Suppressing errors with `knot: ignore`
Type check errors can be suppressed by a `knot: ignore` comment on the same line as the violation.
## Simple `knot: ignore`
```py
a = 4 + test # knot: ignore
```
## Suppressing a specific code
```py
a = 4 + test # knot: ignore[unresolved-reference]
```
## Unused suppression
```py
test = 10
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
a = test + 3 # knot: ignore[possibly-unresolved-reference]
```
## Unused suppression if the error codes don't match
```py
# error: [unresolved-reference]
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'possibly-unresolved-reference'"
a = test + 3 # knot: ignore[possibly-unresolved-reference]
```
## Suppressed unused comment
```py
# error: [unused-ignore-comment]
a = 10 / 2 # knot: ignore[division-by-zero]
a = 10 / 2 # knot: ignore[division-by-zero, unused-ignore-comment]
a = 10 / 2 # knot: ignore[unused-ignore-comment, division-by-zero]
a = 10 / 2 # knot: ignore[unused-ignore-comment] # type: ignore
a = 10 / 2 # type: ignore # knot: ignore[unused-ignore-comment]
```
## Unused ignore comment
```py
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unused-ignore-comment'"
a = 10 / 0 # knot: ignore[division-by-zero, unused-ignore-comment]
```
## Multiple unused comments
Today, Red Knot emits a diagnostic for every unused code. We might want to group the codes by
comment at some point in the future.
```py
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'division-by-zero'"
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
a = 10 / 2 # knot: ignore[division-by-zero, unresolved-reference]
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'invalid-assignment'"
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
a = 10 / 0 # knot: ignore[invalid-assignment, division-by-zero, unresolved-reference]
```
## Multiple suppressions
```py
# fmt: off
def test(a: f"f-string type annotation", b: b"byte-string-type-annotation"): ... # knot: ignore[fstring-type-annotation, byte-string-type-annotation]
```
## Can't suppress syntax errors
<!-- blacken-docs:off -->
```py
# error: [invalid-syntax]
# error: [unused-ignore-comment]
def test( # knot: ignore
```
<!-- blacken-docs:on -->
## Can't suppress `revealed-type` diagnostics
```py
a = 10
# revealed: Literal[10]
# error: [unknown-rule] "Unknown rule `revealed-type`"
reveal_type(a) # knot: ignore[revealed-type]
```
## Extra whitespace in type ignore comments is allowed
```py
a = 10 / 0 # knot : ignore
a = 10 / 0 # knot: ignore [ division-by-zero ]
```
## Whitespace is optional
```py
# fmt: off
a = 10 / 0 #knot:ignore[division-by-zero]
```
## Trailing codes comma
Trailing commas in the codes section are allowed:
```py
a = 10 / 0 # knot: ignore[division-by-zero,]
```
## Invalid characters in codes
```py
# error: [division-by-zero]
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a alphanumeric character or `-` or `_` as code"
a = 10 / 0 # knot: ignore[*-*]
```
## Trailing whitespace
<!-- blacken-docs:off -->
```py
a = 10 / 0 # knot: ignore[division-by-zero]
# ^^^^^^ trailing whitespace
```
<!-- blacken-docs:on -->
## Missing comma
A missing comma results in an invalid suppression comment. We may want to recover from this in the
future.
```py
# error: [unresolved-reference]
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
a = x / 0 # knot: ignore[division-by-zero unresolved-reference]
```
## Missing closing bracket
```py
# error: [unresolved-reference] "Name `x` used when not defined"
# error: [invalid-ignore-comment] "Invalid `knot: ignore` comment: expected a comma separating the rule codes"
a = x / 2 # knot: ignore[unresolved-reference
```
## Empty codes
An empty codes array suppresses no-diagnostics and is always useless
```py
# error: [division-by-zero]
# error: [unused-ignore-comment] "Unused `knot: ignore` without a code"
a = 4 / 0 # knot: ignore[]
```
## File-level suppression comments
File level suppression comments are currently intentionally unsupported because we've yet to decide
if they should use a different syntax that also supports enabling rules or changing the rule's
severity: `knot: possibly-undefined-reference=error`
```py
# error: [unused-ignore-comment]
# knot: ignore[division-by-zero]
a = 4 / 0 # error: [division-by-zero]
```
## Unknown rule
```py
# error: [unknown-rule] "Unknown rule `is-equal-14`"
a = 10 + 4 # knot: ignore[is-equal-14]
```

View File

@@ -1,118 +0,0 @@
# `@no_type_check`
> If a type checker supports the `no_type_check` decorator for functions, it should suppress all
> type errors for the def statement and its body including any nested functions or classes. It
> should also ignore all parameter and return type annotations and treat the function as if it were
> unannotated. [source](https://typing.readthedocs.io/en/latest/spec/directives.html#no-type-check)
## Error in the function body
```py
from typing import no_type_check
@no_type_check
def test() -> int:
return a + 5
```
## Error in nested function
```py
from typing import no_type_check
@no_type_check
def test() -> int:
def nested():
return a + 5
```
## Error in nested class
```py
from typing import no_type_check
@no_type_check
def test() -> int:
class Nested:
def inner(self):
return a + 5
```
## Error in preceding decorator
Don't suppress diagnostics for decorators appearing before the `no_type_check` decorator.
```py
from typing import no_type_check
@unknown_decorator # error: [unresolved-reference]
@no_type_check
def test() -> int:
return a + 5
```
## Error in following decorator
Unlike Pyright and mypy, suppress diagnostics appearing after the `no_type_check` decorator. We do
this because it more closely matches Python's runtime semantics of decorators. For more details, see
the discussion on the
[PR adding `@no_type_check` support](https://github.com/astral-sh/ruff/pull/15122#discussion_r1896869411).
```py
from typing import no_type_check
@no_type_check
@unknown_decorator
def test() -> int:
return a + 5
```
## Error in default value
```py
from typing import no_type_check
@no_type_check
def test(a: int = "test"):
return x + 5
```
## Error in return value position
```py
from typing import no_type_check
@no_type_check
def test() -> Undefined:
return x + 5
```
## `no_type_check` on classes isn't supported
Red Knot does not support decorating classes with `no_type_check`. The behaviour of `no_type_check`
when applied to classes is
[not specified currently](https://typing.readthedocs.io/en/latest/spec/directives.html#no-type-check),
and is not supported by Pyright or mypy.
A future improvement might be to emit a diagnostic if a `no_type_check` annotation is applied to a
class.
```py
from typing import no_type_check
@no_type_check
class Test:
def test(self):
return a + 5 # error: [unresolved-reference]
```
## `type: ignore` comments in `@no_type_check` blocks
```py
from typing import no_type_check
@no_type_check
def test():
# error: [unused-ignore-comment] "Unused `knot: ignore` directive: 'unresolved-reference'"
return x + 5 # knot: ignore[unresolved-reference]
```

View File

@@ -1,162 +0,0 @@
# Suppressing errors with `type: ignore`
Type check errors can be suppressed by a `type: ignore` comment on the same line as the violation.
## Simple `type: ignore`
```py
a = 4 + test # type: ignore
```
## Multiline ranges
A diagnostic with a multiline range can be suppressed by a comment on the same line as the
diagnostic's start or end. This is the same behavior as Mypy's.
```py
# fmt: off
y = (
4 / 0 # type: ignore
)
y = (
4 / # type: ignore
0
)
y = (
4 /
0 # type: ignore
)
```
Pyright diverges from this behavior and instead applies a suppression if its range intersects with
the diagnostic range. This can be problematic for nested expressions because a suppression in a
child expression now suppresses errors in the outer expression.
For example, the `type: ignore` comment in this example suppresses the error of adding `2` to
`"test"` and adding `"other"` to the result of the cast.
```py path=nested.py
# fmt: off
from typing import cast
y = (
cast(int, "test" +
# TODO: Remove the expected error after implementing `invalid-operator` for binary expressions
# error: [unused-ignore-comment]
2 # type: ignore
)
+ "other" # TODO: expected-error[invalid-operator]
)
```
Mypy flags the second usage.
## Before opening parenthesis
A suppression that applies to all errors before the opening parenthesis.
```py
a: Test = ( # type: ignore
Test() # error: [unresolved-reference]
) # fmt: skip
```
## Multiline string
```py
a: int = 4
a = """
This is a multiline string and the suppression is at its end
""" # type: ignore
```
## Line continuations
Suppressions after a line continuation apply to all previous lines.
```py
# fmt: off
a = test \
+ 2 # type: ignore
a = test \
+ a \
+ 2 # type: ignore
```
## Codes
Mypy supports `type: ignore[code]`. Red Knot doesn't understand mypy's rule names. Therefore, ignore
the codes and suppress all errors.
```py
a = test # type: ignore[name-defined]
```
## Nested comments
```py
# fmt: off
a = test \
+ 2 # fmt: skip # type: ignore
a = test \
+ 2 # type: ignore # fmt: skip
```
## Misspelled `type: ignore`
```py
# error: [unresolved-reference]
# error: [invalid-ignore-comment]
a = test + 2 # type: ignoree
```
## Invalid - ignore on opening parentheses
`type: ignore` comments after an opening parentheses suppress any type errors inside the parentheses
in Pyright. Neither Ruff, nor mypy support this and neither does Red Knot.
```py
# fmt: off
# error: [unused-ignore-comment]
a = ( # type: ignore
test + 4 # error: [unresolved-reference]
)
```
## File level suppression
```py
# type: ignore
a = 10 / 0
b = a / 0
```
## File level suppression with leading shebang
```py
#!/usr/bin/env/python
# type: ignore
a = 10 / 0
b = a / 0
```
## Invalid own-line suppression
```py
"""
File level suppressions must come before any non-trivia token,
including module docstrings.
"""
# error: [unused-ignore-comment] "Unused blanket `type: ignore` directive"
# type: ignore
a = 10 / 0 # error: [division-by-zero]
b = a / 0 # error: [division-by-zero]
```

View File

@@ -1,71 +0,0 @@
# `sys.platform`
## Default value
When no target platform is specified, we fall back to the type of `sys.platform` declared in
typeshed:
```toml
[environment]
# No python-platform entry
```
```py
import sys
reveal_type(sys.platform) # revealed: str
```
## Explicit selection of `all` platforms
```toml
[environment]
python-platform = "all"
```
```py
import sys
reveal_type(sys.platform) # revealed: str
```
## Explicit selection of a specific platform
```toml
[environment]
python-platform = "linux"
```
```py
import sys
reveal_type(sys.platform) # revealed: Literal["linux"]
```
## Testing for a specific platform
### Exact comparison
```toml
[environment]
python-platform = "freebsd8"
```
```py
import sys
reveal_type(sys.platform == "freebsd8") # revealed: Literal[True]
reveal_type(sys.platform == "linux") # revealed: Literal[False]
```
### Substring comparison
It is [recommended](https://docs.python.org/3/library/sys.html#sys.platform) to use
`sys.platform.startswith(...)` for platform checks. This is not yet supported in type inference:
```py
import sys
reveal_type(sys.platform.startswith("freebsd")) # revealed: @Todo(instance attributes)
reveal_type(sys.platform.startswith("linux")) # revealed: @Todo(instance attributes)
```

View File

@@ -1,165 +0,0 @@
# Custom unary operations
## Class instances
```py
class Yes:
def __pos__(self) -> bool:
return False
def __neg__(self) -> str:
return "negative"
def __invert__(self) -> int:
return 17
class Sub(Yes): ...
class No: ...
reveal_type(+Yes()) # revealed: bool
reveal_type(-Yes()) # revealed: str
reveal_type(~Yes()) # revealed: int
reveal_type(+Sub()) # revealed: bool
reveal_type(-Sub()) # revealed: str
reveal_type(~Sub()) # revealed: int
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `No`"
reveal_type(+No()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `No`"
reveal_type(-No()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `No`"
reveal_type(~No()) # revealed: Unknown
```
## Classes
```py
class Yes:
def __pos__(self) -> bool:
return False
def __neg__(self) -> str:
return "negative"
def __invert__(self) -> int:
return 17
class Sub(Yes): ...
class No: ...
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Yes]`"
reveal_type(+Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Yes]`"
reveal_type(-Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Yes]`"
reveal_type(~Yes) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[Sub]`"
reveal_type(+Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[Sub]`"
reveal_type(-Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[Sub]`"
reveal_type(~Sub) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
reveal_type(~No) # revealed: Unknown
```
## Function literals
```py
def f():
pass
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[f]`"
reveal_type(+f) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[f]`"
reveal_type(-f) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[f]`"
reveal_type(~f) # revealed: Unknown
```
## Subclass
```py
class Yes:
def __pos__(self) -> bool:
return False
def __neg__(self) -> str:
return "negative"
def __invert__(self) -> int:
return 17
class Sub(Yes): ...
class No: ...
def yes() -> type[Yes]:
return Yes
def sub() -> type[Sub]:
return Sub
def no() -> type[No]:
return No
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Yes]`"
reveal_type(+yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Yes]`"
reveal_type(-yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Yes]`"
reveal_type(~yes()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[Sub]`"
reveal_type(+sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[Sub]`"
reveal_type(-sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[Sub]`"
reveal_type(~sub()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `type[No]`"
reveal_type(+no()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `type[No]`"
reveal_type(-no()) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `type[No]`"
reveal_type(~no()) # revealed: Unknown
```
## Metaclass
```py
class Meta(type):
def __pos__(self) -> bool:
return False
def __neg__(self) -> str:
return "negative"
def __invert__(self) -> int:
return 17
class Yes(metaclass=Meta): ...
class Sub(Yes): ...
class No: ...
reveal_type(+Yes) # revealed: bool
reveal_type(-Yes) # revealed: str
reveal_type(~Yes) # revealed: int
reveal_type(+Sub) # revealed: bool
reveal_type(-Sub) # revealed: str
reveal_type(~Sub) # revealed: int
# error: [unsupported-operator] "Unary operator `+` is unsupported for type `Literal[No]`"
reveal_type(+No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `-` is unsupported for type `Literal[No]`"
reveal_type(-No) # revealed: Unknown
# error: [unsupported-operator] "Unary operator `~` is unsupported for type `Literal[No]`"
reveal_type(~No) # revealed: Unknown
```

View File

@@ -61,7 +61,7 @@ reveal_type(c) # revealed: Literal[4]
### Uneven unpacking (1)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
# TODO: Add diagnostic (there aren't enough values to unpack)
(a, b, c) = (1, 2)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
@@ -71,7 +71,7 @@ reveal_type(c) # revealed: Unknown
### Uneven unpacking (2)
```py
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
# TODO: Add diagnostic (too many values to unpack)
(a, b) = (1, 2, 3)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Literal[2]
@@ -80,7 +80,7 @@ reveal_type(b) # revealed: Literal[2]
### Starred expression (1)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
# TODO: Add diagnostic (need more values to unpack)
[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
@@ -133,7 +133,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
### Starred expression (6)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 5 or more, got 1)"
# TODO: Add diagnostic (need more values to unpack)
(a, b, c, *d, e, f) = (1,)
reveal_type(a) # revealed: Literal[1]
reveal_type(b) # revealed: Unknown
@@ -199,7 +199,7 @@ reveal_type(b) # revealed: LiteralString
### Uneven unpacking (1)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
# TODO: Add diagnostic (there aren't enough values to unpack)
a, b, c = "ab"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
@@ -209,7 +209,7 @@ reveal_type(c) # revealed: Unknown
### Uneven unpacking (2)
```py
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
# TODO: Add diagnostic (too many values to unpack)
a, b = "abc"
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
@@ -218,7 +218,7 @@ reveal_type(b) # revealed: LiteralString
### Starred expression (1)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
# TODO: Add diagnostic (need more values to unpack)
(a, *b, c, d) = "ab"
reveal_type(a) # revealed: LiteralString
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
@@ -271,7 +271,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
### Unicode
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "é"
reveal_type(a) # revealed: LiteralString
@@ -281,7 +281,7 @@ reveal_type(b) # revealed: Unknown
### Unicode escape (1)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "\u9E6C"
reveal_type(a) # revealed: LiteralString
@@ -291,7 +291,7 @@ reveal_type(b) # revealed: Unknown
### Unicode escape (2)
```py
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
# TODO: Add diagnostic (need more values to unpack)
(a, b) = "\U0010FFFF"
reveal_type(a) # revealed: LiteralString
@@ -306,273 +306,3 @@ reveal_type(b) # revealed: Unknown
reveal_type(a) # revealed: LiteralString
reveal_type(b) # revealed: LiteralString
```
## Union
### Same types
Union of two tuples of equal length and each element is of the same type.
```py
def _(arg: tuple[int, int] | tuple[int, int]):
(a, b) = arg
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Mixed types (1)
Union of two tuples of equal length and one element differs in its type.
```py
def _(arg: tuple[int, int] | tuple[int, str]):
a, b = arg
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int | str
```
### Mixed types (2)
Union of two tuples of equal length and both the element types are different.
```py
def _(arg: tuple[int, str] | tuple[str, int]):
a, b = arg
reveal_type(a) # revealed: int | str
reveal_type(b) # revealed: str | int
```
### Mixed types (3)
Union of three tuples of equal length and various combination of element types:
1. All same types
1. One different type
1. All different types
```py
def _(arg: tuple[int, int, int] | tuple[int, str, bytes] | tuple[int, int, str]):
a, b, c = arg
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int | str
reveal_type(c) # revealed: int | bytes | str
```
### Nested
```py
def _(arg: tuple[int, tuple[str, bytes]] | tuple[tuple[int, bytes], Literal["ab"]]):
a, (b, c) = arg
reveal_type(a) # revealed: int | tuple[int, bytes]
reveal_type(b) # revealed: str
reveal_type(c) # revealed: bytes | LiteralString
```
### Starred expression
```py
def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
a, *b, c = arg
reveal_type(a) # revealed: int
# TODO: Should be `list[bytes | int | str]`
reveal_type(b) # revealed: @Todo(starred unpacking)
reveal_type(c) # revealed: int | bytes
```
### Size mismatch (1)
```py
def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 5)"
a, b = arg
reveal_type(a) # revealed: int
reveal_type(b) # revealed: bytes | int
```
### Size mismatch (2)
```py
def _(arg: tuple[int, bytes] | tuple[int, str]):
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
a, b, c = arg
reveal_type(a) # revealed: int
reveal_type(b) # revealed: bytes | str
reveal_type(c) # revealed: Unknown
```
### Same literal types
```py
def _(flag: bool):
if flag:
value = (1, 2)
else:
value = (3, 4)
a, b = value
reveal_type(a) # revealed: Literal[1, 3]
reveal_type(b) # revealed: Literal[2, 4]
```
### Mixed literal types
```py
def _(flag: bool):
if flag:
value = (1, 2)
else:
value = ("a", "b")
a, b = value
reveal_type(a) # revealed: Literal[1] | Literal["a"]
reveal_type(b) # revealed: Literal[2] | Literal["b"]
```
### Typing literal
```py
from typing import Literal
def _(arg: tuple[int, int] | Literal["ab"]):
a, b = arg
reveal_type(a) # revealed: int | LiteralString
reveal_type(b) # revealed: int | LiteralString
```
### Custom iterator (1)
```py
class Iterator:
def __next__(self) -> tuple[int, int] | tuple[int, str]:
return (1, 2)
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
((a, b), c) = Iterable()
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int | str
reveal_type(c) # revealed: tuple[int, int] | tuple[int, str]
```
### Custom iterator (2)
```py
class Iterator:
def __next__(self) -> bytes:
return b""
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
def _(arg: tuple[int, str] | Iterable):
a, b = arg
reveal_type(a) # revealed: int | bytes
reveal_type(b) # revealed: str | bytes
```
## For statement
Unpacking in a `for` statement.
### Same types
```py
def _(arg: tuple[tuple[int, int], tuple[int, int]]):
for a, b in arg:
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Mixed types (1)
```py
def _(arg: tuple[tuple[int, int], tuple[int, str]]):
for a, b in arg:
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int | str
```
### Mixed types (2)
```py
def _(arg: tuple[tuple[int, str], tuple[str, int]]):
for a, b in arg:
reveal_type(a) # revealed: int | str
reveal_type(b) # revealed: str | int
```
### Mixed types (3)
```py
def _(arg: tuple[tuple[int, int, int], tuple[int, str, bytes], tuple[int, int, str]]):
for a, b, c in arg:
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int | str
reveal_type(c) # revealed: int | bytes | str
```
### Same literal values
```py
for a, b in ((1, 2), (3, 4)):
reveal_type(a) # revealed: Literal[1, 3]
reveal_type(b) # revealed: Literal[2, 4]
```
### Mixed literal values (1)
```py
for a, b in ((1, 2), ("a", "b")):
reveal_type(a) # revealed: Literal[1] | Literal["a"]
reveal_type(b) # revealed: Literal[2] | Literal["b"]
```
### Mixed literals values (2)
```py
# error: "Object of type `Literal[1]` is not iterable"
# error: "Object of type `Literal[2]` is not iterable"
# error: "Object of type `Literal[4]` is not iterable"
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c"):
reveal_type(a) # revealed: Unknown | Literal[3, 5] | LiteralString
reveal_type(b) # revealed: Unknown | Literal["a", "b"]
```
### Custom iterator (1)
```py
class Iterator:
def __next__(self) -> tuple[int, int]:
return (1, 2)
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
for a, b in Iterable():
reveal_type(a) # revealed: int
reveal_type(b) # revealed: int
```
### Custom iterator (2)
```py
class Iterator:
def __next__(self) -> bytes:
return b""
class Iterable:
def __iter__(self) -> Iterator:
return Iterator()
def _(arg: tuple[tuple[int, str], Iterable]):
for a, b in arg:
reveal_type(a) # revealed: int | bytes
reveal_type(b) # revealed: str | bytes
```

View File

@@ -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(async with statement)
```

View File

@@ -43,7 +43,7 @@ impl<T> AstNodeRef<T> {
}
/// Returns a reference to the wrapped node.
pub const fn node(&self) -> &T {
pub fn node(&self) -> &T {
// SAFETY: Holding on to `parsed` ensures that the AST to which `node` belongs is still
// alive and not moved.
unsafe { self.node.as_ref() }

View File

@@ -1,4 +1,4 @@
use crate::lint::{LintRegistry, RuleSelection};
use crate::lint::RuleSelection;
use ruff_db::files::File;
use ruff_db::{Db as SourceDb, Upcast};
@@ -8,8 +8,6 @@ pub trait Db: SourceDb + Upcast<dyn SourceDb> {
fn is_file_open(&self, file: File) -> bool;
fn rule_selection(&self) -> &RuleSelection;
fn lint_registry(&self) -> &LintRegistry;
}
#[cfg(test)]
@@ -18,10 +16,10 @@ pub(crate) mod tests {
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::{default_lint_registry, ProgramSettings, PythonPlatform};
use crate::{default_lint_registry, ProgramSettings};
use super::Db;
use crate::lint::{LintRegistry, RuleSelection};
use crate::lint::RuleSelection;
use anyhow::Context;
use ruff_db::files::{File, Files};
use ruff_db::system::{DbWithTestSystem, System, SystemPathBuf, TestSystem};
@@ -47,7 +45,7 @@ pub(crate) mod tests {
vendored: red_knot_vendored::file_system().clone(),
events: Arc::default(),
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
rule_selection: Arc::new(RuleSelection::from_registry(&default_lint_registry())),
}
}
@@ -114,10 +112,6 @@ pub(crate) mod tests {
fn rule_selection(&self) -> &RuleSelection {
&self.rule_selection
}
fn lint_registry(&self) -> &LintRegistry {
default_lint_registry()
}
}
#[salsa::db]
@@ -133,8 +127,6 @@ pub(crate) mod tests {
pub(crate) struct TestDbBuilder<'a> {
/// Target Python version
python_version: PythonVersion,
/// Target Python platform
python_platform: PythonPlatform,
/// Path to a custom typeshed directory
custom_typeshed: Option<SystemPathBuf>,
/// Path and content pairs for files that should be present
@@ -145,7 +137,6 @@ pub(crate) mod tests {
pub(crate) fn new() -> Self {
Self {
python_version: PythonVersion::default(),
python_platform: PythonPlatform::default(),
custom_typeshed: None,
files: vec![],
}
@@ -182,7 +173,6 @@ pub(crate) mod tests {
&db,
&ProgramSettings {
python_version: self.python_version,
python_platform: self.python_platform,
search_paths,
},
)

View File

@@ -3,12 +3,10 @@ use std::hash::BuildHasherDefault;
use rustc_hash::FxHasher;
use crate::lint::{LintRegistry, LintRegistryBuilder};
use crate::suppression::{INVALID_IGNORE_COMMENT, UNKNOWN_RULE, UNUSED_IGNORE_COMMENT};
pub use db::Db;
pub use module_name::ModuleName;
pub use module_resolver::{resolve_module, system_module_search_paths, KnownModule, Module};
pub use module_resolver::{resolve_module, system_module_search_paths, Module};
pub use program::{Program, ProgramSettings, SearchPathSettings, SitePackages};
pub use python_platform::PythonPlatform;
pub use python_version::PythonVersion;
pub use semantic_model::{HasTy, SemanticModel};
@@ -19,36 +17,26 @@ mod module_name;
mod module_resolver;
mod node_key;
mod program;
mod python_platform;
mod python_version;
pub mod semantic_index;
mod semantic_model;
pub(crate) mod site_packages;
mod stdlib;
mod suppression;
pub(crate) mod symbol;
pub mod types;
mod unpack;
mod util;
mod visibility_constraints;
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
/// Returns the default registry with all known semantic lints.
pub fn default_lint_registry() -> &'static LintRegistry {
static REGISTRY: std::sync::LazyLock<LintRegistry> = std::sync::LazyLock::new(|| {
let mut registry = LintRegistryBuilder::default();
register_lints(&mut registry);
registry.build()
});
&REGISTRY
/// Creates a new registry with all known semantic lints.
pub fn default_lint_registry() -> LintRegistry {
let mut registry = LintRegistryBuilder::default();
register_lints(&mut registry);
registry.build()
}
/// Register all known semantic lints.
pub fn register_lints(registry: &mut LintRegistryBuilder) {
types::register_lints(registry);
registry.register_lint(&UNUSED_IGNORE_COMMENT);
registry.register_lint(&UNKNOWN_RULE);
registry.register_lint(&INVALID_IGNORE_COMMENT);
}

View File

@@ -321,7 +321,7 @@ impl LintRegistryBuilder {
}
}
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug)]
pub struct LintRegistry {
lints: Vec<LintId>,
by_name: FxHashMap<&'static str, LintEntry>,
@@ -374,7 +374,7 @@ impl LintRegistry {
}
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[derive(Error, Debug, Clone)]
pub enum GetLintError {
/// The name maps to this removed lint.
#[error("lint {0} has been removed")]
@@ -385,7 +385,7 @@ pub enum GetLintError {
Unknown(String),
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[derive(Debug, PartialEq, Eq)]
pub enum LintEntry {
/// An existing lint rule. Can be in preview, stable or deprecated.
Lint(LintId),
@@ -444,11 +444,6 @@ impl RuleSelection {
self.lints.get(&lint).copied()
}
/// Returns `true` if the `lint` is enabled.
pub fn is_enabled(&self, lint: LintId) -> bool {
self.severity(lint).is_some()
}
/// Enables `lint` and configures with the given `severity`.
///
/// Overrides any previous configuration for the lint.

View File

@@ -1,6 +1,6 @@
use std::iter::FusedIterator;
pub use module::{KnownModule, Module};
pub use module::Module;
pub use resolver::resolve_module;
pub(crate) use resolver::{file_to_module, SearchPaths};
use ruff_db::system::SystemPath;

View File

@@ -19,14 +19,12 @@ impl Module {
search_path: SearchPath,
file: File,
) -> Self {
let known = KnownModule::try_from_search_path_and_name(&search_path, &name);
Self {
inner: Arc::new(ModuleInner {
name,
kind,
search_path,
file,
known,
}),
}
}
@@ -41,16 +39,6 @@ impl Module {
self.inner.file
}
/// Is this a module that we special-case somehow? If so, which one?
pub fn known(&self) -> Option<KnownModule> {
self.inner.known
}
/// Does this module represent the given known module?
pub fn is_known(&self, known_module: KnownModule) -> bool {
self.known() == Some(known_module)
}
/// The search path from which the module was resolved.
pub(crate) fn search_path(&self) -> &SearchPath {
&self.inner.search_path
@@ -79,7 +67,6 @@ struct ModuleInner {
kind: ModuleKind,
search_path: SearchPath,
file: File,
known: Option<KnownModule>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@@ -96,62 +83,3 @@ impl ModuleKind {
matches!(self, ModuleKind::Package)
}
}
/// Enumeration of various core stdlib modules in which important types are located
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum KnownModule {
Builtins,
Types,
Typeshed,
TypingExtensions,
Typing,
Sys,
#[allow(dead_code)]
Abc, // currently only used in tests
Collections,
}
impl KnownModule {
pub const fn as_str(self) -> &'static str {
match self {
Self::Builtins => "builtins",
Self::Types => "types",
Self::Typing => "typing",
Self::Typeshed => "_typeshed",
Self::TypingExtensions => "typing_extensions",
Self::Sys => "sys",
Self::Abc => "abc",
Self::Collections => "collections",
}
}
pub fn name(self) -> ModuleName {
let self_as_str = self.as_str();
ModuleName::new_static(self_as_str)
.unwrap_or_else(|| panic!("{self_as_str} should be a valid module name!"))
}
pub(crate) fn try_from_search_path_and_name(
search_path: &SearchPath,
name: &ModuleName,
) -> Option<Self> {
if !search_path.is_standard_library() {
return None;
}
match name.as_str() {
"builtins" => Some(Self::Builtins),
"types" => Some(Self::Types),
"typing" => Some(Self::Typing),
"_typeshed" => Some(Self::Typeshed),
"typing_extensions" => Some(Self::TypingExtensions),
"sys" => Some(Self::Sys),
"abc" => Some(Self::Abc),
"collections" => Some(Self::Collections),
_ => None,
}
}
pub const fn is_typing(self) -> bool {
matches!(self, Self::Typing)
}
}

View File

@@ -721,8 +721,8 @@ mod tests {
use crate::module_name::ModuleName;
use crate::module_resolver::module::ModuleKind;
use crate::module_resolver::testing::{FileSpec, MockedTypeshed, TestCase, TestCaseBuilder};
use crate::ProgramSettings;
use crate::PythonVersion;
use crate::{ProgramSettings, PythonPlatform};
use super::*;
@@ -1262,7 +1262,7 @@ mod tests {
fn symlink() -> anyhow::Result<()> {
use anyhow::Context;
use crate::{program::Program, PythonPlatform};
use crate::program::Program;
use ruff_db::system::{OsSystem, SystemPath};
use crate::db::tests::TestDb;
@@ -1296,7 +1296,6 @@ mod tests {
&db,
&ProgramSettings {
python_version: PythonVersion::PY38,
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: src.clone(),
@@ -1802,7 +1801,6 @@ not_a_directory
&db,
&ProgramSettings {
python_version: PythonVersion::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: SystemPathBuf::from("/src"),

View File

@@ -4,7 +4,7 @@ use ruff_db::vendored::VendoredPathBuf;
use crate::db::tests::TestDb;
use crate::program::{Program, SearchPathSettings};
use crate::python_version::PythonVersion;
use crate::{ProgramSettings, PythonPlatform, SitePackages};
use crate::{ProgramSettings, SitePackages};
/// A test case for the module resolver.
///
@@ -101,7 +101,6 @@ pub(crate) struct UnspecifiedTypeshed;
pub(crate) struct TestCaseBuilder<T> {
typeshed_option: T,
python_version: PythonVersion,
python_platform: PythonPlatform,
first_party_files: Vec<FileSpec>,
site_packages_files: Vec<FileSpec>,
}
@@ -148,7 +147,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
Self {
typeshed_option: UnspecifiedTypeshed,
python_version: PythonVersion::default(),
python_platform: PythonPlatform::default(),
first_party_files: vec![],
site_packages_files: vec![],
}
@@ -159,14 +157,12 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
let TestCaseBuilder {
typeshed_option: _,
python_version,
python_platform,
first_party_files,
site_packages_files,
} = self;
TestCaseBuilder {
typeshed_option: VendoredTypeshed,
python_version,
python_platform,
first_party_files,
site_packages_files,
}
@@ -180,7 +176,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
let TestCaseBuilder {
typeshed_option: _,
python_version,
python_platform,
first_party_files,
site_packages_files,
} = self;
@@ -188,7 +183,6 @@ impl TestCaseBuilder<UnspecifiedTypeshed> {
TestCaseBuilder {
typeshed_option: typeshed,
python_version,
python_platform,
first_party_files,
site_packages_files,
}
@@ -218,7 +212,6 @@ impl TestCaseBuilder<MockedTypeshed> {
let TestCaseBuilder {
typeshed_option,
python_version,
python_platform,
first_party_files,
site_packages_files,
} = self;
@@ -234,7 +227,6 @@ impl TestCaseBuilder<MockedTypeshed> {
&db,
&ProgramSettings {
python_version,
python_platform,
search_paths: SearchPathSettings {
extra_paths: vec![],
src_root: src.clone(),
@@ -277,7 +269,6 @@ impl TestCaseBuilder<VendoredTypeshed> {
let TestCaseBuilder {
typeshed_option: VendoredTypeshed,
python_version,
python_platform,
first_party_files,
site_packages_files,
} = self;
@@ -292,7 +283,6 @@ impl TestCaseBuilder<VendoredTypeshed> {
&db,
&ProgramSettings {
python_version,
python_platform,
search_paths: SearchPathSettings {
site_packages: SitePackages::Known(vec![site_packages.clone()]),
..SearchPathSettings::new(src.clone())

View File

@@ -1,4 +1,3 @@
use crate::python_platform::PythonPlatform;
use crate::python_version::PythonVersion;
use anyhow::Context;
use salsa::Durability;
@@ -13,8 +12,6 @@ use crate::Db;
pub struct Program {
pub python_version: PythonVersion,
pub python_platform: PythonPlatform,
#[return_ref]
pub(crate) search_paths: SearchPaths,
}
@@ -23,7 +20,6 @@ impl Program {
pub fn from_settings(db: &dyn Db, settings: &ProgramSettings) -> anyhow::Result<Self> {
let ProgramSettings {
python_version,
python_platform,
search_paths,
} = settings;
@@ -32,11 +28,9 @@ impl Program {
let search_paths = SearchPaths::from_settings(db, search_paths)
.with_context(|| "Invalid search path settings")?;
Ok(
Program::builder(*python_version, python_platform.clone(), search_paths)
.durability(Durability::HIGH)
.new(db),
)
Ok(Program::builder(settings.python_version, search_paths)
.durability(Durability::HIGH)
.new(db))
}
pub fn update_search_paths(
@@ -63,7 +57,6 @@ impl Program {
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ProgramSettings {
pub python_version: PythonVersion,
pub python_platform: PythonPlatform,
pub search_paths: SearchPathSettings,
}

View File

@@ -1,19 +0,0 @@
/// The target platform to assume when resolving types.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
pub enum PythonPlatform {
/// Do not make any assumptions about the target platform.
#[default]
All,
/// Assume a specific target platform like `linux`, `darwin` or `win32`.
///
/// We use a string (instead of individual enum variants), as the set of possible platforms
/// may change over time. See <https://docs.python.org/3/library/sys.html#sys.platform> for
/// some known platform identifiers.
#[cfg_attr(feature = "serde", serde(untagged))]
Identifier(String),
}

View File

@@ -29,8 +29,7 @@ pub mod symbol;
mod use_def;
pub(crate) use self::use_def::{
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationWithConstraint,
DeclarationsIterator, ScopedVisibilityConstraintId,
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator,
};
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), FxBuildHasher>;
@@ -379,12 +378,14 @@ mod tests {
impl UseDefMap<'_> {
fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
self.public_bindings(symbol)
.find_map(|constrained_binding| constrained_binding.binding)
.next()
.map(|constrained_binding| constrained_binding.binding)
}
fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
self.bindings_at_use(use_id)
.find_map(|constrained_binding| constrained_binding.binding)
.next()
.map(|constrained_binding| constrained_binding.binding)
}
}

View File

@@ -9,12 +9,12 @@ use ruff_index::IndexVec;
use ruff_python_ast as ast;
use ruff_python_ast::name::Name;
use ruff_python_ast::visitor::{walk_expr, walk_pattern, walk_stmt, Visitor};
use ruff_python_ast::{BoolOp, Expr};
use crate::ast_node_ref::AstNodeRef;
use crate::module_name::ModuleName;
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
use crate::semantic_index::ast_ids::AstIdsBuilder;
use crate::semantic_index::constraint::PatternConstraintKind;
use crate::semantic_index::definition::{
AssignmentDefinitionNodeRef, ComprehensionDefinitionNodeRef, Definition, DefinitionNodeKey,
DefinitionNodeRef, ForStmtDefinitionNodeRef, ImportFromDefinitionNodeRef,
@@ -24,12 +24,9 @@ use crate::semantic_index::symbol::{
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
SymbolTableBuilder,
};
use crate::semantic_index::use_def::{
FlowSnapshot, ScopedConstraintId, ScopedVisibilityConstraintId, UseDefMapBuilder,
};
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
use crate::semantic_index::SemanticIndex;
use crate::unpack::{Unpack, UnpackValue};
use crate::visibility_constraints::VisibilityConstraint;
use crate::unpack::Unpack;
use crate::Db;
use super::constraint::{Constraint, ConstraintNode, PatternConstraint};
@@ -288,7 +285,11 @@ impl<'db> SemanticIndexBuilder<'db> {
constraint
}
fn build_constraint(&mut self, constraint_node: &ast::Expr) -> Constraint<'db> {
fn record_constraint(&mut self, constraint: Constraint<'db>) {
self.current_use_def_map_mut().record_constraint(constraint);
}
fn build_constraint(&mut self, constraint_node: &Expr) -> Constraint<'db> {
let expression = self.add_standalone_expression(constraint_node);
Constraint {
node: ConstraintNode::Expression(expression),
@@ -296,89 +297,12 @@ impl<'db> SemanticIndexBuilder<'db> {
}
}
/// Adds a new constraint to the list of all constraints, but does not record it. Returns the
/// constraint ID for later recording using [`SemanticIndexBuilder::record_constraint_id`].
fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
self.current_use_def_map_mut().add_constraint(constraint)
}
/// Negates a constraint and adds it to the list of all constraints, does not record it.
fn add_negated_constraint(
&mut self,
constraint: Constraint<'db>,
) -> (Constraint<'db>, ScopedConstraintId) {
let negated = Constraint {
node: constraint.node,
is_positive: false,
};
let id = self.current_use_def_map_mut().add_constraint(negated);
(negated, id)
}
/// Records a previously added constraint by adding it to all live bindings.
fn record_constraint_id(&mut self, constraint: ScopedConstraintId) {
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) {
self.current_use_def_map_mut()
.record_constraint_id(constraint);
}
/// Adds and records a constraint, i.e. adds it to all live bindings.
fn record_constraint(&mut self, constraint: Constraint<'db>) {
self.current_use_def_map_mut().record_constraint(constraint);
}
/// Negates the given constraint and then adds it to all live bindings.
fn record_negated_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
let (_, id) = self.add_negated_constraint(constraint);
self.record_constraint_id(id);
id
}
/// Adds a new visibility constraint, but does not record it. Returns the constraint ID
/// for later recording using [`SemanticIndexBuilder::record_visibility_constraint_id`].
fn add_visibility_constraint(
&mut self,
constraint: VisibilityConstraint<'db>,
) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut()
.add_visibility_constraint(constraint)
}
/// Records a previously added visibility constraint by applying it to all live bindings
/// and declarations.
fn record_visibility_constraint_id(&mut self, constraint: ScopedVisibilityConstraintId) {
self.current_use_def_map_mut()
.record_visibility_constraint_id(constraint);
}
/// Negates the given visibility constraint and then adds it to all live bindings and declarations.
fn record_negated_visibility_constraint(
&mut self,
constraint: ScopedVisibilityConstraintId,
) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut()
.record_visibility_constraint(VisibilityConstraint::VisibleIfNot(constraint))
}
/// Records a visibility constraint by applying it to all live bindings and declarations.
fn record_visibility_constraint(
&mut self,
constraint: Constraint<'db>,
) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut()
.record_visibility_constraint(VisibilityConstraint::VisibleIf(constraint))
}
/// Records a [`VisibilityConstraint::Ambiguous`] constraint.
fn record_ambiguous_visibility(&mut self) -> ScopedVisibilityConstraintId {
self.current_use_def_map_mut()
.record_visibility_constraint(VisibilityConstraint::Ambiguous)
}
/// Simplifies (resets) visibility constraints on all live bindings and declarations that did
/// not see any new definitions since the given snapshot.
fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) {
self.current_use_def_map_mut()
.simplify_visibility_constraints(snapshot);
.record_constraint(Constraint {
node: constraint.node,
is_positive: false,
});
}
fn push_assignment(&mut self, assignment: CurrentAssignment<'db>) {
@@ -400,37 +324,30 @@ impl<'db> SemanticIndexBuilder<'db> {
fn add_pattern_constraint(
&mut self,
subject: Expression<'db>,
subject: &ast::Expr,
pattern: &ast::Pattern,
guard: Option<&ast::Expr>,
) -> Constraint<'db> {
let guard = guard.map(|guard| self.add_standalone_expression(guard));
let kind = match pattern {
ast::Pattern::MatchValue(pattern) => {
let value = self.add_standalone_expression(&pattern.value);
PatternConstraintKind::Value(value, guard)
}
ast::Pattern::MatchSingleton(singleton) => {
PatternConstraintKind::Singleton(singleton.value, guard)
}
_ => PatternConstraintKind::Unsupported,
) -> PatternConstraint<'db> {
#[allow(unsafe_code)]
let (subject, pattern) = unsafe {
(
AstNodeRef::new(self.module.clone(), subject),
AstNodeRef::new(self.module.clone(), pattern),
)
};
let pattern_constraint = PatternConstraint::new(
self.db,
self.file,
self.current_scope(),
subject,
kind,
pattern,
countme::Count::default(),
);
let constraint = Constraint {
node: ConstraintNode::Pattern(pattern_constraint),
is_positive: true,
};
self.current_use_def_map_mut().record_constraint(constraint);
constraint
self.current_use_def_map_mut()
.record_constraint(Constraint {
node: ConstraintNode::Pattern(pattern_constraint),
is_positive: true,
});
pattern_constraint
}
/// Record an expression that needs to be a Salsa ingredient, because we need to infer its type
@@ -809,7 +726,7 @@ where
unsafe {
AstNodeRef::new(self.module.clone(), target)
},
UnpackValue::Assign(value),
value,
countme::Count::default(),
)),
})
@@ -882,10 +799,6 @@ where
let constraint = self.record_expression_constraint(&node.test);
let mut constraints = vec![constraint];
self.visit_body(&node.body);
let visibility_constraint_id = self.record_visibility_constraint(constraint);
let mut vis_constraints = vec![visibility_constraint_id];
let mut post_clauses: Vec<FlowSnapshot> = vec![];
let elif_else_clauses = node
.elif_else_clauses
@@ -912,31 +825,15 @@ where
for constraint in &constraints {
self.record_negated_constraint(*constraint);
}
let elif_constraint = if let Some(elif_test) = clause_test {
if let Some(elif_test) = clause_test {
self.visit_expr(elif_test);
let constraint = self.record_expression_constraint(elif_test);
constraints.push(constraint);
Some(constraint)
} else {
None
};
constraints.push(self.record_expression_constraint(elif_test));
}
self.visit_body(clause_body);
for id in &vis_constraints {
self.record_negated_visibility_constraint(*id);
}
if let Some(elif_constraint) = elif_constraint {
let id = self.record_visibility_constraint(elif_constraint);
vis_constraints.push(id);
}
}
for post_clause_state in post_clauses {
self.flow_merge(post_clause_state);
}
self.simplify_visibility_constraints(pre_if);
}
ast::Stmt::While(ast::StmtWhile {
test,
@@ -959,8 +856,6 @@ where
self.visit_body(body);
self.set_inside_loop(outer_loop_state);
let vis_constraint_id = self.record_visibility_constraint(constraint);
// Get the break states from the body of this loop, and restore the saved outer
// ones.
let break_states =
@@ -968,21 +863,15 @@ 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.clone());
self.flow_merge(pre_loop);
self.record_negated_constraint(constraint);
self.visit_body(orelse);
self.record_negated_visibility_constraint(vis_constraint_id);
// 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 {
let snapshot = self.flow_snapshot();
self.flow_restore(break_state);
self.record_visibility_constraint(constraint);
self.flow_merge(snapshot);
self.flow_merge(break_state);
}
self.simplify_visibility_constraints(pre_loop);
}
ast::Stmt::With(ast::StmtWith {
items,
@@ -1020,47 +909,16 @@ where
orelse,
},
) => {
debug_assert_eq!(&self.current_assignments, &[]);
let iter_expr = self.add_standalone_expression(iter);
self.add_standalone_expression(iter);
self.visit_expr(iter);
self.record_ambiguous_visibility();
let pre_loop = self.flow_snapshot();
let saved_break_states = std::mem::take(&mut self.loop_break_states);
let current_assignment = match &**target {
ast::Expr::List(_) | ast::Expr::Tuple(_) => Some(CurrentAssignment::For {
node: for_stmt,
first: true,
unpack: Some(Unpack::new(
self.db,
self.file,
self.current_scope(),
#[allow(unsafe_code)]
unsafe {
AstNodeRef::new(self.module.clone(), target)
},
UnpackValue::Iterable(iter_expr),
countme::Count::default(),
)),
}),
ast::Expr::Name(_) => Some(CurrentAssignment::For {
node: for_stmt,
unpack: None,
first: false,
}),
_ => None,
};
if let Some(current_assignment) = current_assignment {
self.push_assignment(current_assignment);
}
debug_assert_eq!(&self.current_assignments, &[]);
self.push_assignment(for_stmt.into());
self.visit_expr(target);
if current_assignment.is_some() {
self.pop_assignment();
}
self.pop_assignment();
// TODO: Definitions created by loop variables
// (and definitions created inside the body)
@@ -1089,63 +947,32 @@ where
cases,
range: _,
}) => {
let subject_expr = self.add_standalone_expression(subject);
self.add_standalone_expression(subject);
self.visit_expr(subject);
let after_subject = self.flow_snapshot();
let Some((first, remaining)) = cases.split_first() else {
return;
};
let first_constraint_id = self.add_pattern_constraint(
subject_expr,
&first.pattern,
first.guard.as_deref(),
);
self.add_pattern_constraint(subject, &first.pattern);
self.visit_match_case(first);
let first_vis_constraint_id =
self.record_visibility_constraint(first_constraint_id);
let mut vis_constraints = vec![first_vis_constraint_id];
let mut post_case_snapshots = vec![];
for case in remaining {
post_case_snapshots.push(self.flow_snapshot());
self.flow_restore(after_subject.clone());
let constraint_id = self.add_pattern_constraint(
subject_expr,
&case.pattern,
case.guard.as_deref(),
);
self.add_pattern_constraint(subject, &case.pattern);
self.visit_match_case(case);
for id in &vis_constraints {
self.record_negated_visibility_constraint(*id);
}
let vis_constraint_id = self.record_visibility_constraint(constraint_id);
vis_constraints.push(vis_constraint_id);
}
// If there is no final wildcard match case, pretend there is one. This is similar to how
// we add an implicit `else` block in if-elif chains, in case it's not present.
for post_clause_state in post_case_snapshots {
self.flow_merge(post_clause_state);
}
if !cases
.last()
.is_some_and(|case| case.guard.is_none() && case.pattern.is_wildcard())
{
post_case_snapshots.push(self.flow_snapshot());
self.flow_restore(after_subject.clone());
for id in &vis_constraints {
self.record_negated_visibility_constraint(*id);
}
self.flow_merge(after_subject);
}
for post_clause_state in post_case_snapshots {
self.flow_merge(post_clause_state);
}
self.simplify_visibility_constraints(after_subject);
}
ast::Stmt::Try(ast::StmtTry {
body,
@@ -1155,8 +982,6 @@ where
is_star,
range: _,
}) => {
self.record_ambiguous_visibility();
// Save the state prior to visiting any of the `try` block.
//
// Potentially none of the `try` block could have been executed prior to executing
@@ -1311,18 +1136,12 @@ where
Some(CurrentAssignment::AugAssign(aug_assign)) => {
self.add_definition(symbol, aug_assign);
}
Some(CurrentAssignment::For {
node,
first,
unpack,
}) => {
Some(CurrentAssignment::For(node)) => {
self.add_definition(
symbol,
ForStmtDefinitionNodeRef {
unpack,
first,
iterable: &node.iter,
name: name_node,
target: name_node,
is_async: node.is_async,
},
);
@@ -1358,9 +1177,7 @@ where
}
}
if let Some(
CurrentAssignment::Assign { first, .. } | CurrentAssignment::For { first, .. },
) = self.current_assignment_mut()
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
{
*first = false;
}
@@ -1405,19 +1222,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 constraint = self.record_expression_constraint(test);
self.visit_expr(body);
let visibility_constraint = self.record_visibility_constraint(constraint);
let post_body = self.flow_snapshot();
self.flow_restore(pre_if.clone());
self.flow_restore(pre_if);
self.record_negated_constraint(constraint);
self.visit_expr(orelse);
self.record_negated_visibility_constraint(visibility_constraint);
self.flow_merge(post_body);
self.simplify_visibility_constraints(pre_if);
}
ast::Expr::ListComp(
list_comprehension @ ast::ExprListComp {
@@ -1474,55 +1291,27 @@ where
range: _,
op,
}) => {
let pre_op = self.flow_snapshot();
// TODO detect statically known truthy or falsy values (via type inference, not naive
// AST inspection, so we can't simplify here, need to record test expression for
// later checking)
let mut snapshots = vec![];
let mut visibility_constraints = vec![];
for (index, value) in values.iter().enumerate() {
self.visit_expr(value);
for vid in &visibility_constraints {
self.record_visibility_constraint_id(*vid);
}
// For the last value, we don't need to model control flow. There is short-circuiting
// anymore.
// In the last value we don't need to take a snapshot nor add a constraint
if index < values.len() - 1 {
let constraint = self.build_constraint(value);
let (constraint, constraint_id) = match op {
ast::BoolOp::And => (constraint, self.add_constraint(constraint)),
ast::BoolOp::Or => self.add_negated_constraint(constraint),
};
let visibility_constraint = self
.add_visibility_constraint(VisibilityConstraint::VisibleIf(constraint));
let after_expr = self.flow_snapshot();
// We first model the short-circuiting behavior. We take the short-circuit
// path here if all of the previous short-circuit paths were not taken, so
// we record all previously existing visibility constraints, and negate the
// one for the current expression.
for vid in &visibility_constraints {
self.record_visibility_constraint_id(*vid);
}
self.record_negated_visibility_constraint(visibility_constraint);
// Snapshot is taken after visiting the expression but before adding the constraint.
snapshots.push(self.flow_snapshot());
// Then we model the non-short-circuiting behavior. Here, we need to delay
// the application of the visibility constraint until after the expression
// has been evaluated, so we only push it onto the stack here.
self.flow_restore(after_expr);
self.record_constraint_id(constraint_id);
visibility_constraints.push(visibility_constraint);
let constraint = self.build_constraint(value);
match op {
BoolOp::And => self.record_constraint(constraint),
BoolOp::Or => self.record_negated_constraint(constraint),
}
}
}
for snapshot in snapshots {
self.flow_merge(snapshot);
}
self.simplify_visibility_constraints(pre_op);
}
_ => {
walk_expr(self, expr);
@@ -1602,11 +1391,7 @@ enum CurrentAssignment<'a> {
},
AnnAssign(&'a ast::StmtAnnAssign),
AugAssign(&'a ast::StmtAugAssign),
For {
node: &'a ast::StmtFor,
first: bool,
unpack: Option<Unpack<'a>>,
},
For(&'a ast::StmtFor),
Named(&'a ast::ExprNamed),
Comprehension {
node: &'a ast::Comprehension,
@@ -1630,6 +1415,12 @@ impl<'a> From<&'a ast::StmtAugAssign> for CurrentAssignment<'a> {
}
}
impl<'a> From<&'a ast::StmtFor> for CurrentAssignment<'a> {
fn from(value: &'a ast::StmtFor) -> Self {
Self::For(value)
}
}
impl<'a> From<&'a ast::ExprNamed> for CurrentAssignment<'a> {
fn from(value: &'a ast::ExprNamed) -> Self {
Self::Named(value)

View File

@@ -1,6 +1,7 @@
use ruff_db::files::File;
use ruff_python_ast::Singleton;
use ruff_python_ast as ast;
use crate::ast_node_ref::AstNodeRef;
use crate::db::Db;
use crate::semantic_index::expression::Expression;
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
@@ -17,14 +18,6 @@ pub(crate) enum ConstraintNode<'db> {
Pattern(PatternConstraint<'db>),
}
/// Pattern kinds for which we support type narrowing and/or static visibility analysis.
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum PatternConstraintKind<'db> {
Singleton(Singleton, Option<Expression<'db>>),
Value(Expression<'db>, Option<Expression<'db>>),
Unsupported,
}
#[salsa::tracked]
pub(crate) struct PatternConstraint<'db> {
#[id]
@@ -35,11 +28,11 @@ pub(crate) struct PatternConstraint<'db> {
#[no_eq]
#[return_ref]
pub(crate) subject: Expression<'db>,
pub(crate) subject: AstNodeRef<ast::Expr>,
#[no_eq]
#[return_ref]
pub(crate) kind: PatternConstraintKind<'db>,
pub(crate) pattern: AstNodeRef<ast::Pattern>,
#[no_eq]
count: countme::Count<PatternConstraint<'static>>,

View File

@@ -8,7 +8,7 @@ use crate::module_resolver::file_to_module;
use crate::node_key::NodeKey;
use crate::semantic_index::symbol::{FileScopeId, ScopeId, ScopedSymbolId};
use crate::unpack::Unpack;
use crate::{Db, KnownModule};
use crate::Db;
/// A definition of a symbol.
///
@@ -63,16 +63,17 @@ impl<'db> Definition<'db> {
}
pub(crate) fn is_builtin_definition(self, db: &'db dyn Db) -> bool {
file_to_module(db, self.file(db))
.is_some_and(|module| module.is_known(KnownModule::Builtins))
file_to_module(db, self.file(db)).is_some_and(|module| {
module.search_path().is_standard_library() && matches!(&**module.name(), "builtins")
})
}
/// Return true if this symbol was defined in the `typing` or `typing_extensions` modules
pub(crate) fn is_typing_definition(self, db: &'db dyn Db) -> bool {
matches!(
file_to_module(db, self.file(db)).and_then(|module| module.known()),
Some(KnownModule::Typing | KnownModule::TypingExtensions)
)
file_to_module(db, self.file(db)).is_some_and(|module| {
module.search_path().is_standard_library()
&& matches!(&**module.name(), "typing" | "typing_extensions")
})
}
}
@@ -225,10 +226,8 @@ pub(crate) struct WithItemDefinitionNodeRef<'a> {
#[derive(Copy, Clone, Debug)]
pub(crate) struct ForStmtDefinitionNodeRef<'a> {
pub(crate) unpack: Option<Unpack<'a>>,
pub(crate) iterable: &'a ast::Expr,
pub(crate) name: &'a ast::ExprName,
pub(crate) first: bool,
pub(crate) target: &'a ast::ExprName,
pub(crate) is_async: bool,
}
@@ -300,16 +299,12 @@ impl<'db> DefinitionNodeRef<'db> {
DefinitionKind::AugmentedAssignment(AstNodeRef::new(parsed, augmented_assignment))
}
DefinitionNodeRef::For(ForStmtDefinitionNodeRef {
unpack,
iterable,
name,
first,
target,
is_async,
}) => DefinitionKind::For(ForStmtDefinitionKind {
target: TargetKind::from(unpack),
iterable: AstNodeRef::new(parsed.clone(), iterable),
name: AstNodeRef::new(parsed, name),
first,
target: AstNodeRef::new(parsed, target),
is_async,
}),
DefinitionNodeRef::Comprehension(ComprehensionDefinitionNodeRef {
@@ -388,12 +383,10 @@ impl<'db> DefinitionNodeRef<'db> {
Self::AnnotatedAssignment(node) => node.into(),
Self::AugmentedAssignment(node) => node.into(),
Self::For(ForStmtDefinitionNodeRef {
unpack: _,
iterable: _,
name,
first: _,
target,
is_async: _,
}) => name.into(),
}) => target.into(),
Self::Comprehension(ComprehensionDefinitionNodeRef { target, .. }) => target.into(),
Self::VariadicPositionalParameter(node) => node.into(),
Self::VariadicKeywordParameter(node) => node.into(),
@@ -460,7 +453,7 @@ pub enum DefinitionKind<'db> {
Assignment(AssignmentDefinitionKind<'db>),
AnnotatedAssignment(AstNodeRef<ast::StmtAnnAssign>),
AugmentedAssignment(AstNodeRef<ast::StmtAugAssign>),
For(ForStmtDefinitionKind<'db>),
For(ForStmtDefinitionKind),
Comprehension(ComprehensionDefinitionKind),
VariadicPositionalParameter(AstNodeRef<ast::Parameter>),
VariadicKeywordParameter(AstNodeRef<ast::Parameter>),
@@ -473,14 +466,8 @@ pub enum DefinitionKind<'db> {
TypeVarTuple(AstNodeRef<ast::TypeParamTypeVarTuple>),
}
impl DefinitionKind<'_> {
/// Returns the [`TextRange`] of the definition target.
///
/// A definition target would mainly be the node representing the symbol being defined i.e.,
/// [`ast::ExprName`] or [`ast::Identifier`] but could also be other nodes.
///
/// This is mainly used for logging and debugging purposes.
pub(crate) fn target_range(&self) -> TextRange {
impl Ranged for DefinitionKind<'_> {
fn range(&self) -> TextRange {
match self {
DefinitionKind::Import(alias) => alias.range(),
DefinitionKind::ImportFrom(import) => import.alias().range(),
@@ -491,7 +478,7 @@ impl DefinitionKind<'_> {
DefinitionKind::Assignment(assignment) => assignment.name().range(),
DefinitionKind::AnnotatedAssignment(assign) => assign.target.range(),
DefinitionKind::AugmentedAssignment(aug_assign) => aug_assign.target.range(),
DefinitionKind::For(for_stmt) => for_stmt.name().range(),
DefinitionKind::For(for_stmt) => for_stmt.target().range(),
DefinitionKind::Comprehension(comp) => comp.target().range(),
DefinitionKind::VariadicPositionalParameter(parameter) => parameter.name.range(),
DefinitionKind::VariadicKeywordParameter(parameter) => parameter.name.range(),
@@ -504,7 +491,9 @@ impl DefinitionKind<'_> {
DefinitionKind::TypeVarTuple(type_var_tuple) => type_var_tuple.name.range(),
}
}
}
impl DefinitionKind<'_> {
pub(crate) fn category(&self) -> DefinitionCategory {
match self {
// functions, classes, and imports always bind, and we consider them declarations
@@ -677,32 +666,22 @@ impl WithItemDefinitionKind {
}
#[derive(Clone, Debug)]
pub struct ForStmtDefinitionKind<'db> {
target: TargetKind<'db>,
pub struct ForStmtDefinitionKind {
iterable: AstNodeRef<ast::Expr>,
name: AstNodeRef<ast::ExprName>,
first: bool,
target: AstNodeRef<ast::ExprName>,
is_async: bool,
}
impl<'db> ForStmtDefinitionKind<'db> {
impl ForStmtDefinitionKind {
pub(crate) fn iterable(&self) -> &ast::Expr {
self.iterable.node()
}
pub(crate) fn target(&self) -> TargetKind<'db> {
self.target
pub(crate) fn target(&self) -> &ast::ExprName {
self.target.node()
}
pub(crate) fn name(&self) -> &ast::ExprName {
self.name.node()
}
pub(crate) const fn is_first(&self) -> bool {
self.first
}
pub(crate) const fn is_async(&self) -> bool {
pub(crate) fn is_async(&self) -> bool {
self.is_async
}
}
@@ -778,6 +757,12 @@ impl From<&ast::StmtAugAssign> for DefinitionNodeKey {
}
}
impl From<&ast::StmtFor> for DefinitionNodeKey {
fn from(value: &ast::StmtFor) -> Self {
Self(NodeKey::from_node(value))
}
}
impl From<&ast::Parameter> for DefinitionNodeKey {
fn from(node: &ast::Parameter) -> Self {
Self(NodeKey::from_node(node))

View File

@@ -463,7 +463,10 @@ impl NodeWithScopeKind {
}
pub fn expect_function(&self) -> &ast::StmtFunctionDef {
self.as_function().expect("expected function")
match self {
Self::Function(function) => function.node(),
_ => panic!("expected function"),
}
}
pub fn expect_type_alias(&self) -> &ast::StmtTypeAlias {
@@ -472,13 +475,6 @@ impl NodeWithScopeKind {
_ => panic!("expected type alias"),
}
}
pub const fn as_function(&self) -> Option<&ast::StmtFunctionDef> {
match self {
Self::Function(function) => Some(function.node()),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]

View File

@@ -169,11 +169,17 @@
//! indexvecs in the [`UseDefMap`].
//!
//! There is another special kind of possible "definition" for a symbol: there might be a path from
//! the scope entry to a given use in which the symbol is never bound. We model this with a special
//! "unbound" definition (a `None` entry at the start of the `all_definitions` vector). If that
//! sentinel definition is present in the live bindings at a given use, it means that there is a
//! possible path through control flow in which that symbol is unbound. Similarly, if that sentinel
//! is present in the live declarations, it means that the symbol is (possibly) undeclared.
//! the scope entry to a given use in which the symbol is never bound.
//!
//! The simplest way to model "unbound" would be as a "binding" itself: the initial "binding" for
//! each symbol in a scope. But actually modeling it this way would unnecessarily increase the
//! number of [`Definition`]s that Salsa must track. Since "unbound" is special in that all symbols
//! share it, and it doesn't have any additional per-symbol state, and constraints are irrelevant
//! to it, we can represent it more efficiently: we use the `may_be_unbound` boolean on the
//! [`SymbolBindings`] struct. If this flag is `true` for a use of a symbol, it means the symbol
//! has a path to the use in which it is never bound. If this flag is `false`, it means we've
//! eliminated the possibility of unbound: every control flow path to the use includes a binding
//! for this symbol.
//!
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
//! constraint as they are encountered by the
@@ -184,13 +190,11 @@
//! end of the scope, it records the state for each symbol as the public definitions of that
//! symbol.
//!
//! Let's walk through the above example. Initially we do not have any record of `x`. When we add
//! the new symbol (before we process the first binding), we create a new undefined `SymbolState`
//! which has a single live binding (the "unbound" definition) and a single live declaration (the
//! "undeclared" definition). When we see `x = 1`, we record that as the sole live binding of `x`.
//! The "unbound" binding is no longer visible. Then we see `x = 2`, and we replace `x = 1` as the
//! sole live binding of `x`. When we get to `y = x`, we record that the live bindings for that use
//! of `x` are just the `x = 2` definition.
//! Let's walk through the above example. Initially we record for `x` that it has no bindings, and
//! may be unbound. When we see `x = 1`, we record that as the sole live binding of `x`, and flip
//! `may_be_unbound` to `false`. Then we see `x = 2`, and we replace `x = 1` as the sole live
//! binding of `x`. When we get to `y = x`, we record that the live bindings for that use of `x`
//! are just the `x = 2` definition.
//!
//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
//! happen regardless. Then we take a pre-branch snapshot of the current state for all symbols,
@@ -203,8 +207,8 @@
//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
//! failed and we didn't execute the `if` body. So we first reset the builder to the pre-if state,
//! using the snapshot we took previously (meaning we now have `x = 2` as the sole binding for `x`
//! again), and record a *negative* `flag` constraint for all live bindings (`x = 2`). We then
//! visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding of `x`.
//! again), then visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding
//! of `x`.
//!
//! Now we reach the end of the if/else, and want to visit the following code. The state here needs
//! to reflect that we might have gone through the `if` branch, or we might have gone through the
@@ -213,58 +217,18 @@
//! snapshot (which has `x = 3` as the only live binding). The result of this merge is that we now
//! have two live bindings of `x`: `x = 3` and `x = 4`.
//!
//! Another piece of information that the `UseDefMap` needs to provide are visibility constraints.
//! These are similar to the narrowing constraints, but apply to bindings and declarations within a
//! control flow path. Consider the following example:
//! ```py
//! x = 1
//! if test:
//! x = 2
//! y = "y"
//! ```
//! In principle, there are two possible control flow paths here. However, if we can statically
//! infer `test` to be always truthy or always falsy (that is, `__bool__` of `test` is of type
//! `Literal[True]` or `Literal[False]`), we can rule out one of the possible paths. To support
//! this feature, we record a visibility constraint of `test` to all live bindings and declarations
//! *after* visiting the body of the `if` statement. And we record a negative visibility constraint
//! `~test` to all live bindings/declarations in the (implicit) `else` branch. For the example
//! above, we would record the following visibility constraints (adding the implicit "unbound"
//! definitions for clarity):
//! ```py
//! x = <unbound> # not live, shadowed by `x = 1`
//! y = <unbound> # visibility constraint: ~test
//!
//! x = 1 # visibility constraint: ~test
//! if test:
//! x = 2 # visibility constraint: test
//! y = "y" # visibility constraint: test
//! ```
//! When we encounter a use of `x` after this `if` statement, we would record two live bindings: `x
//! = 1` with a constraint of `~test`, and `x = 2` with a constraint of `test`. In type inference,
//! when we iterate over all live bindings, we can evaluate these constraints to determine if a
//! particular binding is actually visible. For example, if `test` is always truthy, we only see
//! the `x = 2` binding. If `test` is always falsy, we only see the `x = 1` binding. And if the
//! `__bool__` method of `test` returns type `bool`, we can see both bindings.
//!
//! Note that we also record visibility constraints for the start of the scope. This is important
//! to determine if a symbol is definitely bound, possibly unbound, or definitely unbound. In the
//! example above, The `y = <unbound>` binding is constrained by `~test`, so `y` would only be
//! definitely-bound if `test` is always truthy.
//!
//! The [`UseDefMapBuilder`] itself just exposes methods for taking a snapshot, resetting to a
//! 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 self::symbol_state::{
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
};
pub(crate) use self::symbol_state::{ScopedConstraintId, ScopedVisibilityConstraintId};
use crate::semantic_index::ast_ids::ScopedUseId;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::symbol::ScopedSymbolId;
use crate::semantic_index::use_def::symbol_state::DeclarationIdWithConstraint;
use crate::visibility_constraints::{VisibilityConstraint, VisibilityConstraints};
use crate::symbol::Boundness;
use ruff_index::IndexVec;
use rustc_hash::FxHashMap;
@@ -273,20 +237,14 @@ use super::constraint::Constraint;
mod bitset;
mod symbol_state;
type AllConstraints<'db> = IndexVec<ScopedConstraintId, Constraint<'db>>;
/// Applicable definitions and constraints for every use of a name.
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct UseDefMap<'db> {
/// Array of [`Definition`] in this scope. Only the first entry should be `None`;
/// this represents the implicit "unbound"/"undeclared" definition of every symbol.
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
/// Array of [`Definition`] in this scope.
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
/// Array of [`Constraint`] in this scope.
all_constraints: AllConstraints<'db>,
/// Array of [`VisibilityConstraint`]s in this scope.
visibility_constraints: VisibilityConstraints<'db>,
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
@@ -317,6 +275,14 @@ impl<'db> UseDefMap<'db> {
self.bindings_iterator(&self.bindings_by_use[use_id])
}
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
if self.bindings_by_use[use_id].may_be_unbound() {
Boundness::PossiblyUnbound
} else {
Boundness::Bound
}
}
pub(crate) fn public_bindings(
&self,
symbol: ScopedSymbolId,
@@ -324,6 +290,14 @@ impl<'db> UseDefMap<'db> {
self.bindings_iterator(self.public_symbols[symbol].bindings())
}
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
if self.public_symbols[symbol].may_be_unbound() {
Boundness::PossiblyUnbound
} else {
Boundness::Bound
}
}
pub(crate) fn bindings_at_declaration(
&self,
declaration: Definition<'db>,
@@ -336,10 +310,10 @@ impl<'db> UseDefMap<'db> {
}
}
pub(crate) fn declarations_at_binding<'map>(
&'map self,
pub(crate) fn declarations_at_binding(
&self,
binding: Definition<'db>,
) -> DeclarationsIterator<'map, 'db> {
) -> DeclarationsIterator<'_, 'db> {
if let SymbolDefinitions::Declarations(declarations) =
&self.definitions_by_definition[&binding]
{
@@ -349,34 +323,37 @@ impl<'db> UseDefMap<'db> {
}
}
pub(crate) fn public_declarations<'map>(
&'map self,
pub(crate) fn public_declarations(
&self,
symbol: ScopedSymbolId,
) -> DeclarationsIterator<'map, 'db> {
) -> DeclarationsIterator<'_, 'db> {
let declarations = self.public_symbols[symbol].declarations();
self.declarations_iterator(declarations)
}
fn bindings_iterator<'map>(
&'map self,
bindings: &'map SymbolBindings,
) -> BindingWithConstraintsIterator<'map, 'db> {
pub(crate) fn has_public_declarations(&self, symbol: ScopedSymbolId) -> bool {
!self.public_symbols[symbol].declarations().is_empty()
}
fn bindings_iterator<'a>(
&'a self,
bindings: &'a SymbolBindings,
) -> BindingWithConstraintsIterator<'a, 'db> {
BindingWithConstraintsIterator {
all_definitions: &self.all_definitions,
all_constraints: &self.all_constraints,
visibility_constraints: &self.visibility_constraints,
inner: bindings.iter(),
}
}
fn declarations_iterator<'map>(
&'map self,
declarations: &'map SymbolDeclarations,
) -> DeclarationsIterator<'map, 'db> {
fn declarations_iterator<'a>(
&'a self,
declarations: &'a SymbolDeclarations,
) -> DeclarationsIterator<'a, 'db> {
DeclarationsIterator {
all_definitions: &self.all_definitions,
visibility_constraints: &self.visibility_constraints,
inner: declarations.iter(),
may_be_undeclared: declarations.may_be_undeclared(),
}
}
}
@@ -390,9 +367,8 @@ enum SymbolDefinitions {
#[derive(Debug)]
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
all_constraints: &'map AllConstraints<'db>,
pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>,
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
inner: BindingIdWithConstraintsIterator<'map>,
}
@@ -400,17 +376,14 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
type Item = BindingWithConstraints<'map, 'db>;
fn next(&mut self) -> Option<Self::Item> {
let all_constraints = self.all_constraints;
self.inner
.next()
.map(|binding_id_with_constraints| BindingWithConstraints {
binding: self.all_definitions[binding_id_with_constraints.definition],
.map(|def_id_with_constraints| BindingWithConstraints {
binding: self.all_definitions[def_id_with_constraints.definition],
constraints: ConstraintsIterator {
all_constraints,
constraint_ids: binding_id_with_constraints.constraint_ids,
all_constraints: self.all_constraints,
constraint_ids: def_id_with_constraints.constraint_ids,
},
visibility_constraint: binding_id_with_constraints.visibility_constraint,
})
}
}
@@ -418,13 +391,12 @@ impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
pub(crate) struct BindingWithConstraints<'map, 'db> {
pub(crate) binding: Option<Definition<'db>>,
pub(crate) binding: Definition<'db>,
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
}
pub(crate) struct ConstraintsIterator<'map, 'db> {
all_constraints: &'map AllConstraints<'db>,
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
constraint_ids: ConstraintIdIterator<'map>,
}
@@ -441,31 +413,22 @@ impl<'db> Iterator for ConstraintsIterator<'_, 'db> {
impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
pub(crate) struct DeclarationsIterator<'map, 'db> {
all_definitions: &'map IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
pub(crate) visibility_constraints: &'map VisibilityConstraints<'db>,
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
inner: DeclarationIdIterator<'map>,
may_be_undeclared: bool,
}
pub(crate) struct DeclarationWithConstraint<'db> {
pub(crate) declaration: Option<Definition<'db>>,
pub(crate) visibility_constraint: ScopedVisibilityConstraintId,
impl DeclarationsIterator<'_, '_> {
pub(crate) fn may_be_undeclared(&self) -> bool {
self.may_be_undeclared
}
}
impl<'db> Iterator for DeclarationsIterator<'_, 'db> {
type Item = DeclarationWithConstraint<'db>;
type Item = Definition<'db>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(
|DeclarationIdWithConstraint {
definition,
visibility_constraint,
}| {
DeclarationWithConstraint {
declaration: self.all_definitions[definition],
visibility_constraint,
}
},
)
self.inner.next().map(|def_id| self.all_definitions[def_id])
}
}
@@ -475,25 +438,15 @@ impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
#[derive(Clone, Debug)]
pub(super) struct FlowSnapshot {
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
scope_start_visibility: ScopedVisibilityConstraintId,
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub(super) struct UseDefMapBuilder<'db> {
/// Append-only array of [`Definition`].
all_definitions: IndexVec<ScopedDefinitionId, Option<Definition<'db>>>,
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
/// Append-only array of [`Constraint`].
all_constraints: AllConstraints<'db>,
/// Append-only array of [`VisibilityConstraint`].
visibility_constraints: VisibilityConstraints<'db>,
/// A constraint which describes the visibility of the unbound/undeclared state, i.e.
/// whether or not the start of the scope is visible. This is important for cases like
/// `if True: x = 1; use(x)` where we need to hide the implicit "x = unbound" binding
/// in the "else" branch.
scope_start_visibility: ScopedVisibilityConstraintId,
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
/// Live bindings at each so-far-recorded use.
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
@@ -505,30 +458,14 @@ pub(super) struct UseDefMapBuilder<'db> {
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
}
impl Default for UseDefMapBuilder<'_> {
fn default() -> Self {
Self {
all_definitions: IndexVec::from_iter([None]),
all_constraints: IndexVec::new(),
visibility_constraints: VisibilityConstraints::default(),
scope_start_visibility: ScopedVisibilityConstraintId::ALWAYS_TRUE,
bindings_by_use: IndexVec::new(),
definitions_by_definition: FxHashMap::default(),
symbol_states: IndexVec::new(),
}
}
}
impl<'db> UseDefMapBuilder<'db> {
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
let new_symbol = self
.symbol_states
.push(SymbolState::undefined(self.scope_start_visibility));
let new_symbol = self.symbol_states.push(SymbolState::undefined());
debug_assert_eq!(symbol, new_symbol);
}
pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) {
let def_id = self.all_definitions.push(Some(binding));
let def_id = self.all_definitions.push(binding);
let symbol_state = &mut self.symbol_states[symbol];
self.definitions_by_definition.insert(
binding,
@@ -537,82 +474,10 @@ impl<'db> UseDefMapBuilder<'db> {
symbol_state.record_binding(def_id);
}
pub(super) fn add_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
self.all_constraints.push(constraint)
}
pub(super) fn record_constraint_id(&mut self, constraint: ScopedConstraintId) {
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
let constraint_id = self.all_constraints.push(constraint);
for state in &mut self.symbol_states {
state.record_constraint(constraint);
}
}
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) -> ScopedConstraintId {
let new_constraint_id = self.add_constraint(constraint);
self.record_constraint_id(new_constraint_id);
new_constraint_id
}
pub(super) fn add_visibility_constraint(
&mut self,
constraint: VisibilityConstraint<'db>,
) -> ScopedVisibilityConstraintId {
self.visibility_constraints.add(constraint)
}
pub(super) fn record_visibility_constraint_id(
&mut self,
constraint: ScopedVisibilityConstraintId,
) {
for state in &mut self.symbol_states {
state.record_visibility_constraint(&mut self.visibility_constraints, constraint);
}
self.scope_start_visibility = self
.visibility_constraints
.add_and_constraint(self.scope_start_visibility, constraint);
}
pub(super) fn record_visibility_constraint(
&mut self,
constraint: VisibilityConstraint<'db>,
) -> ScopedVisibilityConstraintId {
let new_constraint_id = self.add_visibility_constraint(constraint);
self.record_visibility_constraint_id(new_constraint_id);
new_constraint_id
}
/// This method resets the visibility constraints for all symbols to a previous state
/// *if* there have been no new declarations or bindings since then. Consider the
/// following example:
/// ```py
/// x = 0
/// y = 0
/// if test_a:
/// y = 1
/// elif test_b:
/// y = 2
/// elif test_c:
/// y = 3
///
/// # RESET
/// ```
/// We build a complex visibility constraint for the `y = 0` binding. We build the same
/// constraint for the `x = 0` binding as well, but at the `RESET` point, we can get rid
/// of it, as the `if`-`elif`-`elif` chain doesn't include any new bindings of `x`.
pub(super) fn simplify_visibility_constraints(&mut self, snapshot: FlowSnapshot) {
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
self.scope_start_visibility = snapshot.scope_start_visibility;
// Note that this loop terminates when we reach a symbol not present in the snapshot.
// This means we keep visibility constraints for all new symbols, which is intended,
// since these symbols have been introduced in the corresponding branch, which might
// be subject to visibility constraints. We only simplify/reset visibility constraints
// for symbols that have the same bindings and declarations present compared to the
// snapshot.
for (current, snapshot) in self.symbol_states.iter_mut().zip(snapshot.symbol_states) {
current.simplify_visibility_constraints(snapshot);
state.record_constraint(constraint_id);
}
}
@@ -621,7 +486,7 @@ impl<'db> UseDefMapBuilder<'db> {
symbol: ScopedSymbolId,
declaration: Definition<'db>,
) {
let def_id = self.all_definitions.push(Some(declaration));
let def_id = self.all_definitions.push(declaration);
let symbol_state = &mut self.symbol_states[symbol];
self.definitions_by_definition.insert(
declaration,
@@ -636,7 +501,7 @@ impl<'db> UseDefMapBuilder<'db> {
definition: Definition<'db>,
) {
// We don't need to store anything in self.definitions_by_definition.
let def_id = self.all_definitions.push(Some(definition));
let def_id = self.all_definitions.push(definition);
let symbol_state = &mut self.symbol_states[symbol];
symbol_state.record_declaration(def_id);
symbol_state.record_binding(def_id);
@@ -655,7 +520,6 @@ impl<'db> UseDefMapBuilder<'db> {
pub(super) fn snapshot(&self) -> FlowSnapshot {
FlowSnapshot {
symbol_states: self.symbol_states.clone(),
scope_start_visibility: self.scope_start_visibility,
}
}
@@ -669,15 +533,12 @@ impl<'db> UseDefMapBuilder<'db> {
// Restore the current visible-definitions state to the given snapshot.
self.symbol_states = snapshot.symbol_states;
self.scope_start_visibility = snapshot.scope_start_visibility;
// If the snapshot we are restoring is missing some symbols we've recorded since, we need
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the
// snapshot, the correct state to fill them in with is "undefined".
self.symbol_states.resize(
num_symbols,
SymbolState::undefined(self.scope_start_visibility),
);
self.symbol_states
.resize(num_symbols, SymbolState::undefined());
}
/// Merge the given snapshot into the current state, reflecting that we might have taken either
@@ -692,19 +553,13 @@ impl<'db> UseDefMapBuilder<'db> {
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
for current in &mut self.symbol_states {
if let Some(snapshot) = snapshot_definitions_iter.next() {
current.merge(snapshot, &mut self.visibility_constraints);
current.merge(snapshot);
} else {
current.merge(
SymbolState::undefined(snapshot.scope_start_visibility),
&mut self.visibility_constraints,
);
// Symbol not present in snapshot, so it's unbound/undeclared from that path.
current.set_may_be_unbound();
current.set_may_be_undeclared();
}
}
self.scope_start_visibility = self
.visibility_constraints
.add_or_constraint(self.scope_start_visibility, snapshot.scope_start_visibility);
}
pub(super) fn finish(mut self) -> UseDefMap<'db> {
@@ -717,7 +572,6 @@ impl<'db> UseDefMapBuilder<'db> {
UseDefMap {
all_definitions: self.all_definitions,
all_constraints: self.all_constraints,
visibility_constraints: self.visibility_constraints,
bindings_by_use: self.bindings_by_use,
public_symbols: self.symbol_states,
definitions_by_definition: self.definitions_by_definition,

View File

@@ -32,6 +32,10 @@ impl<const B: usize> BitSet<B> {
bitset
}
pub(super) fn is_empty(&self) -> bool {
self.blocks().iter().all(|&b| b == 0)
}
/// Convert from Inline to Heap, if needed, and resize the Heap vector, if needed.
fn resize(&mut self, value: u32) {
let num_blocks_needed = (value / 64) + 1;
@@ -93,6 +97,19 @@ impl<const B: usize> BitSet<B> {
}
}
/// Union in-place with another [`BitSet`].
pub(super) fn union(&mut self, other: &BitSet<B>) {
let mut max_len = self.blocks().len();
let other_len = other.blocks().len();
if other_len > max_len {
max_len = other_len;
self.resize_blocks(max_len);
}
for (my_block, other_block) in self.blocks_mut().iter_mut().zip(other.blocks()) {
*my_block |= other_block;
}
}
/// Return an iterator over the values (in ascending order) in this [`BitSet`].
pub(super) fn iter(&self) -> BitSetIterator<'_, B> {
let blocks = self.blocks();
@@ -222,6 +239,59 @@ mod tests {
assert_bitset(&b1, &[89]);
}
#[test]
fn union() {
let mut b1 = BitSet::<1>::with(2);
let b2 = BitSet::<1>::with(4);
b1.union(&b2);
assert_bitset(&b1, &[2, 4]);
}
#[test]
fn union_mixed_1() {
let mut b1 = BitSet::<1>::with(4);
let mut b2 = BitSet::<1>::with(4);
b1.insert(89);
b2.insert(5);
b1.union(&b2);
assert_bitset(&b1, &[4, 5, 89]);
}
#[test]
fn union_mixed_2() {
let mut b1 = BitSet::<1>::with(4);
let mut b2 = BitSet::<1>::with(4);
b1.insert(23);
b2.insert(89);
b1.union(&b2);
assert_bitset(&b1, &[4, 23, 89]);
}
#[test]
fn union_heap() {
let mut b1 = BitSet::<1>::with(4);
let mut b2 = BitSet::<1>::with(4);
b1.insert(89);
b2.insert(90);
b1.union(&b2);
assert_bitset(&b1, &[4, 89, 90]);
}
#[test]
fn union_heap_2() {
let mut b1 = BitSet::<1>::with(89);
let mut b2 = BitSet::<1>::with(89);
b1.insert(91);
b2.insert(90);
b1.union(&b2);
assert_bitset(&b1, &[89, 90, 91]);
}
#[test]
fn multiple_blocks() {
let mut b = BitSet::<2>::with(120);
@@ -229,4 +299,11 @@ mod tests {
assert!(matches!(b, BitSet::Inline(_)));
assert_bitset(&b, &[45, 120]);
}
#[test]
fn empty() {
let b = BitSet::<1>::default();
assert!(b.is_empty());
}
}

View File

@@ -43,8 +43,6 @@
//!
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
//! similar to tracking live bindings.
use crate::semantic_index::use_def::VisibilityConstraints;
use super::bitset::{BitSet, BitSetIterator};
use ruff_index::newtype_index;
use smallvec::SmallVec;
@@ -53,18 +51,9 @@ use smallvec::SmallVec;
#[newtype_index]
pub(super) struct ScopedDefinitionId;
impl ScopedDefinitionId {
/// A special ID that is used to describe an implicit start-of-scope state. When
/// we see that this definition is live, we know that the symbol is (possibly)
/// unbound or undeclared at a given usage site.
/// When creating a use-def-map builder, we always add an empty `None` definition
/// at index 0, so this ID is always present.
pub(super) const UNBOUND: ScopedDefinitionId = ScopedDefinitionId::from_u32(0);
}
/// A newtype-index for a constraint expression in a particular scope.
#[newtype_index]
pub(crate) struct ScopedConstraintId;
pub(super) struct ScopedConstraintId;
/// Can reference this * 64 total definitions inline; more will fall back to the heap.
const INLINE_BINDING_BLOCKS: usize = 3;
@@ -86,97 +75,58 @@ const INLINE_CONSTRAINT_BLOCKS: usize = 2;
/// Can keep inline this many live bindings per symbol at a given time; more will go to heap.
const INLINE_BINDINGS_PER_SYMBOL: usize = 4;
/// Which constraints apply to a given binding?
type Constraints = BitSet<INLINE_CONSTRAINT_BLOCKS>;
type InlineConstraintArray = [Constraints; INLINE_BINDINGS_PER_SYMBOL];
/// One [`BitSet`] of applicable [`ScopedConstraintId`]s per live binding.
type ConstraintsPerBinding = SmallVec<InlineConstraintArray>;
/// Iterate over all constraints for a single binding.
type ConstraintsIterator<'a> = std::slice::Iter<'a, Constraints>;
/// One [`BitSet`] of applicable [`ScopedConstraintId`] per live binding.
type InlineConstraintArray = [BitSet<INLINE_CONSTRAINT_BLOCKS>; INLINE_BINDINGS_PER_SYMBOL];
type Constraints = SmallVec<InlineConstraintArray>;
type ConstraintsIterator<'a> = std::slice::Iter<'a, BitSet<INLINE_CONSTRAINT_BLOCKS>>;
type ConstraintsIntoIterator = smallvec::IntoIter<InlineConstraintArray>;
/// A newtype-index for a visibility constraint in a particular scope.
#[newtype_index]
pub(crate) struct ScopedVisibilityConstraintId;
impl ScopedVisibilityConstraintId {
/// A special ID that is used for an "always true" / "always visible" constraint.
/// When we create a new [`VisibilityConstraints`] object, this constraint is always
/// present at index 0.
pub(crate) const ALWAYS_TRUE: ScopedVisibilityConstraintId =
ScopedVisibilityConstraintId::from_u32(0);
}
const INLINE_VISIBILITY_CONSTRAINTS: usize = 4;
type InlineVisibilityConstraintsArray =
[ScopedVisibilityConstraintId; INLINE_VISIBILITY_CONSTRAINTS];
/// One [`ScopedVisibilityConstraintId`] per live declaration.
type VisibilityConstraintPerDeclaration = SmallVec<InlineVisibilityConstraintsArray>;
/// One [`ScopedVisibilityConstraintId`] per live binding.
type VisibilityConstraintPerBinding = SmallVec<InlineVisibilityConstraintsArray>;
/// Iterator over the visibility constraints for all live bindings/declarations.
type VisibilityConstraintsIterator<'a> = std::slice::Iter<'a, ScopedVisibilityConstraintId>;
type VisibilityConstraintsIntoIterator = smallvec::IntoIter<InlineVisibilityConstraintsArray>;
/// Live declarations for a single symbol at some point in control flow, with their
/// corresponding visibility constraints.
/// Live declarations for a single symbol at some point in control flow.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct SymbolDeclarations {
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
pub(crate) live_declarations: Declarations,
live_declarations: Declarations,
/// For each live declaration, which visibility constraint applies to it?
pub(crate) visibility_constraints: VisibilityConstraintPerDeclaration,
/// Could the symbol be un-declared at this point?
may_be_undeclared: bool,
}
impl SymbolDeclarations {
fn undeclared(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
fn undeclared() -> Self {
Self {
live_declarations: Declarations::with(0),
visibility_constraints: VisibilityConstraintPerDeclaration::from_iter([
scope_start_visibility,
]),
live_declarations: Declarations::default(),
may_be_undeclared: true,
}
}
/// Record a newly-encountered declaration for this symbol.
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
self.live_declarations = Declarations::with(declaration_id.into());
self.visibility_constraints = VisibilityConstraintPerDeclaration::with_capacity(1);
self.visibility_constraints
.push(ScopedVisibilityConstraintId::ALWAYS_TRUE);
self.may_be_undeclared = false;
}
/// Add given visibility constraint to all live declarations.
pub(super) fn record_visibility_constraint(
&mut self,
visibility_constraints: &mut VisibilityConstraints,
constraint: ScopedVisibilityConstraintId,
) {
for existing in &mut self.visibility_constraints {
*existing = visibility_constraints.add_and_constraint(*existing, constraint);
}
/// Add undeclared as a possibility for this symbol.
fn set_may_be_undeclared(&mut self) {
self.may_be_undeclared = true;
}
/// Return an iterator over live declarations for this symbol.
pub(super) fn iter(&self) -> DeclarationIdIterator {
DeclarationIdIterator {
declarations: self.live_declarations.iter(),
visibility_constraints: self.visibility_constraints.iter(),
inner: self.live_declarations.iter(),
}
}
pub(super) fn is_empty(&self) -> bool {
self.live_declarations.is_empty()
}
pub(super) fn may_be_undeclared(&self) -> bool {
self.may_be_undeclared
}
}
/// Live bindings for a single symbol at some point in control flow. Each live binding comes
/// with a set of narrowing constraints and a visibility constraint.
/// Live bindings and narrowing constraints for a single symbol at some point in control flow.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct SymbolBindings {
/// [`BitSet`]: which bindings (as [`ScopedDefinitionId`]) can reach the current location?
@@ -186,34 +136,34 @@ pub(super) struct SymbolBindings {
///
/// This is a [`smallvec::SmallVec`] which should always have one [`BitSet`] of constraints per
/// binding in `live_bindings`.
constraints: ConstraintsPerBinding,
constraints: Constraints,
/// For each live binding, which visibility constraint applies to it?
visibility_constraints: VisibilityConstraintPerBinding,
/// Could the symbol be unbound at this point?
may_be_unbound: bool,
}
impl SymbolBindings {
fn unbound(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
fn unbound() -> Self {
Self {
live_bindings: Bindings::with(ScopedDefinitionId::UNBOUND.as_u32()),
constraints: ConstraintsPerBinding::from_iter([Constraints::default()]),
visibility_constraints: VisibilityConstraintPerBinding::from_iter([
scope_start_visibility,
]),
live_bindings: Bindings::default(),
constraints: Constraints::default(),
may_be_unbound: true,
}
}
/// Add Unbound as a possibility for this symbol.
fn set_may_be_unbound(&mut self) {
self.may_be_unbound = true;
}
/// Record a newly-encountered binding for this symbol.
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 = ConstraintsPerBinding::with_capacity(1);
self.constraints.push(Constraints::default());
self.visibility_constraints = VisibilityConstraintPerBinding::with_capacity(1);
self.visibility_constraints
.push(ScopedVisibilityConstraintId::ALWAYS_TRUE);
self.constraints = Constraints::with_capacity(1);
self.constraints.push(BitSet::default());
self.may_be_unbound = false;
}
/// Add given constraint to all live bindings.
@@ -223,25 +173,17 @@ impl SymbolBindings {
}
}
/// Add given visibility constraint to all live bindings.
pub(super) fn record_visibility_constraint(
&mut self,
visibility_constraints: &mut VisibilityConstraints,
constraint: ScopedVisibilityConstraintId,
) {
for existing in &mut self.visibility_constraints {
*existing = visibility_constraints.add_and_constraint(*existing, constraint);
}
}
/// Iterate over currently live bindings for this symbol
/// Iterate over currently live bindings for this symbol.
pub(super) fn iter(&self) -> BindingIdWithConstraintsIterator {
BindingIdWithConstraintsIterator {
definitions: self.live_bindings.iter(),
constraints: self.constraints.iter(),
visibility_constraints: self.visibility_constraints.iter(),
}
}
pub(super) fn may_be_unbound(&self) -> bool {
self.may_be_unbound
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -252,16 +194,20 @@ pub(super) struct SymbolState {
impl SymbolState {
/// Return a new [`SymbolState`] representing an unbound, undeclared symbol.
pub(super) fn undefined(scope_start_visibility: ScopedVisibilityConstraintId) -> Self {
pub(super) fn undefined() -> Self {
Self {
declarations: SymbolDeclarations::undeclared(scope_start_visibility),
bindings: SymbolBindings::unbound(scope_start_visibility),
declarations: SymbolDeclarations::undeclared(),
bindings: SymbolBindings::unbound(),
}
}
/// Add Unbound as a possibility for this symbol.
pub(super) fn set_may_be_unbound(&mut self) {
self.bindings.set_may_be_unbound();
}
/// Record a newly-encountered binding for this symbol.
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
debug_assert_ne!(binding_id, ScopedDefinitionId::UNBOUND);
self.bindings.record_binding(binding_id);
}
@@ -270,26 +216,9 @@ impl SymbolState {
self.bindings.record_constraint(constraint_id);
}
/// Add given visibility constraint to all live bindings.
pub(super) fn record_visibility_constraint(
&mut self,
visibility_constraints: &mut VisibilityConstraints,
constraint: ScopedVisibilityConstraintId,
) {
self.bindings
.record_visibility_constraint(visibility_constraints, constraint);
self.declarations
.record_visibility_constraint(visibility_constraints, constraint);
}
pub(super) fn simplify_visibility_constraints(&mut self, snapshot_state: SymbolState) {
if self.bindings.live_bindings == snapshot_state.bindings.live_bindings {
self.bindings.visibility_constraints = snapshot_state.bindings.visibility_constraints;
}
if self.declarations.live_declarations == snapshot_state.declarations.live_declarations {
self.declarations.visibility_constraints =
snapshot_state.declarations.visibility_constraints;
}
/// Add undeclared as a possibility for this symbol.
pub(super) fn set_may_be_undeclared(&mut self) {
self.declarations.set_may_be_undeclared();
}
/// Record a newly-encountered declaration of this symbol.
@@ -298,31 +227,29 @@ impl SymbolState {
}
/// Merge another [`SymbolState`] into this one.
pub(super) fn merge(
&mut self,
b: SymbolState,
visibility_constraints: &mut VisibilityConstraints,
) {
pub(super) fn merge(&mut self, b: SymbolState) {
let mut a = Self {
bindings: SymbolBindings {
live_bindings: Bindings::default(),
constraints: ConstraintsPerBinding::default(),
visibility_constraints: VisibilityConstraintPerBinding::default(),
constraints: Constraints::default(),
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
},
declarations: SymbolDeclarations {
live_declarations: self.declarations.live_declarations.clone(),
visibility_constraints: VisibilityConstraintPerDeclaration::default(),
may_be_undeclared: self.declarations.may_be_undeclared
|| b.declarations.may_be_undeclared,
},
};
std::mem::swap(&mut a, self);
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_vis_constraints_iter = a.bindings.visibility_constraints.into_iter();
let mut b_vis_constraints_iter = b.bindings.visibility_constraints.into_iter();
let mut opt_a_def: Option<u32> = a_defs_iter.next();
let mut opt_b_def: Option<u32> = b_defs_iter.next();
@@ -334,30 +261,17 @@ impl SymbolState {
// path is irrelevant.
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
let push = |def,
constraints_iter: &mut ConstraintsIntoIterator,
visibility_constraints_iter: &mut VisibilityConstraintsIntoIterator,
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 using [`SymbolState::undefined`], which adds
// one "unbound" definition with corresponding narrowing and visibility constraints, or
// using [`SymbolState::record_binding`] or [`SymbolState::record_declaration`], which
// similarly add one definition with corresponding constraints. [`SymbolState::merge`]
// always pushes one definition and one constraint bitset and one visibility constraint
// together (just below), so the number of definitions and the number of constraints can
// never get out of sync.
// 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 constraint bitset together (just
// below), so the number of definitions and the number of constraint bitsets can never
// get out of sync.
let constraints = constraints_iter
.next()
.expect("definitions and constraints length mismatch");
let visibility_constraints = visibility_constraints_iter
.next()
.expect("definitions and visibility_constraints length mismatch");
merged.bindings.constraints.push(constraints);
merged
.bindings
.visibility_constraints
.push(visibility_constraints);
};
loop {
@@ -365,139 +279,50 @@ 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_vis_constraints_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_vis_constraints_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_vis_constraints_iter,
self,
);
// SAFETY: see comment in `push` above.
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
// constraint bitset together (just below), so the number of definitions
// and the number of constraint bitsets can never get out of sync.
let a_constraints = a_constraints_iter
.next()
.expect("definitions and constraints length mismatch");
let current_constraints = self.bindings.constraints.last_mut().unwrap();
// 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.
current_constraints.intersect(&a_constraints);
// For visibility constraints, we merge them using a ternary OR operation:
let a_vis_constraint = a_vis_constraints_iter
.next()
.expect("visibility_constraints length mismatch");
let current_vis_constraint =
self.bindings.visibility_constraints.last_mut().unwrap();
*current_vis_constraint = visibility_constraints
.add_or_constraint(*current_vis_constraint, a_vis_constraint);
self.bindings
.constraints
.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_vis_constraints_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_vis_constraints_iter,
self,
);
push(b_def, &mut b_constraints_iter, self);
opt_b_def = b_defs_iter.next();
}
(None, None) => break,
}
}
// Same as above, but for declarations.
let mut a_decls_iter = a.declarations.live_declarations.iter();
let mut b_decls_iter = b.declarations.live_declarations.iter();
let mut a_vis_constraints_iter = a.declarations.visibility_constraints.into_iter();
let mut b_vis_constraints_iter = b.declarations.visibility_constraints.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,
vis_constraints_iter: &mut VisibilityConstraintsIntoIterator,
merged: &mut Self| {
merged.declarations.live_declarations.insert(decl);
let vis_constraints = vis_constraints_iter
.next()
.expect("declarations and visibility_constraints length mismatch");
merged
.declarations
.visibility_constraints
.push(vis_constraints);
};
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_vis_constraints_iter, self);
opt_a_decl = a_decls_iter.next();
}
std::cmp::Ordering::Greater => {
push(b_decl, &mut b_vis_constraints_iter, self);
opt_b_decl = b_decls_iter.next();
}
std::cmp::Ordering::Equal => {
push(a_decl, &mut b_vis_constraints_iter, self);
let a_vis_constraint = a_vis_constraints_iter
.next()
.expect("declarations and visibility_constraints length mismatch");
let current = self.declarations.visibility_constraints.last_mut().unwrap();
*current =
visibility_constraints.add_or_constraint(*current, a_vis_constraint);
opt_a_decl = a_decls_iter.next();
opt_b_decl = b_decls_iter.next();
}
},
(Some(a_decl), None) => {
push(a_decl, &mut a_vis_constraints_iter, self);
opt_a_decl = a_decls_iter.next();
}
(None, Some(b_decl)) => {
push(b_decl, &mut b_vis_constraints_iter, self);
opt_b_decl = b_decls_iter.next();
}
(None, None) => break,
}
}
}
pub(super) fn bindings(&self) -> &SymbolBindings {
@@ -507,44 +332,47 @@ impl SymbolState {
pub(super) fn declarations(&self) -> &SymbolDeclarations {
&self.declarations
}
/// Could the symbol be unbound?
pub(super) fn may_be_unbound(&self) -> bool {
self.bindings.may_be_unbound()
}
}
/// The default state of a symbol, if we've seen no definitions of it, is undefined (that is,
/// both unbound and undeclared).
impl Default for SymbolState {
fn default() -> Self {
SymbolState::undefined()
}
}
/// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable
/// narrowing constraints ([`ScopedConstraintId`]) and a corresponding visibility
/// visibility constraint ([`ScopedVisibilityConstraintId`]).
/// [`ScopedConstraintId`].
#[derive(Debug)]
pub(super) struct BindingIdWithConstraints<'map> {
pub(super) struct BindingIdWithConstraints<'a> {
pub(super) definition: ScopedDefinitionId,
pub(super) constraint_ids: ConstraintIdIterator<'map>,
pub(super) visibility_constraint: ScopedVisibilityConstraintId,
pub(super) constraint_ids: ConstraintIdIterator<'a>,
}
#[derive(Debug)]
pub(super) struct BindingIdWithConstraintsIterator<'map> {
definitions: BindingsIterator<'map>,
constraints: ConstraintsIterator<'map>,
visibility_constraints: VisibilityConstraintsIterator<'map>,
pub(super) struct BindingIdWithConstraintsIterator<'a> {
definitions: BindingsIterator<'a>,
constraints: ConstraintsIterator<'a>,
}
impl<'map> Iterator for BindingIdWithConstraintsIterator<'map> {
type Item = BindingIdWithConstraints<'map>;
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.visibility_constraints.next(),
) {
(None, None, None) => None,
(Some(def), Some(constraints), Some(visibility_constraint_id)) => {
Some(BindingIdWithConstraints {
definition: ScopedDefinitionId::from_u32(def),
constraint_ids: ConstraintIdIterator {
wrapped: constraints.iter(),
},
visibility_constraint: *visibility_constraint_id,
})
}
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"),
}
@@ -568,34 +396,16 @@ impl Iterator for ConstraintIdIterator<'_> {
impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
/// A single declaration (as [`ScopedDefinitionId`]) with a corresponding visibility
/// visibility constraint ([`ScopedVisibilityConstraintId`]).
#[derive(Debug)]
pub(super) struct DeclarationIdWithConstraint {
pub(super) definition: ScopedDefinitionId,
pub(super) visibility_constraint: ScopedVisibilityConstraintId,
}
pub(super) struct DeclarationIdIterator<'map> {
pub(crate) declarations: DeclarationsIterator<'map>,
pub(crate) visibility_constraints: VisibilityConstraintsIterator<'map>,
pub(super) struct DeclarationIdIterator<'a> {
inner: DeclarationsIterator<'a>,
}
impl Iterator for DeclarationIdIterator<'_> {
type Item = DeclarationIdWithConstraint;
type Item = ScopedDefinitionId;
fn next(&mut self) -> Option<Self::Item> {
match (self.declarations.next(), self.visibility_constraints.next()) {
(None, None) => None,
(Some(declaration), Some(&visibility_constraint)) => {
Some(DeclarationIdWithConstraint {
definition: ScopedDefinitionId::from_u32(declaration),
visibility_constraint,
})
}
// SAFETY: see above.
_ => unreachable!("declarations and visibility_constraints length mismatch"),
}
self.inner.next().map(ScopedDefinitionId::from_u32)
}
}
@@ -603,172 +413,176 @@ impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
#[cfg(test)]
mod tests {
use super::*;
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
#[track_caller]
fn assert_bindings(symbol: &SymbolState, expected: &[&str]) {
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
let actual = symbol
.bindings()
.iter()
.map(|def_id_with_constraints| {
let def_id = def_id_with_constraints.definition;
let def = if def_id == ScopedDefinitionId::UNBOUND {
"unbound".into()
} else {
def_id.as_u32().to_string()
};
let constraints = def_id_with_constraints
.constraint_ids
.map(ScopedConstraintId::as_u32)
.map(|idx| idx.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("{def}<{constraints}>")
format!(
"{}<{}>",
def_id_with_constraints.definition.as_u32(),
def_id_with_constraints
.constraint_ids
.map(ScopedConstraintId::as_u32)
.map(|idx| idx.to_string())
.collect::<Vec<_>>()
.join(", ")
)
})
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}
#[track_caller]
pub(crate) fn assert_declarations(symbol: &SymbolState, expected: &[&str]) {
pub(crate) fn assert_declarations(
symbol: &SymbolState,
may_be_undeclared: bool,
expected: &[u32],
) {
assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared);
let actual = symbol
.declarations()
.iter()
.map(
|DeclarationIdWithConstraint {
definition,
visibility_constraint: _,
}| {
if definition == ScopedDefinitionId::UNBOUND {
"undeclared".into()
} else {
definition.as_u32().to_string()
}
},
)
.map(ScopedDefinitionId::as_u32)
.collect::<Vec<_>>();
assert_eq!(actual, expected);
}
#[test]
fn unbound() {
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let sym = SymbolState::undefined();
assert_bindings(&sym, &["unbound<>"]);
assert_bindings(&sym, true, &[]);
}
#[test]
fn with() {
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym.record_binding(ScopedDefinitionId::from_u32(1));
let mut sym = SymbolState::undefined();
sym.record_binding(ScopedDefinitionId::from_u32(0));
assert_bindings(&sym, &["1<>"]);
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();
assert_bindings(&sym, true, &["0<>"]);
}
#[test]
fn record_constraint() {
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym.record_binding(ScopedDefinitionId::from_u32(1));
let mut sym = SymbolState::undefined();
sym.record_binding(ScopedDefinitionId::from_u32(0));
sym.record_constraint(ScopedConstraintId::from_u32(0));
assert_bindings(&sym, &["1<0>"]);
assert_bindings(&sym, false, &["0<0>"]);
}
#[test]
fn merge() {
let mut visibility_constraints = VisibilityConstraints::default();
// merging the same definition with the same constraint keeps the constraint
let mut sym1a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
sym1a.record_constraint(ScopedConstraintId::from_u32(0));
let mut sym0a = SymbolState::undefined();
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
sym1b.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));
sym1a.merge(sym1b, &mut visibility_constraints);
let mut sym1 = sym1a;
assert_bindings(&sym1, &["1<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 sym2a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
sym2a.record_constraint(ScopedConstraintId::from_u32(1));
let mut sym1a = SymbolState::undefined();
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
let mut sym1b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym1b.record_binding(ScopedDefinitionId::from_u32(2));
let mut sym1b = SymbolState::undefined();
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
sym2a.merge(sym1b, &mut visibility_constraints);
let sym2 = sym2a;
assert_bindings(&sym2, &["2<>"]);
sym1a.merge(sym1b);
let sym1 = sym1a;
assert_bindings(&sym1, false, &["1<>"]);
// merging a constrained definition with unbound keeps both
let mut sym3a = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
sym3a.record_binding(ScopedDefinitionId::from_u32(3));
sym3a.record_constraint(ScopedConstraintId::from_u32(3));
let mut sym2a = SymbolState::undefined();
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
let sym2b = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let sym2b = SymbolState::undefined();
sym3a.merge(sym2b, &mut visibility_constraints);
let sym3 = sym3a;
assert_bindings(&sym3, &["unbound<>", "3<3>"]);
sym2a.merge(sym2b);
let sym2 = sym2a;
assert_bindings(&sym2, true, &["2<3>"]);
// merging different definitions keeps them each with their existing constraints
sym1.merge(sym3, &mut visibility_constraints);
let sym = sym1;
assert_bindings(&sym, &["unbound<>", "1<0>", "3<3>"]);
sym0.merge(sym2);
let sym = sym0;
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
}
#[test]
fn no_declaration() {
let sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let sym = SymbolState::undefined();
assert_declarations(&sym, &["undeclared"]);
assert_declarations(&sym, true, &[]);
}
#[test]
fn record_declaration() {
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
assert_declarations(&sym, &["1"]);
assert_declarations(&sym, false, &[1]);
}
#[test]
fn record_declaration_override() {
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
sym.record_declaration(ScopedDefinitionId::from_u32(2));
assert_declarations(&sym, &["2"]);
assert_declarations(&sym, false, &[2]);
}
#[test]
fn record_declaration_merge() {
let mut visibility_constraints = VisibilityConstraints::default();
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
let mut sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let mut sym2 = SymbolState::undefined();
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
sym.merge(sym2, &mut visibility_constraints);
sym.merge(sym2);
assert_declarations(&sym, &["1", "2"]);
assert_declarations(&sym, false, &[1, 2]);
}
#[test]
fn record_declaration_merge_partial_undeclared() {
let mut visibility_constraints = VisibilityConstraints::default();
let mut sym = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let mut sym = SymbolState::undefined();
sym.record_declaration(ScopedDefinitionId::from_u32(1));
let sym2 = SymbolState::undefined(ScopedVisibilityConstraintId::ALWAYS_TRUE);
let sym2 = SymbolState::undefined();
sym.merge(sym2, &mut visibility_constraints);
sym.merge(sym2);
assert_declarations(&sym, &["undeclared", "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();
assert_declarations(&sym, true, &[0]);
}
}

View File

@@ -1,19 +1,55 @@
use crate::module_resolver::{resolve_module, KnownModule};
use crate::module_name::ModuleName;
use crate::module_resolver::resolve_module;
use crate::semantic_index::global_scope;
use crate::semantic_index::symbol::ScopeId;
use crate::symbol::Symbol;
use crate::types::global_symbol;
use crate::Db;
/// Lookup the type of `symbol` in a given known module
/// Enumeration of various core stdlib modules, for which we have dedicated Salsa queries.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CoreStdlibModule {
Builtins,
Types,
Typeshed,
TypingExtensions,
Typing,
Sys,
#[allow(dead_code)]
Abc, // currently only used in tests
Collections,
}
impl CoreStdlibModule {
pub(crate) const fn as_str(self) -> &'static str {
match self {
Self::Builtins => "builtins",
Self::Types => "types",
Self::Typing => "typing",
Self::Typeshed => "_typeshed",
Self::TypingExtensions => "typing_extensions",
Self::Sys => "sys",
Self::Abc => "abc",
Self::Collections => "collections",
}
}
pub(crate) fn name(self) -> ModuleName {
let self_as_str = self.as_str();
ModuleName::new_static(self_as_str)
.unwrap_or_else(|| panic!("{self_as_str} should be a valid module name!"))
}
}
/// Lookup the type of `symbol` in a given core module
///
/// Returns `Symbol::Unbound` if the given known module cannot be resolved for some reason
pub(crate) fn known_module_symbol<'db>(
/// Returns `Symbol::Unbound` if the given core module cannot be resolved for some reason
pub(crate) fn core_module_symbol<'db>(
db: &'db dyn Db,
known_module: KnownModule,
core_module: CoreStdlibModule,
symbol: &str,
) -> Symbol<'db> {
resolve_module(db, &known_module.name())
resolve_module(db, &core_module.name())
.map(|module| global_symbol(db, module.file(), symbol))
.unwrap_or(Symbol::Unbound)
}
@@ -23,7 +59,7 @@ pub(crate) fn known_module_symbol<'db>(
/// Returns `Symbol::Unbound` if the `builtins` module isn't available for some reason.
#[inline]
pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
known_module_symbol(db, KnownModule::Builtins, symbol)
core_module_symbol(db, CoreStdlibModule::Builtins, symbol)
}
/// Lookup the type of `symbol` in the `typing` module namespace.
@@ -32,7 +68,7 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db>
#[inline]
#[cfg(test)]
pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
known_module_symbol(db, KnownModule::Typing, symbol)
core_module_symbol(db, CoreStdlibModule::Typing, symbol)
}
/// Lookup the type of `symbol` in the `typing_extensions` module namespace.
@@ -40,13 +76,13 @@ pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
/// Returns `Symbol::Unbound` if the `typing_extensions` module isn't available for some reason.
#[inline]
pub(crate) fn typing_extensions_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
known_module_symbol(db, KnownModule::TypingExtensions, symbol)
core_module_symbol(db, CoreStdlibModule::TypingExtensions, symbol)
}
/// Get the scope of a core stdlib module.
///
/// Can return `None` if a custom typeshed is used that is missing the core module in question.
fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_>> {
fn core_module_scope(db: &dyn Db, core_module: CoreStdlibModule) -> Option<ScopeId<'_>> {
resolve_module(db, &core_module.name()).map(|module| global_scope(db, module.file()))
}
@@ -54,5 +90,5 @@ fn core_module_scope(db: &dyn Db, core_module: KnownModule) -> Option<ScopeId<'_
///
/// Can return `None` if a custom typeshed is used that is missing `builtins.pyi`.
pub(crate) fn builtins_module_scope(db: &dyn Db) -> Option<ScopeId<'_>> {
core_module_scope(db, KnownModule::Builtins)
core_module_scope(db, CoreStdlibModule::Builtins)
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@ impl Boundness {
/// possibly_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound),
/// non_existent: Symbol::Unbound,
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Symbol<'db> {
Type(Type<'db>, Boundness),
Unbound,

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
use super::context::InferContext;
use super::diagnostic::CALL_NON_CALLABLE;
use super::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
use super::{Severity, Type, TypeArrayDisplay, UnionBuilder};
use crate::Db;
use ruff_db::diagnostic::DiagnosticId;
@@ -87,23 +86,24 @@ impl<'db> CallOutcome<'db> {
}
/// Get the return type of the call, emitting default diagnostics if needed.
pub(super) fn unwrap_with_diagnostic(
pub(super) fn unwrap_with_diagnostic<'a>(
&self,
context: &InferContext<'db>,
db: &'db dyn Db,
node: ast::AnyNodeRef,
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
) -> Type<'db> {
match self.return_ty_result(context, node) {
match self.return_ty_result(db, node, diagnostics) {
Ok(return_ty) => return_ty,
Err(NotCallableError::Type {
not_callable_ty,
return_ty,
}) => {
context.report_lint(
diagnostics.add_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable",
not_callable_ty.display(context.db())
not_callable_ty.display(db)
),
);
return_ty
@@ -113,13 +113,13 @@ impl<'db> CallOutcome<'db> {
called_ty,
return_ty,
}) => {
context.report_lint(
diagnostics.add_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (due to union element `{}`)",
called_ty.display(context.db()),
not_callable_ty.display(context.db()),
called_ty.display(db),
not_callable_ty.display(db),
),
);
return_ty
@@ -129,13 +129,13 @@ impl<'db> CallOutcome<'db> {
called_ty,
return_ty,
}) => {
context.report_lint(
diagnostics.add_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (due to union elements {})",
called_ty.display(context.db()),
not_callable_tys.display(context.db()),
called_ty.display(db),
not_callable_tys.display(db),
),
);
return_ty
@@ -144,12 +144,12 @@ impl<'db> CallOutcome<'db> {
callable_ty: called_ty,
return_ty,
}) => {
context.report_lint(
diagnostics.add_lint(
&CALL_NON_CALLABLE,
node,
format_args!(
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
called_ty.display(context.db())
called_ty.display(db)
),
);
return_ty
@@ -158,10 +158,11 @@ impl<'db> CallOutcome<'db> {
}
/// Get the return type of the call as a result.
pub(super) fn return_ty_result(
pub(super) fn return_ty_result<'a>(
&self,
context: &InferContext<'db>,
db: &'db dyn Db,
node: ast::AnyNodeRef,
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
) -> Result<Type<'db>, NotCallableError<'db>> {
match self {
Self::Callable { return_ty } => Ok(*return_ty),
@@ -169,11 +170,11 @@ impl<'db> CallOutcome<'db> {
return_ty,
revealed_ty,
} => {
context.report_diagnostic(
diagnostics.add(
node,
DiagnosticId::RevealedType,
Severity::Info,
format_args!("Revealed type is `{}`", revealed_ty.display(context.db())),
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
);
Ok(*return_ty)
}
@@ -186,16 +187,14 @@ impl<'db> CallOutcome<'db> {
call_outcome,
} => Err(NotCallableError::PossiblyUnboundDunderCall {
callable_ty: *called_ty,
return_ty: call_outcome
.return_ty(context.db())
.unwrap_or(Type::Unknown),
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
}),
Self::Union {
outcomes,
called_ty,
} => {
let mut not_callable = vec![];
let mut union_builder = UnionBuilder::new(context.db());
let mut union_builder = UnionBuilder::new(db);
let mut revealed = false;
for outcome in outcomes {
let return_ty = match outcome {
@@ -211,10 +210,10 @@ impl<'db> CallOutcome<'db> {
*return_ty
} else {
revealed = true;
outcome.unwrap_with_diagnostic(context, node)
outcome.unwrap_with_diagnostic(db, node, diagnostics)
}
}
_ => outcome.unwrap_with_diagnostic(context, node),
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
};
union_builder = union_builder.add(return_ty);
}

View File

@@ -1,197 +0,0 @@
use std::fmt;
use drop_bomb::DebugDropBomb;
use ruff_db::{
diagnostic::{DiagnosticId, Severity},
files::File,
};
use ruff_python_ast::AnyNodeRef;
use ruff_text_size::Ranged;
use super::{binding_ty, KnownFunction, TypeCheckDiagnostic, TypeCheckDiagnostics};
use crate::semantic_index::semantic_index;
use crate::semantic_index::symbol::ScopeId;
use crate::{
lint::{LintId, LintMetadata},
suppression::suppressions,
Db,
};
/// Context for inferring the types of a single file.
///
/// One context exists for at least for every inferred region but it's
/// possible that inferring a sub-region, like an unpack assignment, creates
/// a sub-context.
///
/// Tracks the reported diagnostics of the inferred region.
///
/// ## Consuming
/// It's important that the context is explicitly consumed before dropping by calling
/// [`InferContext::finish`] and the returned diagnostics must be stored
/// on the current [`TypeInference`](super::infer::TypeInference) result.
pub(crate) struct InferContext<'db> {
db: &'db dyn Db,
scope: ScopeId<'db>,
file: File,
diagnostics: std::cell::RefCell<TypeCheckDiagnostics>,
no_type_check: InNoTypeCheck,
bomb: DebugDropBomb,
}
impl<'db> InferContext<'db> {
pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self {
Self {
db,
scope,
file: scope.file(db),
diagnostics: std::cell::RefCell::new(TypeCheckDiagnostics::default()),
no_type_check: InNoTypeCheck::default(),
bomb: DebugDropBomb::new("`InferContext` needs to be explicitly consumed by calling `::finish` to prevent accidental loss of diagnostics."),
}
}
/// The file for which the types are inferred.
pub(crate) fn file(&self) -> File {
self.file
}
pub(crate) fn db(&self) -> &'db dyn Db {
self.db
}
pub(crate) fn extend<T>(&mut self, other: &T)
where
T: WithDiagnostics,
{
self.diagnostics.get_mut().extend(other.diagnostics());
}
/// Reports a lint located at `node`.
pub(super) fn report_lint(
&self,
lint: &'static LintMetadata,
node: AnyNodeRef,
message: fmt::Arguments,
) {
if !self.db.is_file_open(self.file) {
return;
}
// Skip over diagnostics if the rule is disabled.
let Some(severity) = self.db.rule_selection().severity(LintId::of(lint)) else {
return;
};
if self.is_in_no_type_check() {
return;
}
let suppressions = suppressions(self.db, self.file);
if let Some(suppression) = suppressions.find_suppression(node.range(), LintId::of(lint)) {
self.diagnostics.borrow_mut().mark_used(suppression.id());
return;
}
self.report_diagnostic(node, DiagnosticId::Lint(lint.name()), severity, message);
}
/// Adds a new diagnostic.
///
/// The diagnostic does not get added if the rule isn't enabled for this file.
pub(super) fn report_diagnostic(
&self,
node: AnyNodeRef,
id: DiagnosticId,
severity: Severity,
message: fmt::Arguments,
) {
if !self.db.is_file_open(self.file) {
return;
}
// TODO: Don't emit the diagnostic if:
// * The enclosing node contains any syntax errors
// * The rule is disabled for this file. We probably want to introduce a new query that
// returns a rule selector for a given file that respects the package's settings,
// any global pragma comments in the file, and any per-file-ignores.
self.diagnostics.borrow_mut().push(TypeCheckDiagnostic {
file: self.file,
id,
message: message.to_string(),
range: node.range(),
severity,
});
}
pub(super) fn set_in_no_type_check(&mut self, no_type_check: InNoTypeCheck) {
self.no_type_check = no_type_check;
}
fn is_in_no_type_check(&self) -> bool {
match self.no_type_check {
InNoTypeCheck::Possibly => {
// Accessing the semantic index here is fine because
// the index belongs to the same file as for which we emit the diagnostic.
let index = semantic_index(self.db, self.file);
let scope_id = self.scope.file_scope_id(self.db);
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
let mut function_scope_tys = index
.ancestor_scopes(scope_id)
.filter_map(|(_, scope)| scope.node().as_function())
.filter_map(|function| {
binding_ty(self.db, index.definition(function)).into_function_literal()
});
// Iterate over all functions and test if any is decorated with `@no_type_check`.
function_scope_tys.any(|function_ty| {
function_ty
.decorators(self.db)
.iter()
.filter_map(|decorator| decorator.into_function_literal())
.any(|decorator_ty| {
decorator_ty.is_known(self.db, KnownFunction::NoTypeCheck)
})
})
}
InNoTypeCheck::Yes => true,
}
}
#[must_use]
pub(crate) fn finish(mut self) -> TypeCheckDiagnostics {
self.bomb.defuse();
let mut diagnostics = self.diagnostics.into_inner();
diagnostics.shrink_to_fit();
diagnostics
}
}
impl fmt::Debug for InferContext<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("TyContext")
.field("file", &self.file)
.field("diagnostics", &self.diagnostics)
.field("defused", &self.bomb)
.finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub(crate) enum InNoTypeCheck {
/// The inference might be in a `no_type_check` block but only if any
/// ancestor function is decorated with `@no_type_check`.
#[default]
Possibly,
/// The inference is known to be in an `@no_type_check` decorated function.
Yes,
}
pub(crate) trait WithDiagnostics {
fn diagnostics(&self) -> &TypeCheckDiagnostics;
}

View File

@@ -1,18 +1,15 @@
use super::context::InferContext;
use crate::declare_lint;
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
use crate::suppression::FileSuppressionId;
use crate::lint::{Level, LintId, LintMetadata, LintRegistryBuilder, LintStatus};
use crate::types::string_annotation::{
BYTE_STRING_TYPE_ANNOTATION, ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, FSTRING_TYPE_ANNOTATION,
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::{ClassLiteralType, Type};
use crate::{declare_lint, Db};
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashSet;
use std::borrow::Cow;
use std::fmt::Formatter;
use std::ops::Deref;
@@ -27,7 +24,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
registry.register_lint(&DIVISION_BY_ZERO);
registry.register_lint(&DUPLICATE_BASE);
registry.register_lint(&INCOMPATIBLE_SLOTS);
registry.register_lint(&INCONSISTENT_MRO);
registry.register_lint(&INDEX_OUT_OF_BOUNDS);
registry.register_lint(&INVALID_ASSIGNMENT);
@@ -36,7 +32,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_DECLARATION);
registry.register_lint(&INVALID_EXCEPTION_CAUGHT);
registry.register_lint(&INVALID_PARAMETER_DEFAULT);
registry.register_lint(&INVALID_RAISE);
registry.register_lint(&INVALID_TYPE_FORM);
registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS);
registry.register_lint(&NON_SUBSCRIPTABLE);
@@ -44,7 +39,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&POSSIBLY_UNBOUND_ATTRIBUTE);
registry.register_lint(&POSSIBLY_UNBOUND_IMPORT);
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
registry.register_lint(&SUBCLASS_OF_FINAL_CLASS);
registry.register_lint(&UNDEFINED_REVEAL);
registry.register_lint(&UNRESOLVED_ATTRIBUTE);
registry.register_lint(&UNRESOLVED_IMPORT);
@@ -149,64 +143,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for classes whose bases define incompatible `__slots__`.
///
/// ## Why is this bad?
/// Inheriting from bases with incompatible `__slots__`s
/// will lead to a `TypeError` at runtime.
///
/// Classes with no or empty `__slots__` are always compatible:
///
/// ```python
/// class A: ...
/// class B:
/// __slots__ = ()
/// class C:
/// __slots__ = ("a", "b")
///
/// # fine
/// class D(A, B, C): ...
/// ```
///
/// Multiple inheritance from more than one different class
/// defining non-empty `__slots__` is not allowed:
///
/// ```python
/// class A:
/// __slots__ = ("a", "b")
///
/// class B:
/// __slots__ = ("a", "b") # Even if the values are the same
///
/// # TypeError: multiple bases have instance lay-out conflict
/// class C(A, B): ...
/// ```
///
/// ## Known problems
/// Dynamic (not tuple or string literal) `__slots__` are not checked.
/// Additionally, classes inheriting from built-in classes with implicit layouts
/// like `str` or `int` are also not checked.
///
/// ```pycon
/// >>> hasattr(int, "__slots__")
/// False
/// >>> hasattr(str, "__slots__")
/// False
/// >>> class A(int, str): ...
/// Traceback (most recent call last):
/// File "<python-input-0>", line 1, in <module>
/// class A(int, str): ...
/// TypeError: multiple bases have instance lay-out conflict
/// ```
pub(crate) static INCOMPATIBLE_SLOTS = {
summary: "detects class definitions whose MRO has conflicting `__slots__`",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// TODO #14889
pub(crate) static INCONSISTENT_MRO = {
@@ -310,49 +246,6 @@ declare_lint! {
}
}
declare_lint! {
/// Checks for `raise` statements that raise non-exceptions or use invalid
/// causes for their raised exceptions.
///
/// ## Why is this bad?
/// Only subclasses or instances of `BaseException` can be raised.
/// For an exception's cause, the same rules apply, except that `None` is also
/// permitted. Violating these rules results in a `TypeError` at runtime.
///
/// ## Examples
/// ```python
/// def f():
/// try:
/// something()
/// except NameError:
/// raise "oops!" from f
///
/// def g():
/// raise NotImplemented from 42
/// ```
///
/// Use instead:
/// ```python
/// def f():
/// try:
/// something()
/// except NameError as e:
/// raise RuntimeError("oops!") from e
///
/// def g():
/// raise NotImplementedError from None
/// ```
///
/// ## References
/// - [Python documentation: The `raise` statement](https://docs.python.org/3/reference/simple_stmts.html#raise)
/// - [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions)
pub(crate) static INVALID_RAISE = {
summary: "detects `raise` statements that raise invalid exceptions or use invalid causes",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// ## What it does
/// Checks for invalid type expressions.
@@ -456,29 +349,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for classes that subclass final classes.
///
/// ## Why is this bad?
/// Decorating a class with `@final` declares to the type checker that it should not be subclassed.
///
/// ## Example
///
/// ```python
/// from typing import final
///
/// @final
/// class A: ...
/// class B(A): ... # Error raised here
/// ```
pub(crate) static SUBCLASS_OF_FINAL_CLASS = {
summary: "detects subclasses of final classes",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// ## What it does
/// Checks for calls to `reveal_type` without importing it.
@@ -572,11 +442,11 @@ declare_lint! {
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct TypeCheckDiagnostic {
pub(crate) id: DiagnosticId,
pub(crate) message: String,
pub(crate) range: TextRange,
pub(crate) severity: Severity,
pub(crate) file: File,
pub(super) id: DiagnosticId,
pub(super) message: String,
pub(super) range: TextRange,
pub(super) severity: Severity,
pub(super) file: File,
}
impl TypeCheckDiagnostic {
@@ -630,41 +500,41 @@ impl Ranged for TypeCheckDiagnostic {
/// each Salsa-struct comes with an overhead.
#[derive(Default, Eq, PartialEq)]
pub struct TypeCheckDiagnostics {
diagnostics: Vec<Arc<TypeCheckDiagnostic>>,
used_suppressions: FxHashSet<FileSuppressionId>,
inner: Vec<std::sync::Arc<TypeCheckDiagnostic>>,
}
impl TypeCheckDiagnostics {
pub(crate) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
self.diagnostics.push(Arc::new(diagnostic));
}
pub(super) fn extend(&mut self, other: &TypeCheckDiagnostics) {
self.diagnostics.extend_from_slice(&other.diagnostics);
self.used_suppressions.extend(&other.used_suppressions);
}
pub(crate) fn mark_used(&mut self, suppression_id: FileSuppressionId) {
self.used_suppressions.insert(suppression_id);
}
pub(crate) fn is_used(&self, suppression_id: FileSuppressionId) -> bool {
self.used_suppressions.contains(&suppression_id)
}
pub(crate) fn used_len(&self) -> usize {
self.used_suppressions.len()
pub(super) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
self.inner.push(Arc::new(diagnostic));
}
pub(crate) fn shrink_to_fit(&mut self) {
self.used_suppressions.shrink_to_fit();
self.diagnostics.shrink_to_fit();
self.inner.shrink_to_fit();
}
}
impl Extend<TypeCheckDiagnostic> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = TypeCheckDiagnostic>>(&mut self, iter: T) {
self.inner.extend(iter.into_iter().map(std::sync::Arc::new));
}
}
impl Extend<std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
self.inner.extend(iter);
}
}
impl<'a> Extend<&'a std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
fn extend<T: IntoIterator<Item = &'a Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
self.inner
.extend(iter.into_iter().map(std::sync::Arc::clone));
}
}
impl std::fmt::Debug for TypeCheckDiagnostics {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.diagnostics.fmt(f)
self.inner.fmt(f)
}
}
@@ -672,7 +542,7 @@ impl Deref for TypeCheckDiagnostics {
type Target = [std::sync::Arc<TypeCheckDiagnostic>];
fn deref(&self) -> &Self::Target {
&self.diagnostics
&self.inner
}
}
@@ -681,7 +551,7 @@ impl IntoIterator for TypeCheckDiagnostics {
type IntoIter = std::vec::IntoIter<std::sync::Arc<TypeCheckDiagnostic>>;
fn into_iter(self) -> Self::IntoIter {
self.diagnostics.into_iter()
self.inner.into_iter()
}
}
@@ -690,193 +560,227 @@ impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
type IntoIter = std::slice::Iter<'a, std::sync::Arc<TypeCheckDiagnostic>>;
fn into_iter(self) -> Self::IntoIter {
self.diagnostics.iter()
self.inner.iter()
}
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
pub(super) fn report_not_iterable(context: &InferContext, node: AnyNodeRef, not_iterable_ty: Type) {
context.report_lint(
&NOT_ITERABLE,
node,
format_args!(
"Object of type `{}` is not iterable",
not_iterable_ty.display(context.db())
),
);
pub(super) struct TypeCheckDiagnosticsBuilder<'db> {
db: &'db dyn Db,
file: File,
diagnostics: TypeCheckDiagnostics,
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
/// because its `__iter__` method is possibly unbound.
pub(super) fn report_not_iterable_possibly_unbound(
context: &InferContext,
node: AnyNodeRef,
element_ty: Type,
) {
context.report_lint(
&NOT_ITERABLE,
node,
format_args!(
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
element_ty.display(context.db())
),
);
}
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
pub(super) fn report_index_out_of_bounds(
context: &InferContext,
kind: &'static str,
node: AnyNodeRef,
tuple_ty: Type,
length: usize,
index: i64,
) {
context.report_lint(
&INDEX_OUT_OF_BOUNDS,
node,
format_args!(
"Index {index} is out of bounds for {kind} `{}` with length {length}",
tuple_ty.display(context.db())
),
);
}
/// Emit a diagnostic declaring that a type does not support subscripting.
pub(super) fn report_non_subscriptable(
context: &InferContext,
node: AnyNodeRef,
non_subscriptable_ty: Type,
method: &str,
) {
context.report_lint(
&NON_SUBSCRIPTABLE,
node,
format_args!(
"Cannot subscript object of type `{}` with no `{method}` method",
non_subscriptable_ty.display(context.db())
),
);
}
pub(super) fn report_unresolved_module<'db>(
context: &InferContext,
import_node: impl Into<AnyNodeRef<'db>>,
level: u32,
module: Option<&str>,
) {
context.report_lint(
&UNRESOLVED_IMPORT,
import_node.into(),
format_args!(
"Cannot resolve import `{}{}`",
".".repeat(level as usize),
module.unwrap_or_default()
),
);
}
pub(super) fn report_slice_step_size_zero(context: &InferContext, node: AnyNodeRef) {
context.report_lint(
&ZERO_STEPSIZE_IN_SLICE,
node,
format_args!("Slice step size can not be zero"),
);
}
pub(super) fn report_invalid_assignment(
context: &InferContext,
node: AnyNodeRef,
declared_ty: Type,
assigned_ty: Type,
) {
match declared_ty {
Type::ClassLiteral(ClassLiteralType { class }) => {
context.report_lint(&INVALID_ASSIGNMENT, node, format_args!(
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
class.name(context.db())));
}
Type::FunctionLiteral(function) => {
context.report_lint(&INVALID_ASSIGNMENT, node, format_args!(
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
function.name(context.db())));
}
_ => {
context.report_lint(
&INVALID_ASSIGNMENT,
node,
format_args!(
"Object of type `{}` is not assignable to `{}`",
assigned_ty.display(context.db()),
declared_ty.display(context.db()),
),
);
impl<'db> TypeCheckDiagnosticsBuilder<'db> {
pub(super) fn new(db: &'db dyn Db, file: File) -> Self {
Self {
db,
file,
diagnostics: TypeCheckDiagnostics::default(),
}
}
}
pub(super) fn report_possibly_unresolved_reference(
context: &InferContext,
expr_name_node: &ast::ExprName,
) {
let ast::ExprName { id, .. } = expr_name_node;
context.report_lint(
&POSSIBLY_UNRESOLVED_REFERENCE,
expr_name_node.into(),
format_args!("Name `{id}` used when possibly not defined"),
);
}
pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
context.report_lint(
&UNRESOLVED_REFERENCE,
expr_name_node.into(),
format_args!("Name `{id}` used when not defined"),
);
}
pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) {
context.report_lint(
&INVALID_EXCEPTION_CAUGHT,
node.into(),
format_args!(
"Cannot catch object of type `{}` in an exception handler \
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
ty.display(context.db())
),
);
}
pub(crate) fn report_invalid_exception_raised(context: &InferContext, node: &ast::Expr, ty: Type) {
context.report_lint(
&INVALID_RAISE,
node.into(),
format_args!(
"Cannot raise object of type `{}` (must be a `BaseException` subclass or instance)",
ty.display(context.db())
),
);
}
pub(crate) fn report_invalid_exception_cause(context: &InferContext, node: &ast::Expr, ty: Type) {
context.report_lint(
&INVALID_RAISE,
node.into(),
format_args!(
"Cannot use object of type `{}` as exception cause \
(must be a `BaseException` subclass or instance or `None`)",
ty.display(context.db())
),
);
}
pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node: &ast::Expr) {
context.report_lint(
&INCOMPATIBLE_SLOTS,
node.into(),
format_args!("Class base has incompatible `__slots__`"),
);
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
self.add_lint(
&NOT_ITERABLE,
node,
format_args!(
"Object of type `{}` is not iterable",
not_iterable_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
/// because its `__iter__` method is possibly unbound.
pub(super) fn add_not_iterable_possibly_unbound(
&mut self,
node: AnyNodeRef,
element_ty: Type<'db>,
) {
self.add_lint(
&NOT_ITERABLE,
node,
format_args!(
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
element_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
pub(super) fn add_index_out_of_bounds(
&mut self,
kind: &'static str,
node: AnyNodeRef,
tuple_ty: Type<'db>,
length: usize,
index: i64,
) {
self.add_lint(
&INDEX_OUT_OF_BOUNDS,
node,
format_args!(
"Index {index} is out of bounds for {kind} `{}` with length {length}",
tuple_ty.display(self.db)
),
);
}
/// Emit a diagnostic declaring that a type does not support subscripting.
pub(super) fn add_non_subscriptable(
&mut self,
node: AnyNodeRef,
non_subscriptable_ty: Type<'db>,
method: &str,
) {
self.add_lint(
&NON_SUBSCRIPTABLE,
node,
format_args!(
"Cannot subscript object of type `{}` with no `{method}` method",
non_subscriptable_ty.display(self.db)
),
);
}
pub(super) fn add_unresolved_module(
&mut self,
import_node: impl Into<AnyNodeRef<'db>>,
level: u32,
module: Option<&str>,
) {
self.add_lint(
&UNRESOLVED_IMPORT,
import_node.into(),
format_args!(
"Cannot resolve import `{}{}`",
".".repeat(level as usize),
module.unwrap_or_default()
),
);
}
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
self.add_lint(
&ZERO_STEPSIZE_IN_SLICE,
node,
format_args!("Slice step size can not be zero"),
);
}
pub(super) fn add_invalid_assignment(
&mut self,
node: AnyNodeRef,
declared_ty: Type<'db>,
assigned_ty: Type<'db>,
) {
match declared_ty {
Type::ClassLiteral(ClassLiteralType { class }) => {
self.add_lint(&INVALID_ASSIGNMENT, node, format_args!(
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
class.name(self.db)));
}
Type::FunctionLiteral(function) => {
self.add_lint(&INVALID_ASSIGNMENT, node, format_args!(
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
function.name(self.db)));
}
_ => {
self.add_lint(
&INVALID_ASSIGNMENT,
node,
format_args!(
"Object of type `{}` is not assignable to `{}`",
assigned_ty.display(self.db),
declared_ty.display(self.db),
),
);
}
}
}
pub(super) fn add_possibly_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
self.add_lint(
&POSSIBLY_UNRESOLVED_REFERENCE,
expr_name_node.into(),
format_args!("Name `{id}` used when possibly not defined"),
);
}
pub(super) fn add_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
let ast::ExprName { id, .. } = expr_name_node;
self.add_lint(
&UNRESOLVED_REFERENCE,
expr_name_node.into(),
format_args!("Name `{id}` used when not defined"),
);
}
pub(super) fn add_invalid_exception_caught(&mut self, db: &dyn Db, node: &ast::Expr, ty: Type) {
self.add_lint(
&INVALID_EXCEPTION_CAUGHT,
node.into(),
format_args!(
"Cannot catch object of type `{}` in an exception handler \
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
ty.display(db)
),
);
}
pub(super) fn add_lint(
&mut self,
lint: &'static LintMetadata,
node: AnyNodeRef,
message: std::fmt::Arguments,
) {
// Skip over diagnostics if the rule is disabled.
let Some(severity) = self.db.rule_selection().severity(LintId::of(lint)) else {
return;
};
self.add(node, DiagnosticId::Lint(lint.name()), severity, message);
}
/// Adds a new diagnostic.
///
/// The diagnostic does not get added if the rule isn't enabled for this file.
pub(super) fn add(
&mut self,
node: AnyNodeRef,
id: DiagnosticId,
severity: Severity,
message: std::fmt::Arguments,
) {
if !self.db.is_file_open(self.file) {
return;
}
// TODO: Don't emit the diagnostic if:
// * The enclosing node contains any syntax errors
// * The rule is disabled for this file. We probably want to introduce a new query that
// returns a rule selector for a given file that respects the package's settings,
// any global pragma comments in the file, and any per-file-ignores.
self.diagnostics.push(TypeCheckDiagnostic {
file: self.file,
id,
message: message.to_string(),
range: node.range(),
severity,
});
}
pub(super) fn extend(&mut self, diagnostics: &TypeCheckDiagnostics) {
self.diagnostics.extend(diagnostics);
}
pub(super) fn finish(mut self) -> TypeCheckDiagnostics {
self.diagnostics.shrink_to_fit();
self.diagnostics
}
}

View File

@@ -324,12 +324,6 @@ impl<'db> TypeArrayDisplay<'db> for Vec<Type<'db>> {
}
}
impl<'db> TypeArrayDisplay<'db> for [Type<'db>] {
fn display(&self, db: &'db dyn Db) -> DisplayTypeArray {
DisplayTypeArray { types: self, db }
}
}
pub(crate) struct DisplayTypeArray<'b, 'db> {
types: &'b [Type<'db>],
db: &'db dyn Db,

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,5 @@
use crate::semantic_index::ast_ids::HasScopedExpressionId;
use crate::semantic_index::constraint::{
Constraint, ConstraintNode, PatternConstraint, PatternConstraintKind,
};
use crate::semantic_index::constraint::{Constraint, ConstraintNode, PatternConstraint};
use crate::semantic_index::definition::Definition;
use crate::semantic_index::expression::Expression;
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
@@ -218,12 +216,31 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
) -> Option<NarrowingConstraints<'db>> {
let subject = pattern.subject(self.db);
match pattern.kind(self.db) {
PatternConstraintKind::Singleton(singleton, _guard) => {
self.evaluate_match_pattern_singleton(*subject, *singleton)
match pattern.pattern(self.db).node() {
ast::Pattern::MatchValue(_) => {
None // TODO
}
ast::Pattern::MatchSingleton(singleton_pattern) => {
self.evaluate_match_pattern_singleton(subject, singleton_pattern)
}
ast::Pattern::MatchSequence(_) => {
None // TODO
}
ast::Pattern::MatchMapping(_) => {
None // TODO
}
ast::Pattern::MatchClass(_) => {
None // TODO
}
ast::Pattern::MatchStar(_) => {
None // TODO
}
ast::Pattern::MatchAs(_) => {
None // TODO
}
ast::Pattern::MatchOr(_) => {
None // TODO
}
// TODO: support more pattern kinds
PatternConstraintKind::Value(..) | PatternConstraintKind::Unsupported => None,
}
}
@@ -466,14 +483,14 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
fn evaluate_match_pattern_singleton(
&mut self,
subject: Expression<'db>,
singleton: ast::Singleton,
subject: &ast::Expr,
pattern: &ast::PatternMatchSingleton,
) -> Option<NarrowingConstraints<'db>> {
if let Some(ast::ExprName { id, .. }) = subject.node_ref(self.db).as_name_expr() {
if let Some(ast::ExprName { id, .. }) = subject.as_name_expr() {
// SAFETY: we should always have a symbol for every Name node.
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
let ty = match singleton {
let ty = match pattern.value {
ast::Singleton::None => Type::none(self.db),
ast::Singleton::True => Type::BooleanLiteral(true),
ast::Singleton::False => Type::BooleanLiteral(false),

View File

@@ -1,103 +0,0 @@
use ruff_python_ast as ast;
use crate::db::Db;
use crate::symbol::{Boundness, Symbol};
use crate::types::class_base::ClassBase;
use crate::types::diagnostic::report_base_with_incompatible_slots;
use crate::types::{Class, ClassLiteralType, Type};
use super::InferContext;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum SlotsKind {
/// `__slots__` is not found in the class.
NotSpecified,
/// `__slots__` is defined but empty: `__slots__ = ()`.
Empty,
/// `__slots__` is defined and is not empty: `__slots__ = ("a", "b")`.
NotEmpty,
/// `__slots__` is defined but its value is dynamic:
/// * `__slots__ = tuple(a for a in b)`
/// * `__slots__ = ["a", "b"]`
Dynamic,
}
impl SlotsKind {
fn from(db: &dyn Db, base: Class) -> Self {
let Symbol::Type(slots_ty, bound) = base.own_class_member(db, "__slots__") else {
return Self::NotSpecified;
};
if matches!(bound, Boundness::PossiblyUnbound) {
return Self::Dynamic;
};
match slots_ty {
// __slots__ = ("a", "b")
Type::Tuple(tuple) => {
if tuple.elements(db).is_empty() {
Self::Empty
} else {
Self::NotEmpty
}
}
// __slots__ = "abc" # Same as `("abc",)`
Type::StringLiteral(_) => Self::NotEmpty,
_ => Self::Dynamic,
}
}
}
pub(super) fn check_class_slots(context: &InferContext, class: Class, node: &ast::StmtClassDef) {
let db = context.db();
let mut first_with_solid_base = None;
let mut common_solid_base = None;
let mut found_second = false;
for (index, base) in class.explicit_bases(db).iter().enumerate() {
let Type::ClassLiteral(ClassLiteralType { class: base }) = base else {
continue;
};
let solid_base = base.iter_mro(db).find_map(|current| {
let ClassBase::Class(current) = current else {
return None;
};
match SlotsKind::from(db, current) {
SlotsKind::NotEmpty => Some(current),
SlotsKind::NotSpecified | SlotsKind::Empty => None,
SlotsKind::Dynamic => None,
}
});
if solid_base.is_none() {
continue;
}
let base_node = &node.bases()[index];
if first_with_solid_base.is_none() {
first_with_solid_base = Some(index);
common_solid_base = solid_base;
continue;
}
if solid_base == common_solid_base {
continue;
}
found_second = true;
report_base_with_incompatible_slots(context, base_node);
}
if found_second {
if let Some(index) = first_with_solid_base {
let base_node = &node.bases()[index];
report_base_with_incompatible_slots(context, base_node);
};
}
}

View File

@@ -1,13 +1,13 @@
use ruff_db::files::File;
use ruff_db::source::source_text;
use ruff_python_ast::str::raw_contents;
use ruff_python_ast::{self as ast, ModExpression, StringFlags};
use ruff_python_parser::{parse_expression_range, Parsed};
use ruff_text_size::Ranged;
use crate::declare_lint;
use crate::lint::{Level, LintStatus};
use super::context::InferContext;
use crate::types::diagnostic::{TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::{declare_lint, Db};
declare_lint! {
/// ## What it does
@@ -127,23 +127,24 @@ declare_lint! {
}
}
type AnnotationParseResult = Result<Parsed<ModExpression>, TypeCheckDiagnostics>;
/// Parses the given expression as a string annotation.
pub(crate) fn parse_string_annotation(
context: &InferContext,
db: &dyn Db,
file: File,
string_expr: &ast::ExprStringLiteral,
) -> Option<Parsed<ModExpression>> {
let file = context.file();
let db = context.db();
) -> AnnotationParseResult {
let _span = tracing::trace_span!("parse_string_annotation", string=?string_expr.range(), file=%file.path(db)).entered();
let source = source_text(db.upcast(), file);
let node_text = &source[string_expr.range()];
let mut diagnostics = TypeCheckDiagnosticsBuilder::new(db, file);
if let [string_literal] = string_expr.value.as_slice() {
let prefix = string_literal.flags.prefix();
if prefix.is_raw() {
context.report_lint(
diagnostics.add_lint(
&RAW_STRING_TYPE_ANNOTATION,
string_literal.into(),
format_args!("Type expressions cannot use raw string literal"),
@@ -166,8 +167,8 @@ pub(crate) fn parse_string_annotation(
// """ = 1
// ```
match parse_expression_range(source.as_str(), range_excluding_quotes) {
Ok(parsed) => return Some(parsed),
Err(parse_error) => context.report_lint(
Ok(parsed) => return Ok(parsed),
Err(parse_error) => diagnostics.add_lint(
&INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
string_literal.into(),
format_args!("Syntax error in forward annotation: {}", parse_error.error),
@@ -176,7 +177,7 @@ pub(crate) fn parse_string_annotation(
} else {
// The raw contents of the string doesn't match the parsed content. This could be the
// case for annotations that contain escape sequences.
context.report_lint(
diagnostics.add_lint(
&ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION,
string_expr.into(),
format_args!("Type expressions cannot contain escape characters"),
@@ -184,12 +185,12 @@ pub(crate) fn parse_string_annotation(
}
} else {
// String is implicitly concatenated.
context.report_lint(
diagnostics.add_lint(
&IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION,
string_expr.into(),
format_args!("Type expressions cannot span multiple string literals"),
);
}
None
Err(diagnostics.finish())
}

View File

@@ -1,249 +1,126 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use rustc_hash::FxHashMap;
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
use rustc_hash::FxHashMap;
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
use crate::semantic_index::symbol::ScopeId;
use crate::types::{infer_expression_types, todo_type, Type, TypeCheckDiagnostics};
use crate::unpack::UnpackValue;
use crate::types::{todo_type, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
use crate::Db;
use super::context::{InferContext, WithDiagnostics};
use super::diagnostic::INVALID_ASSIGNMENT;
use super::{TupleType, UnionType};
/// Unpacks the value expression type to their respective targets.
pub(crate) struct Unpacker<'db> {
context: InferContext<'db>,
scope: ScopeId<'db>,
db: &'db dyn Db,
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
diagnostics: TypeCheckDiagnosticsBuilder<'db>,
}
impl<'db> Unpacker<'db> {
pub(crate) fn new(db: &'db dyn Db, scope: ScopeId<'db>) -> Self {
pub(crate) fn new(db: &'db dyn Db, file: File) -> Self {
Self {
context: InferContext::new(db, scope),
db,
targets: FxHashMap::default(),
scope,
diagnostics: TypeCheckDiagnosticsBuilder::new(db, file),
}
}
fn db(&self) -> &'db dyn Db {
self.context.db()
}
/// Unpack the value to the target expression.
pub(crate) fn unpack(&mut self, target: &ast::Expr, value: UnpackValue<'db>) {
debug_assert!(
matches!(target, ast::Expr::List(_) | ast::Expr::Tuple(_)),
"Unpacking target must be a list or tuple expression"
);
let mut value_ty = infer_expression_types(self.db(), value.expression())
.expression_ty(value.scoped_expression_id(self.db(), self.scope));
if value.is_iterable() {
// If the value is an iterable, then the type that needs to be unpacked is the iterator
// type.
value_ty = value_ty
.iterate(self.db())
.unwrap_with_diagnostic(&self.context, value.as_any_node_ref(self.db()));
}
self.unpack_inner(target, value_ty);
}
fn unpack_inner(&mut self, target: &ast::Expr, value_ty: Type<'db>) {
pub(crate) fn unpack(&mut self, target: &ast::Expr, value_ty: Type<'db>, scope: ScopeId<'db>) {
match target {
ast::Expr::Name(target_name) => {
self.targets.insert(
target_name.scoped_expression_id(self.db(), self.scope),
value_ty,
);
self.targets
.insert(target_name.scoped_expression_id(self.db, scope), value_ty);
}
ast::Expr::Starred(ast::ExprStarred { value, .. }) => {
self.unpack_inner(value, value_ty);
self.unpack(value, value_ty, scope);
}
ast::Expr::List(ast::ExprList { elts, .. })
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
// Initialize the vector of target types, one for each target.
//
// This is mainly useful for the union type where the target type at index `n` is
// going to be a union of types from every union type element at index `n`.
//
// For example, if the type is `tuple[int, int] | tuple[int, str]` and the target
// has two elements `(a, b)`, then
// * The type of `a` will be a union of `int` and `int` which are at index 0 in the
// first and second tuple respectively which resolves to an `int`.
// * Similarly, the type of `b` will be a union of `int` and `str` which are at
// index 1 in the first and second tuple respectively which will be `int | str`.
let mut target_types = vec![vec![]; elts.len()];
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => match value_ty {
Type::Tuple(tuple_ty) => {
let starred_index = elts.iter().position(ast::Expr::is_starred_expr);
let unpack_types = match value_ty {
Type::Union(union_ty) => union_ty.elements(self.db()),
_ => std::slice::from_ref(&value_ty),
};
let element_types = if let Some(starred_index) = starred_index {
if tuple_ty.len(self.db) >= elts.len() - 1 {
let mut element_types = Vec::with_capacity(elts.len());
element_types.extend_from_slice(
// SAFETY: Safe because of the length check above.
&tuple_ty.elements(self.db)[..starred_index],
);
for ty in unpack_types.iter().copied() {
// Deconstruct certain types to delegate the inference back to the tuple type
// for correct handling of starred expressions.
let ty = match ty {
Type::StringLiteral(string_literal_ty) => {
// We could go further and deconstruct to an array of `StringLiteral`
// with each individual character, instead of just an array of
// `LiteralString`, but there would be a cost and it's not clear that
// it's worth it.
Type::tuple(
self.db(),
std::iter::repeat(Type::LiteralString)
.take(string_literal_ty.python_len(self.db())),
)
}
_ => ty,
};
// E.g., in `(a, *b, c, d) = ...`, the index of starred element `b`
// is 1 and the remaining elements after that are 2.
let remaining = elts.len() - (starred_index + 1);
// This index represents the type of the last element that belongs
// to the starred expression, in an exclusive manner.
let starred_end_index = tuple_ty.len(self.db) - remaining;
// SAFETY: Safe because of the length check above.
let _starred_element_types =
&tuple_ty.elements(self.db)[starred_index..starred_end_index];
// 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"));
if let Some(tuple_ty) = ty.into_tuple() {
let tuple_ty_elements = self.tuple_ty_elements(target, elts, tuple_ty);
match elts.len().cmp(&tuple_ty_elements.len()) {
Ordering::Less => {
self.context.report_lint(
&INVALID_ASSIGNMENT,
target.into(),
format_args!(
"Too many values to unpack (expected {}, got {})",
elts.len(),
tuple_ty_elements.len()
),
);
}
Ordering::Greater => {
self.context.report_lint(
&INVALID_ASSIGNMENT,
target.into(),
format_args!(
"Not enough values to unpack (expected {}, got {})",
elts.len(),
tuple_ty_elements.len()
),
);
}
Ordering::Equal => {}
}
for (index, ty) in tuple_ty_elements.iter().enumerate() {
if let Some(element_types) = target_types.get_mut(index) {
element_types.push(*ty);
}
element_types.extend_from_slice(
// SAFETY: Safe because of the length check above.
&tuple_ty.elements(self.db)[starred_end_index..],
);
Cow::Owned(element_types)
} else {
let mut element_types = tuple_ty.elements(self.db).to_vec();
// Subtract 1 to insert the starred expression type at the correct
// index.
element_types.resize(elts.len() - 1, Type::Unknown);
// TODO: This should be `list[Unknown]`
element_types.insert(starred_index, todo_type!("starred unpacking"));
Cow::Owned(element_types)
}
} else {
let ty = if ty.is_literal_string() {
Type::LiteralString
} else {
ty.iterate(self.db())
.unwrap_with_diagnostic(&self.context, AnyNodeRef::from(target))
};
for target_type in &mut target_types {
target_type.push(ty);
}
Cow::Borrowed(tuple_ty.elements(self.db).as_ref())
};
for (index, element) in elts.iter().enumerate() {
self.unpack(
element,
element_types.get(index).copied().unwrap_or(Type::Unknown),
scope,
);
}
}
for (index, element) in elts.iter().enumerate() {
// SAFETY: `target_types` is initialized with the same length as `elts`.
let element_ty = match target_types[index].as_slice() {
[] => Type::Unknown,
types => UnionType::from_elements(self.db(), types),
};
self.unpack_inner(element, element_ty);
Type::StringLiteral(string_literal_ty) => {
// Deconstruct the string literal to delegate the inference back to the
// tuple type for correct handling of starred expressions. We could go
// further and deconstruct to an array of `StringLiteral` with each
// individual character, instead of just an array of `LiteralString`, but
// there would be a cost and it's not clear that it's worth it.
let value_ty = Type::tuple(
self.db,
std::iter::repeat(Type::LiteralString)
.take(string_literal_ty.python_len(self.db)),
);
self.unpack(target, value_ty, scope);
}
}
_ => {
let value_ty = if value_ty.is_literal_string() {
Type::LiteralString
} else {
value_ty
.iterate(self.db)
.unwrap_with_diagnostic(AnyNodeRef::from(target), &mut self.diagnostics)
};
for element in elts {
self.unpack(element, value_ty, scope);
}
}
},
_ => {}
}
}
/// Returns the [`Type`] elements inside the given [`TupleType`] taking into account that there
/// can be a starred expression in the `elements`.
fn tuple_ty_elements(
&self,
expr: &ast::Expr,
targets: &[ast::Expr],
tuple_ty: TupleType<'db>,
) -> Cow<'_, [Type<'db>]> {
// If there is a starred expression, it will consume all of the types at that location.
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
// Otherwise, the types will be unpacked 1-1 to the targets.
return Cow::Borrowed(tuple_ty.elements(self.db()).as_ref());
};
if tuple_ty.len(self.db()) >= targets.len() - 1 {
// This branch is only taken when there are enough elements in the tuple type to
// combine for the starred expression. So, the arithmetic and indexing operations are
// safe to perform.
let mut element_types = Vec::with_capacity(targets.len());
// Insert all the elements before the starred expression.
element_types.extend_from_slice(
// SAFETY: Safe because of the length check above.
&tuple_ty.elements(self.db())[..starred_index],
);
// The number of target expressions that are remaining after the starred expression.
// For example, in `(a, *b, c, d) = ...`, the index of starred element `b` is 1 and the
// remaining elements after that are 2.
let remaining = targets.len() - (starred_index + 1);
// This index represents the position of the last element that belongs to the starred
// expression, in an exclusive manner. For example, in `(a, *b, c) = (1, 2, 3, 4)`, the
// starred expression `b` will consume the elements `Literal[2]` and `Literal[3]` and
// the index value would be 3.
let starred_end_index = tuple_ty.len(self.db()) - remaining;
// SAFETY: Safe because of the length check above.
let _starred_element_types =
&tuple_ty.elements(self.db())[starred_index..starred_end_index];
// 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"));
// Insert the types remaining that aren't consumed by the starred expression.
element_types.extend_from_slice(
// SAFETY: Safe because of the length check above.
&tuple_ty.elements(self.db())[starred_end_index..],
);
Cow::Owned(element_types)
} else {
self.context.report_lint(
&INVALID_ASSIGNMENT,
expr.into(),
format_args!(
"Not enough values to unpack (expected {} or more, got {})",
targets.len() - 1,
tuple_ty.len(self.db())
),
);
let mut element_types = tuple_ty.elements(self.db()).to_vec();
// Subtract 1 to insert the starred expression type at the correct
// index.
element_types.resize(targets.len() - 1, Type::Unknown);
// TODO: This should be `list[Unknown]`
element_types.insert(starred_index, todo_type!("starred unpacking"));
Cow::Owned(element_types)
}
}
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
self.targets.shrink_to_fit();
UnpackResult {
diagnostics: self.context.finish(),
diagnostics: self.diagnostics.finish(),
targets: self.targets,
}
}
@@ -259,10 +136,8 @@ impl<'db> UnpackResult<'db> {
pub(crate) fn get(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
self.targets.get(&expr_id).copied()
}
}
impl WithDiagnostics for UnpackResult<'_> {
fn diagnostics(&self) -> &TypeCheckDiagnostics {
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
&self.diagnostics
}
}

View File

@@ -1,9 +1,7 @@
use ruff_db::files::File;
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_text_size::{Ranged, TextRange};
use ruff_python_ast::{self as ast};
use crate::ast_node_ref::AstNodeRef;
use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId};
use crate::semantic_index::expression::Expression;
use crate::semantic_index::symbol::{FileScopeId, ScopeId};
use crate::Db;
@@ -43,7 +41,7 @@ pub(crate) struct Unpack<'db> {
/// The ingredient representing the value expression of the unpacking. For example, in
/// `(a, b) = (1, 2)`, the value expression is `(1, 2)`.
#[no_eq]
pub(crate) value: UnpackValue<'db>,
pub(crate) value: Expression<'db>,
#[no_eq]
count: countme::Count<Unpack<'static>>,
@@ -54,48 +52,4 @@ impl<'db> Unpack<'db> {
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
self.file_scope(db).to_scope_id(db, self.file(db))
}
/// Returns the range of the unpack target expression.
pub(crate) fn range(self, db: &'db dyn Db) -> TextRange {
self.target(db).range()
}
}
/// The expression that is being unpacked.
#[derive(Clone, Copy, Debug)]
pub(crate) enum UnpackValue<'db> {
/// An iterable expression like the one in a `for` loop or a comprehension.
Iterable(Expression<'db>),
/// An expression that is being assigned to a target.
Assign(Expression<'db>),
}
impl<'db> UnpackValue<'db> {
/// Returns `true` if the value is an iterable expression.
pub(crate) const fn is_iterable(self) -> bool {
matches!(self, UnpackValue::Iterable(_))
}
/// Returns the underlying [`Expression`] that is being unpacked.
pub(crate) const fn expression(self) -> Expression<'db> {
match self {
UnpackValue::Assign(expr) | UnpackValue::Iterable(expr) => expr,
}
}
/// Returns the [`ScopedExpressionId`] of the underlying expression.
pub(crate) fn scoped_expression_id(
self,
db: &'db dyn Db,
scope: ScopeId<'db>,
) -> ScopedExpressionId {
self.expression()
.node_ref(db)
.scoped_expression_id(db, scope)
}
/// Returns the expression as an [`AnyNodeRef`].
pub(crate) fn as_any_node_ref(self, db: &'db dyn Db) -> AnyNodeRef<'db> {
self.expression().node_ref(db).node().into()
}
}

View File

@@ -1,338 +0,0 @@
//! # Visibility constraints
//!
//! During semantic index building, we collect visibility constraints for each binding and
//! declaration. These constraints are then used during type-checking to determine the static
//! visibility of a certain definition. This allows us to re-analyze control flow during type
//! checking, potentially "hiding" some branches that we can statically determine to never be
//! taken. Consider the following example first. We added implicit "unbound" definitions at the
//! start of the scope. Note how visibility constraints can apply to bindings outside of the
//! if-statement:
//! ```py
//! x = <unbound> # not a live binding for the use of x below, shadowed by `x = 1`
//! y = <unbound> # visibility constraint: ~test
//!
//! x = 1 # visibility constraint: ~test
//! if test:
//! x = 2 # visibility constraint: test
//!
//! y = 2 # visibility constraint: test
//!
//! use(x)
//! use(y)
//! ```
//! The static truthiness of the `test` condition can either be always-false, ambiguous, or
//! always-true. Similarly, we have the same three options when evaluating a visibility constraint.
//! This outcome determines the visibility of a definition: always-true means that the definition
//! is definitely visible for a given use, always-false means that the definition is definitely
//! not visible, and ambiguous means that we might see this definition or not. In the latter case,
//! we need to consider both options during type inference and boundness analysis. For the example
//! above, these are the possible type inference / boundness results for the uses of `x` and `y`:
//!
//! ```text
//! | `test` truthiness | `~test` truthiness | type of `x` | boundness of `y` |
//! |-------------------|--------------------|-----------------|------------------|
//! | always false | always true | `Literal[1]` | unbound |
//! | ambiguous | ambiguous | `Literal[1, 2]` | possibly unbound |
//! | always true | always false | `Literal[2]` | bound |
//! ```
//!
//! ### Sequential constraints (ternary AND)
//!
//! As we have seen above, visibility constraints can apply outside of a control flow element.
//! So we need to consider the possibility that multiple constraints apply to the same binding.
//! Here, we consider what happens if multiple `if`-statements lead to a sequence of constraints.
//! Consider the following example:
//! ```py
//! x = 0
//!
//! if test1:
//! x = 1
//!
//! if test2:
//! x = 2
//! ```
//! The binding `x = 2` is easy to analyze. Its visibility corresponds to the truthiness of `test2`.
//! For the `x = 1` binding, things are a bit more interesting. It is always visible if `test1` is
//! always-true *and* `test2` is always-false. It is never visible if `test1` is always-false *or*
//! `test2` is always-true. And it is ambiguous otherwise. This corresponds to a ternary *test1 AND
//! ~test2* operation in three-valued Kleene logic [Kleene]:
//!
//! ```text
//! | AND | always-false | ambiguous | always-true |
//! |--------------|--------------|--------------|--------------|
//! | always false | always-false | always-false | always-false |
//! | ambiguous | always-false | ambiguous | ambiguous |
//! | always true | always-false | ambiguous | always-true |
//! ```
//!
//! The `x = 0` binding can be handled similarly, with the difference that both `test1` and `test2`
//! are negated:
//! ```py
//! x = 0 # ~test1 AND ~test2
//!
//! if test1:
//! x = 1 # test1 AND ~test2
//!
//! if test2:
//! x = 2 # test2
//! ```
//!
//! ### Merged constraints (ternary OR)
//!
//! Finally, we consider what happens in "parallel" control flow. Consider the following example
//! where we have omitted the test condition for the outer `if` for clarity:
//! ```py
//! x = 0
//!
//! if <…>:
//! if test1:
//! x = 1
//! else:
//! if test2:
//! x = 2
//!
//! use(x)
//! ```
//! At the usage of `x`, i.e. after control flow has been merged again, the visibility of the `x =
//! 0` binding behaves as follows: the binding is always visible if `test1` is always-false *or*
//! `test2` is always-false; and it is never visible if `test1` is always-true *and* `test2` is
//! always-true. This corresponds to a ternary *OR* operation in Kleene logic:
//!
//! ```text
//! | OR | always-false | ambiguous | always-true |
//! |--------------|--------------|--------------|--------------|
//! | always false | always-false | ambiguous | always-true |
//! | ambiguous | ambiguous | ambiguous | always-true |
//! | always true | always-true | always-true | always-true |
//! ```
//!
//! Using this, we can annotate the visibility constraints for the example above:
//! ```py
//! x = 0 # ~test1 OR ~test2
//!
//! if <…>:
//! if test1:
//! x = 1 # test1
//! else:
//! if test2:
//! x = 2 # test2
//!
//! use(x)
//! ```
//!
//! ### Explicit ambiguity
//!
//! In some cases, we explicitly add a `VisibilityConstraint::Ambiguous` constraint to all bindings
//! in a certain control flow path. We do this when branching on something that we can not (or
//! intentionally do not want to) analyze statically. `for` loops are one example:
//! ```py
//! x = <unbound>
//!
//! for _ in range(2):
//! x = 1
//! ```
//! Here, we report an ambiguous visibility constraint before branching off. If we don't do this,
//! the `x = <unbound>` binding would be considered unconditionally visible in the no-loop case.
//! And since the other branch does not have the live `x = <unbound>` binding, we would incorrectly
//! create a state where the `x = <unbound>` binding is always visible.
//!
//!
//! ### Properties
//!
//! The ternary `AND` and `OR` operations have the property that `~a OR ~b = ~(a AND b)`. This
//! means we could, in principle, get rid of either of these two to simplify the representation.
//!
//! However, we already apply negative constraints `~test1` and `~test2` to the "branches not
//! taken" in the example above. This means that the tree-representation `~test1 OR ~test2` is much
//! cheaper/shallower than basically creating `~(~(~test1) AND ~(~test2))`. Similarly, if we wanted
//! to get rid of `AND`, we would also have to create additional nodes. So for performance reasons,
//! there is a small "duplication" in the code between those two constraint types.
//!
//! [Kleene]: <https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics>
use ruff_index::IndexVec;
use crate::semantic_index::ScopedVisibilityConstraintId;
use crate::semantic_index::{
ast_ids::HasScopedExpressionId,
constraint::{Constraint, ConstraintNode, PatternConstraintKind},
};
use crate::types::{infer_expression_types, Truthiness};
use crate::Db;
/// The maximum depth of recursion when evaluating visibility constraints.
///
/// This is a performance optimization that prevents us from descending deeply in case of
/// pathological cases. The actual limit here has been derived from performance testing on
/// the `black` codebase. When increasing the limit beyond 32, we see a 5x runtime increase
/// resulting from a few files with a lot of boolean expressions and `if`-statements.
const MAX_RECURSION_DEPTH: usize = 24;
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum VisibilityConstraint<'db> {
AlwaysTrue,
Ambiguous,
VisibleIf(Constraint<'db>),
VisibleIfNot(ScopedVisibilityConstraintId),
KleeneAnd(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
KleeneOr(ScopedVisibilityConstraintId, ScopedVisibilityConstraintId),
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct VisibilityConstraints<'db> {
constraints: IndexVec<ScopedVisibilityConstraintId, VisibilityConstraint<'db>>,
}
impl Default for VisibilityConstraints<'_> {
fn default() -> Self {
Self {
constraints: IndexVec::from_iter([VisibilityConstraint::AlwaysTrue]),
}
}
}
impl<'db> VisibilityConstraints<'db> {
pub(crate) fn add(
&mut self,
constraint: VisibilityConstraint<'db>,
) -> ScopedVisibilityConstraintId {
self.constraints.push(constraint)
}
pub(crate) fn add_or_constraint(
&mut self,
a: ScopedVisibilityConstraintId,
b: ScopedVisibilityConstraintId,
) -> ScopedVisibilityConstraintId {
match (&self.constraints[a], &self.constraints[b]) {
(_, VisibilityConstraint::VisibleIfNot(id)) if a == *id => {
ScopedVisibilityConstraintId::ALWAYS_TRUE
}
(VisibilityConstraint::VisibleIfNot(id), _) if *id == b => {
ScopedVisibilityConstraintId::ALWAYS_TRUE
}
_ => self.add(VisibilityConstraint::KleeneOr(a, b)),
}
}
pub(crate) fn add_and_constraint(
&mut self,
a: ScopedVisibilityConstraintId,
b: ScopedVisibilityConstraintId,
) -> ScopedVisibilityConstraintId {
if a == ScopedVisibilityConstraintId::ALWAYS_TRUE {
b
} else if b == ScopedVisibilityConstraintId::ALWAYS_TRUE {
a
} else {
self.add(VisibilityConstraint::KleeneAnd(a, b))
}
}
/// Analyze the statically known visibility for a given visibility constraint.
pub(crate) fn evaluate(&self, db: &'db dyn Db, id: ScopedVisibilityConstraintId) -> Truthiness {
self.evaluate_impl(db, id, MAX_RECURSION_DEPTH)
}
fn evaluate_impl(
&self,
db: &'db dyn Db,
id: ScopedVisibilityConstraintId,
max_depth: usize,
) -> Truthiness {
if max_depth == 0 {
return Truthiness::Ambiguous;
}
let visibility_constraint = &self.constraints[id];
match visibility_constraint {
VisibilityConstraint::AlwaysTrue => Truthiness::AlwaysTrue,
VisibilityConstraint::Ambiguous => Truthiness::Ambiguous,
VisibilityConstraint::VisibleIf(constraint) => Self::analyze_single(db, constraint),
VisibilityConstraint::VisibleIfNot(negated) => {
self.evaluate_impl(db, *negated, max_depth - 1).negate()
}
VisibilityConstraint::KleeneAnd(lhs, rhs) => {
let lhs = self.evaluate_impl(db, *lhs, max_depth - 1);
if lhs == Truthiness::AlwaysFalse {
return Truthiness::AlwaysFalse;
}
let rhs = self.evaluate_impl(db, *rhs, max_depth - 1);
if rhs == Truthiness::AlwaysFalse {
Truthiness::AlwaysFalse
} else if lhs == Truthiness::AlwaysTrue && rhs == Truthiness::AlwaysTrue {
Truthiness::AlwaysTrue
} else {
Truthiness::Ambiguous
}
}
VisibilityConstraint::KleeneOr(lhs_id, rhs_id) => {
let lhs = self.evaluate_impl(db, *lhs_id, max_depth - 1);
if lhs == Truthiness::AlwaysTrue {
return Truthiness::AlwaysTrue;
}
let rhs = self.evaluate_impl(db, *rhs_id, max_depth - 1);
if rhs == Truthiness::AlwaysTrue {
Truthiness::AlwaysTrue
} else if lhs == Truthiness::AlwaysFalse && rhs == Truthiness::AlwaysFalse {
Truthiness::AlwaysFalse
} else {
Truthiness::Ambiguous
}
}
}
}
fn analyze_single(db: &dyn Db, constraint: &Constraint) -> Truthiness {
match constraint.node {
ConstraintNode::Expression(test_expr) => {
let inference = infer_expression_types(db, test_expr);
let scope = test_expr.scope(db);
let ty =
inference.expression_ty(test_expr.node_ref(db).scoped_expression_id(db, scope));
ty.bool(db).negate_if(!constraint.is_positive)
}
ConstraintNode::Pattern(inner) => match inner.kind(db) {
PatternConstraintKind::Value(value, guard) => {
let subject_expression = inner.subject(db);
let inference = infer_expression_types(db, *subject_expression);
let scope = subject_expression.scope(db);
let subject_ty = inference.expression_ty(
subject_expression
.node_ref(db)
.scoped_expression_id(db, scope),
);
let inference = infer_expression_types(db, *value);
let scope = value.scope(db);
let value_ty =
inference.expression_ty(value.node_ref(db).scoped_expression_id(db, scope));
if subject_ty.is_single_valued(db) {
let truthiness =
Truthiness::from(subject_ty.is_equivalent_to(db, value_ty));
if truthiness.is_always_true() && guard.is_some() {
// Fall back to ambiguous, the guard might change the result.
Truthiness::Ambiguous
} else {
truthiness
}
} else {
Truthiness::Ambiguous
}
}
PatternConstraintKind::Singleton(..) | PatternConstraintKind::Unsupported => {
Truthiness::Ambiguous
}
},
}
}
}

View File

@@ -9,7 +9,7 @@
//! ```
use anyhow::Context;
use red_knot_python_semantic::{PythonPlatform, PythonVersion};
use red_knot_python_semantic::PythonVersion;
use serde::Deserialize;
#[derive(Deserialize, Debug, Default, Clone)]
@@ -28,22 +28,13 @@ impl MarkdownTestConfig {
pub(crate) fn python_version(&self) -> Option<PythonVersion> {
self.environment.as_ref().and_then(|env| env.python_version)
}
pub(crate) fn python_platform(&self) -> Option<PythonPlatform> {
self.environment
.as_ref()
.and_then(|env| env.python_platform.clone())
}
}
#[derive(Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub(crate) struct Environment {
/// Target Python version to assume when resolving types.
/// Python version to assume when resolving types.
pub(crate) python_version: Option<PythonVersion>,
/// Target platform to assume when resolving types.
pub(crate) python_platform: Option<PythonPlatform>,
}
#[derive(Deserialize, Debug, Clone)]

View File

@@ -1,7 +1,7 @@
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
use red_knot_python_semantic::lint::RuleSelection;
use red_knot_python_semantic::{
default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonPlatform,
PythonVersion, SearchPathSettings,
default_lint_registry, Db as SemanticDb, Program, ProgramSettings, PythonVersion,
SearchPathSettings,
};
use ruff_db::files::{File, Files};
use ruff_db::system::{DbWithTestSystem, System, SystemPath, SystemPathBuf, TestSystem};
@@ -21,7 +21,7 @@ pub(crate) struct Db {
impl Db {
pub(crate) fn setup(workspace_root: SystemPathBuf) -> Self {
let rule_selection = RuleSelection::from_registry(default_lint_registry());
let rule_selection = RuleSelection::from_registry(&default_lint_registry());
let db = Self {
workspace_root,
@@ -40,7 +40,6 @@ impl Db {
&db,
&ProgramSettings {
python_version: PythonVersion::default(),
python_platform: PythonPlatform::default(),
search_paths: SearchPathSettings::new(db.workspace_root.clone()),
},
)
@@ -97,10 +96,6 @@ impl SemanticDb for Db {
fn rule_selection(&self) -> &RuleSelection {
&self.rule_selection
}
fn lint_registry(&self) -> &LintRegistry {
default_lint_registry()
}
}
#[salsa::db]

View File

@@ -52,9 +52,6 @@ pub fn run(path: &Utf8Path, long_title: &str, short_title: &str, test_name: &str
Program::get(&db)
.set_python_version(&mut db)
.to(test.configuration().python_version().unwrap_or_default());
Program::get(&db)
.set_python_platform(&mut db)
.to(test.configuration().python_platform().unwrap_or_default());
// Remove all files so that the db is in a "fresh" state.
db.memory_file_system().remove_all();
@@ -100,11 +97,7 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
let test_files: Vec<_> = test
.files()
.filter_map(|embedded| {
if embedded.lang == "ignore" {
return None;
}
.map(|embedded| {
assert!(
matches!(embedded.lang, "py" | "pyi"),
"Non-Python files not supported yet."
@@ -113,10 +106,10 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
db.write_file(&full_path, embedded.code).unwrap();
let file = system_path_to_file(db, full_path).unwrap();
Some(TestFile {
TestFile {
file,
backtick_offset: embedded.md_offset,
})
}
})
.collect();

View File

@@ -1 +1 @@
2047b820730fdd65d37e6e8efcf29ca9af7ec3e7
fc11e835108394728930059c8db5b436209bc957

View File

@@ -65,7 +65,7 @@ class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportIn
self,
coro: _TaskCompatibleCoro[_T_co],
*,
loop: AbstractEventLoop | None = None,
loop: AbstractEventLoop = ...,
name: str | None = ...,
context: Context | None = None,
eager_start: bool = False,
@@ -75,13 +75,13 @@ class Task(Future[_T_co]): # type: ignore[type-var] # pyright: ignore[reportIn
self,
coro: _TaskCompatibleCoro[_T_co],
*,
loop: AbstractEventLoop | None = None,
loop: AbstractEventLoop = ...,
name: str | None = ...,
context: Context | None = None,
) -> None: ...
else:
def __init__(
self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop | None = None, name: str | None = ...
self, coro: _TaskCompatibleCoro[_T_co], *, loop: AbstractEventLoop = ..., name: str | None = ...
) -> None: ...
if sys.version_info >= (3, 12):

View File

@@ -22,8 +22,8 @@ class blake2b:
digest_size: int
name: str
if sys.version_info >= (3, 9):
def __new__(
cls,
def __init__(
self,
data: ReadableBuffer = b"",
/,
*,
@@ -39,10 +39,10 @@ class blake2b:
inner_size: int = 0,
last_node: bool = False,
usedforsecurity: bool = True,
) -> Self: ...
) -> None: ...
else:
def __new__(
cls,
def __init__(
self,
data: ReadableBuffer = b"",
/,
*,
@@ -57,7 +57,7 @@ class blake2b:
node_depth: int = 0,
inner_size: int = 0,
last_node: bool = False,
) -> Self: ...
) -> None: ...
def copy(self) -> Self: ...
def digest(self) -> bytes: ...
@@ -74,8 +74,8 @@ class blake2s:
digest_size: int
name: str
if sys.version_info >= (3, 9):
def __new__(
cls,
def __init__(
self,
data: ReadableBuffer = b"",
/,
*,
@@ -91,10 +91,10 @@ class blake2s:
inner_size: int = 0,
last_node: bool = False,
usedforsecurity: bool = True,
) -> Self: ...
) -> None: ...
else:
def __new__(
cls,
def __init__(
self,
data: ReadableBuffer = b"",
/,
*,
@@ -109,7 +109,7 @@ class blake2s:
node_depth: int = 0,
inner_size: int = 0,
last_node: bool = False,
) -> Self: ...
) -> None: ...
def copy(self) -> Self: ...
def digest(self) -> bytes: ...

View File

@@ -1,15 +1,9 @@
import sys
from _typeshed import ReadableBuffer
from typing import final
from typing_extensions import Self
@final
class BZ2Compressor:
if sys.version_info >= (3, 12):
def __new__(cls, compresslevel: int = 9, /) -> Self: ...
else:
def __init__(self, compresslevel: int = 9, /) -> None: ...
def __init__(self, compresslevel: int = 9) -> None: ...
def compress(self, data: ReadableBuffer, /) -> bytes: ...
def flush(self) -> bytes: ...

View File

@@ -8,7 +8,6 @@ from typing import ( # noqa: Y022,Y038
AsyncIterator as AsyncIterator,
Awaitable as Awaitable,
Callable as Callable,
ClassVar,
Collection as Collection,
Container as Container,
Coroutine as Coroutine,
@@ -75,7 +74,6 @@ _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers.
class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented
def __eq__(self, value: object, /) -> bool: ...
def __reversed__(self) -> Iterator[_KT_co]: ...
__hash__: ClassVar[None] # type: ignore[assignment]
if sys.version_info >= (3, 13):
def isdisjoint(self, other: Iterable[_KT_co], /) -> bool: ...
if sys.version_info >= (3, 10):
@@ -93,7 +91,6 @@ class dict_values(ValuesView[_VT_co], Generic[_KT_co, _VT_co]): # undocumented
class dict_items(ItemsView[_KT_co, _VT_co]): # undocumented
def __eq__(self, value: object, /) -> bool: ...
def __reversed__(self) -> Iterator[tuple[_KT_co, _VT_co]]: ...
__hash__: ClassVar[None] # type: ignore[assignment]
if sys.version_info >= (3, 13):
def isdisjoint(self, other: Iterable[tuple[_KT_co, _VT_co]], /) -> bool: ...
if sys.version_info >= (3, 10):

View File

@@ -1,7 +1,7 @@
import sys
from collections.abc import Callable, Iterator, Mapping
from typing import Any, ClassVar, Generic, TypeVar, final, overload
from typing_extensions import ParamSpec, Self
from typing_extensions import ParamSpec
if sys.version_info >= (3, 9):
from types import GenericAlias
@@ -13,9 +13,9 @@ _P = ParamSpec("_P")
@final
class ContextVar(Generic[_T]):
@overload
def __new__(cls, name: str) -> Self: ...
def __init__(self, name: str) -> None: ...
@overload
def __new__(cls, name: str, *, default: _T) -> Self: ...
def __init__(self, name: str, *, default: _T) -> None: ...
def __hash__(self) -> int: ...
@property
def name(self) -> str: ...
@@ -37,7 +37,6 @@ class Token(Generic[_T]):
@property
def old_value(self) -> Any: ... # returns either _T or MISSING, but that's hard to express
MISSING: ClassVar[object]
__hash__: ClassVar[None] # type: ignore[assignment]
if sys.version_info >= (3, 9):
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
@@ -56,7 +55,6 @@ class Context(Mapping[ContextVar[Any], Any]):
def get(self, key: ContextVar[_T], default: _D, /) -> _T | _D: ...
def run(self, callable: Callable[_P, _T], *args: _P.args, **kwargs: _P.kwargs) -> _T: ...
def copy(self) -> Context: ...
__hash__: ClassVar[None] # type: ignore[assignment]
def __getitem__(self, key: ContextVar[_T], /) -> _T: ...
def __iter__(self) -> Iterator[ContextVar[Any]]: ...
def __len__(self) -> int: ...

View File

@@ -32,8 +32,8 @@ class Dialect:
lineterminator: str
quoting: _QuotingType
strict: bool
def __new__(
cls,
def __init__(
self,
dialect: _DialectLike | None = ...,
delimiter: str = ",",
doublequote: bool = True,
@@ -43,7 +43,7 @@ class Dialect:
quoting: _QuotingType = 0,
skipinitialspace: bool = False,
strict: bool = False,
) -> Self: ...
) -> None: ...
if sys.version_info >= (3, 10):
# This class calls itself _csv.reader.
@@ -115,7 +115,7 @@ def reader(
) -> _reader: ...
def register_dialect(
name: str,
dialect: type[Dialect | csv.Dialect] = ...,
dialect: type[Dialect] = ...,
*,
delimiter: str = ",",
quotechar: str | None = '"',

View File

@@ -169,18 +169,18 @@ class CFuncPtr(_PointerLike, _CData, metaclass=_PyCFuncPtrType):
# Abstract attribute that must be defined on subclasses
_flags_: ClassVar[int]
@overload
def __new__(cls) -> Self: ...
def __init__(self) -> None: ...
@overload
def __new__(cls, address: int, /) -> Self: ...
def __init__(self, address: int, /) -> None: ...
@overload
def __new__(cls, callable: Callable[..., Any], /) -> Self: ...
def __init__(self, callable: Callable[..., Any], /) -> None: ...
@overload
def __new__(cls, func_spec: tuple[str | int, CDLL], paramflags: tuple[_PF, ...] | None = ..., /) -> Self: ...
def __init__(self, func_spec: tuple[str | int, CDLL], paramflags: tuple[_PF, ...] | None = ..., /) -> None: ...
if sys.platform == "win32":
@overload
def __new__(
cls, vtbl_index: int, name: str, paramflags: tuple[_PF, ...] | None = ..., iid: _CData | _CDataType | None = ..., /
) -> Self: ...
def __init__(
self, vtbl_index: int, name: str, paramflags: tuple[_PF, ...] | None = ..., iid: _CData | _CDataType | None = ..., /
) -> None: ...
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...

View File

@@ -1,7 +1,7 @@
import sys
from _typeshed import ReadOnlyBuffer, SupportsRead, SupportsWrite
from _typeshed import ReadOnlyBuffer, SupportsRead
from curses import _ncurses_version
from typing import Any, final, overload
from typing import IO, Any, final, overload
from typing_extensions import TypeAlias
# NOTE: This module is ordinarily only available on Unix, but the windows-curses
@@ -517,7 +517,7 @@ class window: # undocumented
def overwrite(
self, destwin: window, sminrow: int, smincol: int, dminrow: int, dmincol: int, dmaxrow: int, dmaxcol: int
) -> None: ...
def putwin(self, file: SupportsWrite[bytes], /) -> None: ...
def putwin(self, file: IO[Any], /) -> None: ...
def redrawln(self, beg: int, num: int, /) -> None: ...
def redrawwin(self) -> None: ...
@overload

View File

@@ -5,7 +5,7 @@ import types
from _typeshed.importlib import LoaderProtocol
from collections.abc import Mapping, Sequence
from types import ModuleType
from typing import Any, ClassVar
from typing import Any
# Signature of `builtins.__import__` should be kept identical to `importlib.__import__`
def __import__(
@@ -43,7 +43,6 @@ class ModuleSpec:
def parent(self) -> str | None: ...
has_location: bool
def __eq__(self, other: object) -> bool: ...
__hash__: ClassVar[None] # type: ignore[assignment]
class BuiltinImporter(importlib.abc.MetaPathFinder, importlib.abc.InspectLoader):
# MetaPathFinder

View File

@@ -112,7 +112,7 @@ class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore
def truncate(self, pos: int | None = None, /) -> int: ...
class BufferedRWPair(BufferedIOBase, _BufferedIOBase):
def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = 8192, /) -> None: ...
def __init__(self, reader: RawIOBase, writer: RawIOBase, buffer_size: int = 8192) -> None: ...
def peek(self, size: int = 0, /) -> bytes: ...
class _TextIOBase(_IOBase):

View File

@@ -1,6 +1,5 @@
from collections.abc import Callable
from typing import Any, final
from typing_extensions import Self
@final
class make_encoder:
@@ -20,8 +19,8 @@ class make_encoder:
def encoder(self) -> Callable[[str], str]: ...
@property
def item_separator(self) -> str: ...
def __new__(
cls,
def __init__(
self,
markers: dict[int, Any] | None,
default: Callable[[Any], Any],
encoder: Callable[[str], str],
@@ -31,7 +30,7 @@ class make_encoder:
sort_keys: bool,
skipkeys: bool,
allow_nan: bool,
) -> Self: ...
) -> None: ...
def __call__(self, obj: object, _current_indent_level: int) -> Any: ...
@final
@@ -43,7 +42,7 @@ class make_scanner:
parse_float: Any
strict: bool
# TODO: 'context' needs the attrs above (ducktype), but not __call__.
def __new__(cls, context: make_scanner) -> Self: ...
def __init__(self, context: make_scanner) -> None: ...
def __call__(self, string: str, index: int) -> tuple[Any, int]: ...
def encode_basestring(s: str, /) -> str: ...

View File

@@ -1,8 +1,7 @@
import sys
from _typeshed import ReadableBuffer
from collections.abc import Mapping, Sequence
from typing import Any, Final, final
from typing_extensions import Self, TypeAlias
from typing_extensions import TypeAlias
_FilterChain: TypeAlias = Sequence[Mapping[str, Any]]
@@ -37,11 +36,7 @@ PRESET_EXTREME: int # v big number
@final
class LZMADecompressor:
if sys.version_info >= (3, 12):
def __new__(cls, format: int | None = ..., memlimit: int | None = ..., filters: _FilterChain | None = ...) -> Self: ...
else:
def __init__(self, format: int | None = ..., memlimit: int | None = ..., filters: _FilterChain | None = ...) -> None: ...
def __init__(self, format: int | None = ..., memlimit: int | None = ..., filters: _FilterChain | None = ...) -> None: ...
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes: ...
@property
def check(self) -> int: ...
@@ -54,15 +49,9 @@ class LZMADecompressor:
@final
class LZMACompressor:
if sys.version_info >= (3, 12):
def __new__(
cls, format: int | None = ..., check: int = ..., preset: int | None = ..., filters: _FilterChain | None = ...
) -> Self: ...
else:
def __init__(
self, format: int | None = ..., check: int = ..., preset: int | None = ..., filters: _FilterChain | None = ...
) -> None: ...
def __init__(
self, format: int | None = ..., check: int = ..., preset: int | None = ..., filters: _FilterChain | None = ...
) -> None: ...
def compress(self, data: ReadableBuffer, /) -> bytes: ...
def flush(self) -> bytes: ...

View File

@@ -66,6 +66,7 @@ class Pickler:
self,
file: SupportsWrite[bytes],
protocol: int | None = None,
*,
fix_imports: bool = True,
buffer_callback: _BufferCallback = None,
) -> None: ...

View File

@@ -12,7 +12,7 @@ from ssl import (
SSLWantWriteError as SSLWantWriteError,
SSLZeroReturnError as SSLZeroReturnError,
)
from typing import Any, ClassVar, Literal, TypedDict, final, overload
from typing import Any, Literal, TypedDict, final, overload
from typing_extensions import NotRequired, Self, TypeAlias
_PasswordType: TypeAlias = Callable[[], str | bytes | bytearray] | str | bytes | bytearray
@@ -119,7 +119,6 @@ class MemoryBIO:
@final
class SSLSession:
__hash__: ClassVar[None] # type: ignore[assignment]
@property
def has_ticket(self) -> bool: ...
@property

View File

@@ -13,11 +13,17 @@ error = RuntimeError
def _count() -> int: ...
@final
class RLock:
class LockType:
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release(self) -> None: ...
__enter__ = acquire
def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ...
def locked(self) -> bool: ...
def acquire_lock(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release_lock(self) -> None: ...
def locked_lock(self) -> bool: ...
def __enter__(self) -> bool: ...
def __exit__(
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> None: ...
if sys.version_info >= (3, 13):
@final
@@ -31,33 +37,7 @@ if sys.version_info >= (3, 13):
def start_joinable_thread(
function: Callable[[], object], handle: _ThreadHandle | None = None, daemon: bool = True
) -> _ThreadHandle: ...
@final
class lock:
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release(self) -> None: ...
def locked(self) -> bool: ...
def acquire_lock(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release_lock(self) -> None: ...
def locked_lock(self) -> bool: ...
def __enter__(self) -> bool: ...
def __exit__(
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> None: ...
LockType = lock
else:
@final
class LockType:
def acquire(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release(self) -> None: ...
def locked(self) -> bool: ...
def acquire_lock(self, blocking: bool = True, timeout: float = -1) -> bool: ...
def release_lock(self) -> None: ...
def locked_lock(self) -> bool: ...
def __enter__(self) -> bool: ...
def __exit__(
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> None: ...
lock = LockType
@overload
def start_new_thread(function: Callable[[Unpack[_Ts]], object], args: tuple[Unpack[_Ts]], /) -> int: ...

View File

@@ -1,6 +1,6 @@
import sys
from collections.abc import Iterable, Iterator, MutableSet
from typing import Any, ClassVar, TypeVar, overload
from typing import Any, TypeVar, overload
from typing_extensions import Self
if sys.version_info >= (3, 9):
@@ -21,7 +21,6 @@ class WeakSet(MutableSet[_T]):
def copy(self) -> Self: ...
def remove(self, item: _T) -> None: ...
def update(self, other: Iterable[_T]) -> None: ...
__hash__: ClassVar[None] # type: ignore[assignment]
def __contains__(self, item: object) -> bool: ...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[_T]: ...

View File

@@ -1,8 +1,8 @@
import sys
from _typeshed import SupportsWrite, sentinel
from _typeshed import sentinel
from collections.abc import Callable, Generator, Iterable, Sequence
from re import Pattern
from typing import IO, Any, ClassVar, Final, Generic, NewType, NoReturn, Protocol, TypeVar, overload
from typing import IO, Any, Final, Generic, NewType, NoReturn, Protocol, TypeVar, overload
from typing_extensions import Self, TypeAlias, deprecated
__all__ = [
@@ -207,8 +207,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
help: str | None = None,
metavar: str | None = None,
) -> _SubParsersAction[_ArgumentParserT]: ...
def print_usage(self, file: SupportsWrite[str] | None = None) -> None: ...
def print_help(self, file: SupportsWrite[str] | None = None) -> None: ...
def print_usage(self, file: IO[str] | None = None) -> None: ...
def print_help(self, file: IO[str] | None = None) -> None: ...
def format_usage(self) -> str: ...
def format_help(self) -> str: ...
@overload
@@ -254,7 +254,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
def _get_value(self, action: Action, arg_string: str) -> Any: ...
def _check_value(self, action: Action, value: Any) -> None: ...
def _get_formatter(self) -> HelpFormatter: ...
def _print_message(self, message: str, file: SupportsWrite[str] | None = None) -> None: ...
def _print_message(self, message: str, file: IO[str] | None = None) -> None: ...
class HelpFormatter:
# undocumented
@@ -456,7 +456,6 @@ class Namespace(_AttributeHolder):
def __setattr__(self, name: str, value: Any, /) -> None: ...
def __contains__(self, key: str) -> bool: ...
def __eq__(self, other: object) -> bool: ...
__hash__: ClassVar[None] # type: ignore[assignment]
class FileType:
# undocumented

View File

@@ -3,7 +3,7 @@ from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite
from collections.abc import Iterable
# pytype crashes if array inherits from collections.abc.MutableSequence instead of typing.MutableSequence
from typing import Any, ClassVar, Literal, MutableSequence, SupportsIndex, TypeVar, overload # noqa: Y022
from typing import Any, Literal, MutableSequence, SupportsIndex, TypeVar, overload # noqa: Y022
from typing_extensions import Self, TypeAlias
if sys.version_info >= (3, 12):
@@ -24,21 +24,19 @@ class array(MutableSequence[_T]):
@property
def itemsize(self) -> int: ...
@overload
def __new__(
cls: type[array[int]], typecode: _IntTypeCode, initializer: bytes | bytearray | Iterable[int] = ..., /
) -> array[int]: ...
def __init__(self: array[int], typecode: _IntTypeCode, initializer: bytes | bytearray | Iterable[int] = ..., /) -> None: ...
@overload
def __new__(
cls: type[array[float]], typecode: _FloatTypeCode, initializer: bytes | bytearray | Iterable[float] = ..., /
) -> array[float]: ...
def __init__(
self: array[float], typecode: _FloatTypeCode, initializer: bytes | bytearray | Iterable[float] = ..., /
) -> None: ...
@overload
def __new__(
cls: type[array[str]], typecode: _UnicodeTypeCode, initializer: bytes | bytearray | Iterable[str] = ..., /
) -> array[str]: ...
def __init__(
self: array[str], typecode: _UnicodeTypeCode, initializer: bytes | bytearray | Iterable[str] = ..., /
) -> None: ...
@overload
def __new__(cls, typecode: str, initializer: Iterable[_T], /) -> Self: ...
def __init__(self, typecode: str, initializer: Iterable[_T], /) -> None: ...
@overload
def __new__(cls, typecode: str, initializer: bytes | bytearray = ..., /) -> Self: ...
def __init__(self, typecode: str, initializer: bytes | bytearray = ..., /) -> None: ...
def append(self, v: _T, /) -> None: ...
def buffer_info(self) -> tuple[int, int]: ...
def byteswap(self) -> None: ...
@@ -64,7 +62,6 @@ class array(MutableSequence[_T]):
def fromstring(self, buffer: str | ReadableBuffer, /) -> None: ...
def tostring(self) -> bytes: ...
__hash__: ClassVar[None] # type: ignore[assignment]
def __len__(self) -> int: ...
@overload
def __getitem__(self, key: SupportsIndex, /) -> _T: ...

View File

@@ -7,7 +7,7 @@ from _ast import (
PyCF_TYPE_COMMENTS as PyCF_TYPE_COMMENTS,
)
from _typeshed import ReadableBuffer, Unused
from collections.abc import Iterable, Iterator
from collections.abc import Iterator
from typing import Any, ClassVar, Generic, Literal, TypedDict, TypeVar as _TypeVar, overload
from typing_extensions import Self, Unpack, deprecated
@@ -1154,7 +1154,6 @@ class Tuple(expr):
if sys.version_info >= (3, 14):
def __replace__(self, *, elts: list[expr] = ..., ctx: expr_context = ..., **kwargs: Unpack[_Attributes]) -> Self: ...
@deprecated("Deprecated since Python 3.9.")
class slice(AST): ... # deprecated and moved to ast.py for >= (3, 9)
if sys.version_info >= (3, 9):
@@ -1186,38 +1185,22 @@ class Slice(_Slice):
**kwargs: Unpack[_SliceAttributes],
) -> Self: ...
@deprecated("Deprecated since Python 3.9. Use ast.Tuple instead.")
class ExtSlice(slice): # deprecated and moved to ast.py if sys.version_info >= (3, 9)
if sys.version_info >= (3, 9):
def __new__(cls, dims: Iterable[slice] = (), **kwargs: Unpack[_SliceAttributes]) -> Tuple: ... # type: ignore[misc]
else:
dims: list[slice]
def __init__(self, dims: list[slice], **kwargs: Unpack[_SliceAttributes]) -> None: ...
dims: list[slice]
def __init__(self, dims: list[slice], **kwargs: Unpack[_SliceAttributes]) -> None: ...
@deprecated("Deprecated since Python 3.9. Use the index value directly instead.")
class Index(slice): # deprecated and moved to ast.py if sys.version_info >= (3, 9)
if sys.version_info >= (3, 9):
def __new__(cls, value: expr, **kwargs: Unpack[_SliceAttributes]) -> expr: ... # type: ignore[misc]
else:
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_SliceAttributes]) -> None: ...
value: expr
def __init__(self, value: expr, **kwargs: Unpack[_SliceAttributes]) -> None: ...
class expr_context(AST): ...
@deprecated("Deprecated since Python 3.9. Unused in Python 3.")
class AugLoad(expr_context): ... # deprecated and moved to ast.py if sys.version_info >= (3, 9)
@deprecated("Deprecated since Python 3.9. Unused in Python 3.")
class AugStore(expr_context): ... # deprecated and moved to ast.py if sys.version_info >= (3, 9)
@deprecated("Deprecated since Python 3.9. Unused in Python 3.")
class Param(expr_context): ... # deprecated and moved to ast.py if sys.version_info >= (3, 9)
@deprecated("Deprecated since Python 3.9. Unused in Python 3.")
class Suite(mod): # deprecated and moved to ast.py if sys.version_info >= (3, 9)
if sys.version_info < (3, 9):
body: list[stmt]
def __init__(self, body: list[stmt]) -> None: ...
body: list[stmt]
def __init__(self, body: list[stmt]) -> None: ...
class Load(expr_context): ...
class Store(expr_context): ...

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@ from socket import AddressFamily, SocketKind, _Address, _RetAddress, socket
from typing import IO, Any, Literal, TypeVar, overload
from typing_extensions import TypeAlias, TypeVarTuple, Unpack
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.version_info >= (3, 9):
__all__ = ("BaseEventLoop", "Server")
else:
@@ -453,7 +452,6 @@ class BaseEventLoop(AbstractEventLoop):
bufsize: Literal[0] = 0,
encoding: None = None,
errors: None = None,
text: Literal[False] | None = None,
**kwargs: Any,
) -> tuple[SubprocessTransport, _ProtocolT]: ...
def add_reader(self, fd: FileDescriptorLike, callback: Callable[[Unpack[_Ts]], Any], *args: Unpack[_Ts]) -> None: ...

View File

@@ -3,7 +3,6 @@ from collections.abc import Awaitable, Callable, Coroutine
from typing import Any, TypeVar, overload
from typing_extensions import ParamSpec, TypeGuard, TypeIs
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.version_info >= (3, 11):
__all__ = ("iscoroutinefunction", "iscoroutine")
else:

View File

@@ -22,7 +22,6 @@ from .tasks import Task
from .transports import BaseTransport, DatagramTransport, ReadTransport, SubprocessTransport, Transport, WriteTransport
from .unix_events import AbstractChildWatcher
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.version_info >= (3, 14):
__all__ = (
"AbstractEventLoopPolicy",

View File

@@ -1,6 +1,5 @@
import sys
# Keep asyncio.__all__ updated with any changes to __all__ here
if sys.version_info >= (3, 11):
__all__ = (
"BrokenBarrierError",

View File

@@ -5,7 +5,6 @@ from typing_extensions import TypeIs
from .events import AbstractEventLoop
# Keep asyncio.__all__ updated with any changes to __all__ here
__all__ = ("Future", "wrap_future", "isfuture")
_T = TypeVar("_T")

Some files were not shown because too many files have changed in this diff Show More