Compare commits
111 Commits
micha/accu
...
0.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed7b98cf9b | ||
|
|
6591775cd9 | ||
|
|
1f82731856 | ||
|
|
874da9c400 | ||
|
|
375cead202 | ||
|
|
9ec690b8f8 | ||
|
|
a48d779c4e | ||
|
|
ba6c7f6897 | ||
|
|
8095ff0e55 | ||
|
|
24cd592a1d | ||
|
|
a40bc6a460 | ||
|
|
577de6c599 | ||
|
|
d8b1afbc6e | ||
|
|
9a3001b571 | ||
|
|
ec2c7cad0e | ||
|
|
924741cb11 | ||
|
|
77e8da7497 | ||
|
|
5e64863895 | ||
|
|
78e4753d74 | ||
|
|
eb55b9b5a0 | ||
|
|
0eb36e4345 | ||
|
|
5fcf0afff4 | ||
|
|
b946cfd1f7 | ||
|
|
95c8f5fd0f | ||
|
|
89aa804b2d | ||
|
|
f789b12705 | ||
|
|
3e36a7ab81 | ||
|
|
5c548dcc04 | ||
|
|
bd30701980 | ||
|
|
2b6d66b793 | ||
|
|
147ea399fd | ||
|
|
907047bf4b | ||
|
|
13a1483f1e | ||
|
|
be69f61b3e | ||
|
|
f1f3bd1cd3 | ||
|
|
3bef23669f | ||
|
|
f82ee8ea59 | ||
|
|
b8a65182dd | ||
|
|
fc15d8a3bd | ||
|
|
b3b5c19105 | ||
|
|
f8aae9b1d6 | ||
|
|
9180635171 | ||
|
|
3ef4b3bf32 | ||
|
|
5a3886c8b5 | ||
|
|
813ec23ecd | ||
|
|
13883414af | ||
|
|
84d4f114ef | ||
|
|
1c586b29e2 | ||
|
|
d76a8518c2 | ||
|
|
5f0ee2670a | ||
|
|
f8ca6c3316 | ||
|
|
ba7b023f26 | ||
|
|
e947d163b2 | ||
|
|
1cf4d2ff69 | ||
|
|
2308522f38 | ||
|
|
438f3d967b | ||
|
|
5bf4759cff | ||
|
|
2e9e96338e | ||
|
|
5fa7ace1f5 | ||
|
|
704868ca83 | ||
|
|
dc71c8a484 | ||
|
|
2499297392 | ||
|
|
7b9189bb2c | ||
|
|
d4cf61d98b | ||
|
|
5d91ba0b10 | ||
|
|
a7e9f0c4b9 | ||
|
|
c7d48e10e6 | ||
|
|
94dee2a36d | ||
|
|
555a5c9319 | ||
|
|
1279c20ee1 | ||
|
|
ce3af27f59 | ||
|
|
71da1d6df5 | ||
|
|
e598240f04 | ||
|
|
c9b84e2a85 | ||
|
|
d3f1c8e536 | ||
|
|
eea6b31980 | ||
|
|
b8dc780bdc | ||
|
|
93fdf7ed36 | ||
|
|
b19f388249 | ||
|
|
de947deee7 | ||
|
|
c0c4ae14ac | ||
|
|
645ce7e5ec | ||
|
|
1430f21283 | ||
|
|
953e862aca | ||
|
|
fbf140a665 | ||
|
|
670f958525 | ||
|
|
fed35a25e8 | ||
|
|
d1ef418bb0 | ||
|
|
272d24bf3e | ||
|
|
2624249219 | ||
|
|
4b08d17088 | ||
|
|
5b6169b02d | ||
|
|
2040e93add | ||
|
|
794eb886e4 | ||
|
|
57ba25caaf | ||
|
|
4f74db5630 | ||
|
|
adc4216afb | ||
|
|
fe8e49de9a | ||
|
|
574eb3f4bd | ||
|
|
311b0bdf9a | ||
|
|
f2546c562c | ||
|
|
59c0dacea0 | ||
|
|
b8188b2262 | ||
|
|
136721e608 | ||
|
|
5b500b838b | ||
|
|
cb003ebe22 | ||
|
|
03a5788aa1 | ||
|
|
626f716de6 | ||
|
|
46c5a13103 | ||
|
|
31681f66c9 | ||
|
|
a56ee9268e |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -16,7 +16,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.11"
|
||||
PYTHON_VERSION: "3.12"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
|
||||
2
.github/workflows/publish-playground.yml
vendored
2
.github/workflows/publish-playground.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@v3.11.0
|
||||
uses: cloudflare/wrangler-action@v3.12.1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -51,11 +51,15 @@ repos:
|
||||
- id: blacken-docs
|
||||
args: ["--pyi", "--line-length", "130"]
|
||||
files: '^crates/.*/resources/mdtest/.*\.md'
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.*?invalid(_.+)*_syntax\.md
|
||||
)$
|
||||
additional_dependencies:
|
||||
- black==24.10.0
|
||||
|
||||
- repo: https://github.com/crate-ci/typos
|
||||
rev: v1.27.0
|
||||
rev: v1.27.3
|
||||
hooks:
|
||||
- id: typos
|
||||
|
||||
@@ -69,7 +73,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.2
|
||||
rev: v0.7.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
||||
86
CHANGELOG.md
86
CHANGELOG.md
@@ -1,5 +1,91 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-datetimez`\] Detect usages of `datetime.max`/`datetime.min` (`DTZ901`) ([#14288](https://github.com/astral-sh/ruff/pull/14288))
|
||||
- \[`flake8-logging`\] Implement `root-logger-calls` (`LOG015`) ([#14302](https://github.com/astral-sh/ruff/pull/14302))
|
||||
- \[`flake8-no-pep420`\] Detect empty implicit namespace packages (`INP001`) ([#14236](https://github.com/astral-sh/ruff/pull/14236))
|
||||
- \[`flake8-pyi`\] Add "replace with `Self`" fix (`PYI019`) ([#14238](https://github.com/astral-sh/ruff/pull/14238))
|
||||
- \[`perflint`\] Implement quick-fix for `manual-list-comprehension` (`PERF401`) ([#13919](https://github.com/astral-sh/ruff/pull/13919))
|
||||
- \[`pylint`\] Implement `shallow-copy-environ` (`W1507`) ([#14241](https://github.com/astral-sh/ruff/pull/14241))
|
||||
- \[`ruff`\] Implement `none-not-at-end-of-union` (`RUF036`) ([#14314](https://github.com/astral-sh/ruff/pull/14314))
|
||||
- \[`ruff`\] Implementation `unsafe-markup-call` from `flake8-markupsafe` plugin (`RUF035`) ([#14224](https://github.com/astral-sh/ruff/pull/14224))
|
||||
- \[`ruff`\] Report problems for `attrs` dataclasses (`RUF008`, `RUF009`) ([#14327](https://github.com/astral-sh/ruff/pull/14327))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-boolean-trap`\] Exclude dunder methods that define operators (`FBT001`) ([#14203](https://github.com/astral-sh/ruff/pull/14203))
|
||||
- \[`flake8-pyi`\] Add "replace with `Self`" fix (`PYI034`) ([#14217](https://github.com/astral-sh/ruff/pull/14217))
|
||||
- \[`flake8-pyi`\] Always autofix `duplicate-union-members` (`PYI016`) ([#14270](https://github.com/astral-sh/ruff/pull/14270))
|
||||
- \[`flake8-pyi`\] Improve autofix for nested and mixed type unions for `unnecessary-type-union` (`PYI055`) ([#14272](https://github.com/astral-sh/ruff/pull/14272))
|
||||
- \[`flake8-pyi`\] Mark fix as unsafe when type annotation contains comments for `duplicate-literal-member` (`PYI062`) ([#14268](https://github.com/astral-sh/ruff/pull/14268))
|
||||
|
||||
### Server
|
||||
|
||||
- Use the current working directory to resolve settings from `ruff.configuration` ([#14352](https://github.com/astral-sh/ruff/pull/14352))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid conflicts between `PLC014` (`useless-import-alias`) and `I002` (`missing-required-import`) by considering `lint.isort.required-imports` for `PLC014` ([#14287](https://github.com/astral-sh/ruff/pull/14287))
|
||||
- \[`flake8-type-checking`\] Skip quoting annotation if it becomes invalid syntax (`TCH001`)
|
||||
- \[`flake8-pyi`\] Avoid using `typing.Self` in stub files pre-Python 3.11 (`PYI034`) ([#14230](https://github.com/astral-sh/ruff/pull/14230))
|
||||
- \[`flake8-pytest-style`\] Flag `pytest.raises` call with keyword argument `expected_exception` (`PT011`) ([#14298](https://github.com/astral-sh/ruff/pull/14298))
|
||||
- \[`flake8-simplify`\] Infer "unknown" truthiness for literal iterables whose items are all unpacks (`SIM222`) ([#14263](https://github.com/astral-sh/ruff/pull/14263))
|
||||
- \[`flake8-type-checking`\] Fix false positives for `typing.Annotated` (`TCH001`) ([#14311](https://github.com/astral-sh/ruff/pull/14311))
|
||||
- \[`pylint`\] Allow `await` at the top-level scope of a notebook (`PLE1142`) ([#14225](https://github.com/astral-sh/ruff/pull/14225))
|
||||
- \[`pylint`\] Fix miscellaneous issues in `await-outside-async` detection (`PLE1142`) ([#14218](https://github.com/astral-sh/ruff/pull/14218))
|
||||
- \[`pyupgrade`\] Avoid applying PEP 646 rewrites in invalid contexts (`UP044`) ([#14234](https://github.com/astral-sh/ruff/pull/14234))
|
||||
- \[`pyupgrade`\] Detect permutations in redundant open modes (`UP015`) ([#14255](https://github.com/astral-sh/ruff/pull/14255))
|
||||
- \[`refurb`\] Avoid triggering `hardcoded-string-charset` for reordered sets (`FURB156`) ([#14233](https://github.com/astral-sh/ruff/pull/14233))
|
||||
- \[`refurb`\] Further special cases added to `verbose-decimal-constructor` (`FURB157`) ([#14216](https://github.com/astral-sh/ruff/pull/14216))
|
||||
- \[`refurb`\] Use `UserString` instead of non-existent `UserStr` (`FURB189`) ([#14209](https://github.com/astral-sh/ruff/pull/14209))
|
||||
- \[`ruff`\] Avoid treating lowercase letters as `# noqa` codes (`RUF100`) ([#14229](https://github.com/astral-sh/ruff/pull/14229))
|
||||
- \[`ruff`\] Do not report when `Optional` has no type arguments (`RUF013`) ([#14181](https://github.com/astral-sh/ruff/pull/14181))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add "Notebook behavior" section for `F704`, `PLE1142` ([#14266](https://github.com/astral-sh/ruff/pull/14266))
|
||||
- Document comment policy around fix safety ([#14300](https://github.com/astral-sh/ruff/pull/14300))
|
||||
|
||||
## 0.7.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- Formatter: Disallow single-line implicit concatenated strings ([#13928](https://github.com/astral-sh/ruff/pull/13928))
|
||||
- \[`flake8-pyi`\] Include all Python file types for `PYI006` and `PYI066` ([#14059](https://github.com/astral-sh/ruff/pull/14059))
|
||||
- \[`flake8-simplify`\] Implement `split-of-static-string` (`SIM905`) ([#14008](https://github.com/astral-sh/ruff/pull/14008))
|
||||
- \[`refurb`\] Implement `subclass-builtin` (`FURB189`) ([#14105](https://github.com/astral-sh/ruff/pull/14105))
|
||||
- \[`ruff`\] Improve diagnostic messages and docs (`RUF031`, `RUF032`, `RUF034`) ([#14068](https://github.com/astral-sh/ruff/pull/14068))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Detect items that hash to same value in duplicate sets (`B033`, `PLC0208`) ([#14064](https://github.com/astral-sh/ruff/pull/14064))
|
||||
- \[`eradicate`\] Better detection of IntelliJ language injection comments (`ERA001`) ([#14094](https://github.com/astral-sh/ruff/pull/14094))
|
||||
- \[`flake8-pyi`\] Add autofix for `docstring-in-stub` (`PYI021`) ([#14150](https://github.com/astral-sh/ruff/pull/14150))
|
||||
- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to alawys provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188))
|
||||
- \[`pyflakes`\] Detect items that hash to same value in duplicate dictionaries (`F601`) ([#14065](https://github.com/astral-sh/ruff/pull/14065))
|
||||
- \[`ruff`\] Fix false positive for decorators (`RUF028`) ([#14061](https://github.com/astral-sh/ruff/pull/14061))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid parsing joint rule codes as distinct codes in `# noqa` ([#12809](https://github.com/astral-sh/ruff/pull/12809))
|
||||
- \[`eradicate`\] ignore `# language=` in commented-out-code rule (ERA001) ([#14069](https://github.com/astral-sh/ruff/pull/14069))
|
||||
- \[`flake8-bugbear`\] - do not run `mutable-argument-default` on stubs (`B006`) ([#14058](https://github.com/astral-sh/ruff/pull/14058))
|
||||
- \[`flake8-builtins`\] Skip lambda expressions in `builtin-argument-shadowing (A002)` ([#14144](https://github.com/astral-sh/ruff/pull/14144))
|
||||
- \[`flake8-comprehension`\] Also remove trailing comma while fixing `C409` and `C419` ([#14097](https://github.com/astral-sh/ruff/pull/14097))
|
||||
- \[`flake8-simplify`\] Allow `open` without context manager in `return` statement (`SIM115`) ([#14066](https://github.com/astral-sh/ruff/pull/14066))
|
||||
- \[`pylint`\] Respect hash-equivalent literals in `iteration-over-set` (`PLC0208`) ([#14063](https://github.com/astral-sh/ruff/pull/14063))
|
||||
- \[`pylint`\] Update known dunder methods for Python 3.13 (`PLW3201`) ([#14146](https://github.com/astral-sh/ruff/pull/14146))
|
||||
- \[`pyupgrade`\] - ignore kwarg unpacking for `UP044` ([#14053](https://github.com/astral-sh/ruff/pull/14053))
|
||||
- \[`refurb`\] Parse more exotic decimal strings in `verbose-decimal-constructor` (`FURB157`) ([#14098](https://github.com/astral-sh/ruff/pull/14098))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add links to missing related options within rule documentations ([#13971](https://github.com/astral-sh/ruff/pull/13971))
|
||||
- Add rule short code to mkdocs tags to allow searching via rule codes ([#14040](https://github.com/astral-sh/ruff/pull/14040))
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Preview features
|
||||
|
||||
366
Cargo.lock
generated
366
Cargo.lock
generated
@@ -2,12 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
@@ -123,9 +117,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.92"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
|
||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||
|
||||
[[package]]
|
||||
name = "append-only-vec"
|
||||
@@ -424,7 +418,7 @@ checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c"
|
||||
dependencies = [
|
||||
"nix 0.28.0",
|
||||
"terminfo",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
"which",
|
||||
"winapi",
|
||||
]
|
||||
@@ -812,6 +806,17 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
@@ -1045,9 +1050,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.0"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
@@ -1108,6 +1113,124 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_locid_transform_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locid_transform_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"utf16_iter",
|
||||
"utf8_iter",
|
||||
"write16",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locid_transform",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locid",
|
||||
"icu_provider_macros",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider_macros"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -1116,12 +1239,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1167,7 +1301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.0",
|
||||
"hashbrown 0.15.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -1260,11 +1394,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-macro"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2069faacbe981460232f880d26bf3c7634e322d49053aa48c27e3ae642f728f1"
|
||||
checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
@@ -1367,9 +1501,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
version = "0.2.162"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -1383,7 +1517,7 @@ dependencies = [
|
||||
"paste",
|
||||
"peg",
|
||||
"regex",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1429,6 +1563,12 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
@@ -1486,9 +1626,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
checksum = "bd0aa4b8ca861b08d68afc8702af3250776898c1508b278e1da9d01e01d4b45c"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
@@ -1815,7 +1955,7 @@ dependencies = [
|
||||
"pep440_rs 0.4.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
"tracing",
|
||||
"unicode-width 0.1.13",
|
||||
"url",
|
||||
@@ -1834,7 +1974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
@@ -2004,7 +2144,7 @@ dependencies = [
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2111,7 +2251,7 @@ dependencies = [
|
||||
"compact_str",
|
||||
"countme",
|
||||
"dir-test",
|
||||
"hashbrown 0.15.0",
|
||||
"hashbrown 0.15.1",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itertools 0.13.0",
|
||||
@@ -2133,7 +2273,7 @@ dependencies = [
|
||||
"static_assertions",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -2253,7 +2393,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
"thiserror 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2317,7 +2457,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2364,7 +2504,7 @@ dependencies = [
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tikv-jemallocator",
|
||||
"toml",
|
||||
"tracing",
|
||||
@@ -2432,7 +2572,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
@@ -2534,7 +2674,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2584,7 +2724,7 @@ dependencies = [
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"toml",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
@@ -2618,7 +2758,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"test-case",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2691,7 +2831,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -2771,6 +2911,7 @@ dependencies = [
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -2823,7 +2964,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror",
|
||||
"thiserror 2.0.3",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -2849,7 +2990,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.7.2"
|
||||
version = "0.7.4"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -2932,9 +3073,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.37"
|
||||
version = "0.38.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
@@ -3234,6 +3375,12 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
@@ -3324,9 +3471,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
|
||||
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
@@ -3403,7 +3550,16 @@ version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
"thiserror-impl 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3417,6 +3573,17 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
@@ -3447,6 +3614,16 @@ dependencies = [
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
@@ -3663,12 +3840,6 @@ dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
@@ -3748,9 +3919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||
checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -3758,6 +3929,18 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf16_iter"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
@@ -4194,6 +4377,18 @@ version = "0.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
@@ -4209,6 +4404,30 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.32"
|
||||
@@ -4229,12 +4448,55 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.6"
|
||||
|
||||
@@ -136,7 +136,7 @@ strum_macros = { version = "0.26.0" }
|
||||
syn = { version = "2.0.55" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
test-case = { version = "3.3.1" }
|
||||
thiserror = { version = "1.0.58" }
|
||||
thiserror = { version = "2.0.0" }
|
||||
tikv-jemallocator = { version = "0.6.0" }
|
||||
toml = { version = "0.8.11" }
|
||||
tracing = { version = "0.1.40" }
|
||||
|
||||
@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.7.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.2/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.7.4/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.4/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.7.2
|
||||
rev: v0.7.4
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -5,8 +5,6 @@ use anyhow::{anyhow, Context};
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use crossbeam::channel as crossbeam_channel;
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
|
||||
use red_knot_python_semantic::SitePackages;
|
||||
use red_knot_server::run_server;
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
@@ -14,7 +12,9 @@ use red_knot_workspace::watch;
|
||||
use red_knot_workspace::watch::WorkspaceWatcher;
|
||||
use red_knot_workspace::workspace::settings::Configuration;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::system::{OsSystem, System, SystemPath, SystemPathBuf};
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use target_version::TargetVersion;
|
||||
|
||||
use crate::logging::{setup_tracing, Verbosity};
|
||||
@@ -318,8 +318,9 @@ impl MainLoop {
|
||||
} => {
|
||||
let has_diagnostics = !result.is_empty();
|
||||
if check_revision == revision {
|
||||
#[allow(clippy::print_stdout)]
|
||||
for diagnostic in result {
|
||||
tracing::error!("{}", diagnostic);
|
||||
println!("{}", diagnostic.display(db));
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(
|
||||
@@ -378,7 +379,10 @@ impl MainLoopCancellationToken {
|
||||
#[derive(Debug)]
|
||||
enum MainLoopMessage {
|
||||
CheckWorkspace,
|
||||
CheckCompleted { result: Vec<String>, revision: u64 },
|
||||
CheckCompleted {
|
||||
result: Vec<Box<dyn Diagnostic>>,
|
||||
revision: u64,
|
||||
},
|
||||
ApplyChanges(Vec<watch::ChangeEvent>),
|
||||
Exit,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ license = { workspace = true }
|
||||
ruff_db = { workspace = true }
|
||||
ruff_index = { workspace = true }
|
||||
ruff_python_ast = { workspace = true, features = ["salsa"] }
|
||||
ruff_python_parser = { workspace = true }
|
||||
ruff_python_stdlib = { workspace = true }
|
||||
ruff_source_file = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# String annotations
|
||||
|
||||
## Simple
|
||||
|
||||
```py
|
||||
def f() -> "int":
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py
|
||||
def f() -> "'int'":
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Type expression
|
||||
|
||||
```py
|
||||
def f1() -> "int | str":
|
||||
return 1
|
||||
|
||||
def f2() -> "tuple[int, str]":
|
||||
return 1
|
||||
|
||||
reveal_type(f1()) # revealed: int | str
|
||||
reveal_type(f2()) # revealed: tuple[int, str]
|
||||
```
|
||||
|
||||
## Partial
|
||||
|
||||
```py
|
||||
def f() -> tuple[int, "str"]:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: tuple[int, str]
|
||||
```
|
||||
|
||||
## Deferred
|
||||
|
||||
```py
|
||||
def f() -> "Foo":
|
||||
return Foo()
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: Foo
|
||||
```
|
||||
|
||||
## Deferred (undefined)
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
def f() -> "Foo":
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Partial deferred
|
||||
|
||||
```py
|
||||
def f() -> int | "Foo":
|
||||
return 1
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: int | Foo
|
||||
```
|
||||
|
||||
## `typing.Literal`
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f1() -> Literal["Foo", "Bar"]:
|
||||
return "Foo"
|
||||
|
||||
def f2() -> 'Literal["Foo", "Bar"]':
|
||||
return "Foo"
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f1()) # revealed: Literal["Foo", "Bar"]
|
||||
reveal_type(f2()) # revealed: Literal["Foo", "Bar"]
|
||||
```
|
||||
|
||||
## Various string kinds
|
||||
|
||||
```py
|
||||
# error: [annotation-raw-string] "Type expressions cannot use raw string literal"
|
||||
def f1() -> r"int":
|
||||
return 1
|
||||
|
||||
# error: [annotation-f-string] "Type expressions cannot use f-strings"
|
||||
def f2() -> f"int":
|
||||
return 1
|
||||
|
||||
# error: [annotation-byte-string] "Type expressions cannot use bytes literal"
|
||||
def f3() -> b"int":
|
||||
return 1
|
||||
|
||||
def f4() -> "int":
|
||||
return 1
|
||||
|
||||
# error: [annotation-implicit-concat] "Type expressions cannot span multiple string literals"
|
||||
def f5() -> "in" "t":
|
||||
return 1
|
||||
|
||||
# error: [annotation-escape-character] "Type expressions cannot contain escape characters"
|
||||
def f6() -> "\N{LATIN SMALL LETTER I}nt":
|
||||
return 1
|
||||
|
||||
# error: [annotation-escape-character] "Type expressions cannot contain escape characters"
|
||||
def f7() -> "\x69nt":
|
||||
return 1
|
||||
|
||||
def f8() -> """int""":
|
||||
return 1
|
||||
|
||||
# error: [annotation-byte-string] "Type expressions cannot use bytes literal"
|
||||
def f9() -> "b'int'":
|
||||
return 1
|
||||
|
||||
reveal_type(f1()) # revealed: Unknown
|
||||
reveal_type(f2()) # revealed: Unknown
|
||||
reveal_type(f3()) # revealed: Unknown
|
||||
reveal_type(f4()) # revealed: int
|
||||
reveal_type(f5()) # revealed: Unknown
|
||||
reveal_type(f6()) # revealed: Unknown
|
||||
reveal_type(f7()) # revealed: Unknown
|
||||
reveal_type(f8()) # revealed: int
|
||||
reveal_type(f9()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Various string kinds in `typing.Literal`
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f() -> Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]:
|
||||
return "normal"
|
||||
|
||||
reveal_type(f()) # revealed: Literal["a", "b", "de", "f", "g", "h"] | Literal[b"c"]
|
||||
```
|
||||
|
||||
## Class variables
|
||||
|
||||
```py
|
||||
MyType = int
|
||||
|
||||
class Aliases:
|
||||
MyType = str
|
||||
|
||||
forward: "MyType"
|
||||
not_forward: MyType
|
||||
|
||||
reveal_type(Aliases.forward) # revealed: str
|
||||
reveal_type(Aliases.not_forward) # revealed: str
|
||||
```
|
||||
|
||||
## Annotated assignment
|
||||
|
||||
```py
|
||||
a: "int" = 1
|
||||
b: "'int'" = 1
|
||||
c: "Foo"
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`"
|
||||
d: "Foo" = 1
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
c = Foo()
|
||||
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Literal[1]
|
||||
reveal_type(c) # revealed: Foo
|
||||
reveal_type(d) # revealed: Foo
|
||||
```
|
||||
|
||||
## Parameter
|
||||
|
||||
TODO: Add tests once parameter inference is supported
|
||||
@@ -1,9 +0,0 @@
|
||||
# String annotations
|
||||
|
||||
```py
|
||||
def f() -> "int":
|
||||
return 1
|
||||
|
||||
# TODO: We do not support string annotations, but we should not panic if we encounter them
|
||||
reveal_type(f()) # revealed: @Todo
|
||||
```
|
||||
@@ -110,3 +110,29 @@ c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = ((
|
||||
# error: [invalid-assignment] "Object of type `Literal["foo"]` is not assignable to `tuple[tuple[int, int], int]`"
|
||||
c: builtins.tuple[builtins.tuple[builtins.int, builtins.int], builtins.int] = "foo"
|
||||
```
|
||||
|
||||
## Future annotations are deferred
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
x: Foo
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
|
||||
## Annotations in stub files are deferred
|
||||
|
||||
```pyi path=main.pyi
|
||||
x: Foo
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
```
|
||||
|
||||
@@ -35,6 +35,7 @@ class C:
|
||||
if flag:
|
||||
x = 2
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[2]
|
||||
reveal_type(C.y) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -9,14 +9,21 @@ def bool_instance() -> bool:
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
class C:
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
else:
|
||||
class C:
|
||||
class C1:
|
||||
x = 2
|
||||
|
||||
reveal_type(C.x) # revealed: Literal[1, 2]
|
||||
class C2:
|
||||
if flag:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(C1.x) # revealed: Literal[1, 2]
|
||||
reveal_type(C2.x) # revealed: Literal[3, 4]
|
||||
```
|
||||
|
||||
## Inherited attributes
|
||||
@@ -53,3 +60,77 @@ reveal_type(A.__mro__)
|
||||
# `E` is earlier in the MRO than `F`, so we should use the type of `E.X`
|
||||
reveal_type(A.X) # revealed: Literal[42]
|
||||
```
|
||||
|
||||
## Unions with possibly unbound paths
|
||||
|
||||
### Definite boundness within a class
|
||||
|
||||
In this example, the `x` attribute is not defined in the `C2` element of the union:
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
class C2: ...
|
||||
|
||||
class C3:
|
||||
x = 3
|
||||
|
||||
flag1 = bool_instance()
|
||||
flag2 = bool_instance()
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 3]
|
||||
```
|
||||
|
||||
### Possibly-unbound within a class
|
||||
|
||||
We raise the same diagnostic if the attribute is possibly-unbound in at least one element of the
|
||||
union:
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
class C2:
|
||||
if bool_instance():
|
||||
x = 2
|
||||
|
||||
class C3:
|
||||
x = 3
|
||||
|
||||
flag1 = bool_instance()
|
||||
flag2 = bool_instance()
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
## Unions with all paths unbound
|
||||
|
||||
If the symbol is unbound in all elements of the union, we detect that:
|
||||
|
||||
```py
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class C1: ...
|
||||
class C2: ...
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
C = C1 if flag else C2
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -18,3 +18,58 @@ class Unit: ...
|
||||
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly unbound `__call__` method
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
class PossiblyNotCallable:
|
||||
if flag():
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly unbound callable
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag():
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
a = PossiblyUnbound()
|
||||
reveal_type(a()) # revealed: int
|
||||
```
|
||||
|
||||
## Non-callable `__call__`
|
||||
|
||||
```py
|
||||
class NonCallable:
|
||||
__call__ = 1
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `NonCallable` is not callable"
|
||||
reveal_type(a()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly non-callable `__call__`
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
class NonCallable:
|
||||
if flag():
|
||||
__call__ = 1
|
||||
else:
|
||||
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
|
||||
```
|
||||
|
||||
@@ -19,6 +19,15 @@ async def get_int_async() -> int:
|
||||
reveal_type(get_int_async()) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Generic
|
||||
|
||||
```py
|
||||
def get_int[T]() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(get_int()) # revealed: int
|
||||
```
|
||||
|
||||
## Decorated
|
||||
|
||||
```py
|
||||
@@ -44,3 +53,16 @@ reveal_type(bar()) # revealed: @Todo
|
||||
nonsense = 123
|
||||
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
|
||||
```
|
||||
|
||||
## Potentially unbound function
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag():
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
# Comparison: Intersections
|
||||
|
||||
## Positive contributions
|
||||
|
||||
If we have an intersection type `A & B` and we get a definitive true/false answer for one of the
|
||||
types, we can infer that the result for the intersection type is also true/false:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
class Child1(Base):
|
||||
def __eq__(self, other) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class Child2(Base): ...
|
||||
|
||||
def get_base() -> Base: ...
|
||||
|
||||
x = get_base()
|
||||
c1 = Child1()
|
||||
|
||||
# Create an intersection type through narrowing:
|
||||
if isinstance(x, Child1):
|
||||
if isinstance(x, Child2):
|
||||
reveal_type(x) # revealed: Child1 & Child2
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[True]
|
||||
|
||||
# Other comparison operators fall back to the base type:
|
||||
reveal_type(x > 1) # revealed: bool
|
||||
reveal_type(x is c1) # revealed: bool
|
||||
```
|
||||
|
||||
## Negative contributions
|
||||
|
||||
Negative contributions to the intersection type only allow simplifications in a few special cases
|
||||
(equality and identity comparisons).
|
||||
|
||||
### Equality comparisons
|
||||
|
||||
#### Literal strings
|
||||
|
||||
```py
|
||||
x = "x" * 1_000_000_000
|
||||
y = "y" * 1_000_000_000
|
||||
reveal_type(x) # revealed: LiteralString
|
||||
|
||||
if x != "abc":
|
||||
reveal_type(x) # revealed: LiteralString & ~Literal["abc"]
|
||||
|
||||
reveal_type(x == "abc") # revealed: Literal[False]
|
||||
reveal_type("abc" == x) # revealed: Literal[False]
|
||||
reveal_type(x == "something else") # revealed: bool
|
||||
reveal_type("something else" == x) # revealed: bool
|
||||
|
||||
reveal_type(x != "abc") # revealed: Literal[True]
|
||||
reveal_type("abc" != x) # revealed: Literal[True]
|
||||
reveal_type(x != "something else") # revealed: bool
|
||||
reveal_type("something else" != x) # revealed: bool
|
||||
|
||||
reveal_type(x == y) # revealed: bool
|
||||
reveal_type(y == x) # revealed: bool
|
||||
reveal_type(x != y) # revealed: bool
|
||||
reveal_type(y != x) # revealed: bool
|
||||
|
||||
reveal_type(x >= "abc") # revealed: bool
|
||||
reveal_type("abc" >= x) # revealed: bool
|
||||
|
||||
reveal_type(x in "abc") # revealed: bool
|
||||
reveal_type("abc" in x) # revealed: bool
|
||||
```
|
||||
|
||||
#### Integers
|
||||
|
||||
```py
|
||||
def get_int() -> int: ...
|
||||
|
||||
x = get_int()
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
|
||||
reveal_type(x != 1) # revealed: Literal[True]
|
||||
reveal_type(x != 2) # revealed: bool
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[False]
|
||||
reveal_type(x == 2) # revealed: bool
|
||||
```
|
||||
|
||||
### Identity comparisons
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
o = object()
|
||||
|
||||
a = A()
|
||||
n = None
|
||||
|
||||
if o is not None:
|
||||
reveal_type(o) # revealed: object & ~None
|
||||
|
||||
reveal_type(o is n) # revealed: Literal[False]
|
||||
reveal_type(o is not n) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Diagnostics
|
||||
|
||||
### Unsupported operators for positive contributions
|
||||
|
||||
Raise an error if any of the positive contributions to the intersection type are unsupported for the
|
||||
given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
### Unsupported operators for negative contributions
|
||||
|
||||
Do *not* raise an error if any of the negative contributions to the intersection type are
|
||||
unsupported for the given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if not isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & ~NonContainer
|
||||
|
||||
# No error here!
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
@@ -41,11 +41,10 @@ except EXCEPTIONS as f:
|
||||
## Dynamic exception types
|
||||
|
||||
```py
|
||||
# TODO: we should not emit these `call-possibly-unbound-method` errors for `tuple.__class_getitem__`
|
||||
def foo(
|
||||
x: type[AttributeError],
|
||||
y: tuple[type[OSError], type[RuntimeError]], # error: [call-possibly-unbound-method]
|
||||
z: tuple[type[BaseException], ...], # error: [call-possibly-unbound-method]
|
||||
y: tuple[type[OSError], type[RuntimeError]],
|
||||
z: tuple[type[BaseException], ...],
|
||||
):
|
||||
try:
|
||||
help()
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Exception Handling
|
||||
|
||||
## Invalid syntax
|
||||
|
||||
```py
|
||||
from typing_extensions import reveal_type
|
||||
|
||||
try:
|
||||
print
|
||||
except as e: # error: [invalid-syntax]
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
```
|
||||
@@ -0,0 +1,28 @@
|
||||
# Attribute access
|
||||
|
||||
## Boundness
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
class A:
|
||||
always_bound = 1
|
||||
|
||||
if flag():
|
||||
union = 1
|
||||
else:
|
||||
union = "abc"
|
||||
|
||||
if flag():
|
||||
possibly_unbound = "abc"
|
||||
|
||||
reveal_type(A.always_bound) # revealed: Literal[1]
|
||||
|
||||
reveal_type(A.union) # revealed: Literal[1] | Literal["abc"]
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound"
|
||||
reveal_type(A.possibly_unbound) # revealed: Literal["abc"]
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`"
|
||||
reveal_type(A.non_existent) # revealed: Unknown
|
||||
```
|
||||
@@ -6,13 +6,9 @@ Basic PEP 695 generics
|
||||
|
||||
```py
|
||||
class MyBox[T]:
|
||||
# TODO: `T` is defined here
|
||||
# error: [unresolved-reference] "Name `T` used when not defined"
|
||||
data: T
|
||||
box_model_number = 695
|
||||
|
||||
# TODO: `T` is defined here
|
||||
# error: [unresolved-reference] "Name `T` used when not defined"
|
||||
def __init__(self, data: T):
|
||||
self.data = data
|
||||
|
||||
@@ -31,17 +27,12 @@ reveal_type(MyBox.box_model_number) # revealed: Literal[695]
|
||||
|
||||
```py
|
||||
class MyBox[T]:
|
||||
# TODO: `T` is defined here
|
||||
# error: [unresolved-reference] "Name `T` used when not defined"
|
||||
data: T
|
||||
|
||||
# TODO: `T` is defined here
|
||||
# error: [unresolved-reference] "Name `T` used when not defined"
|
||||
def __init__(self, data: T):
|
||||
self.data = data
|
||||
|
||||
# TODO not error on the subscripting or the use of type param
|
||||
# error: [unresolved-reference] "Name `T` used when not defined"
|
||||
# TODO not error on the subscripting
|
||||
# error: [non-subscriptable]
|
||||
class MySecureBox[T](MyBox[T]): ...
|
||||
|
||||
@@ -66,3 +57,55 @@ class S[T](Seq[S]): ... # error: [non-subscriptable]
|
||||
|
||||
reveal_type(S) # revealed: Literal[S]
|
||||
```
|
||||
|
||||
## Type params
|
||||
|
||||
A PEP695 type variable defines a value of type `typing.TypeVar` with attributes `__name__`,
|
||||
`__bounds__`, `__constraints__`, and `__default__` (the latter three all lazily evaluated):
|
||||
|
||||
```py
|
||||
def f[T, U: A, V: (A, B), W = A, X: A = A1]():
|
||||
reveal_type(T) # revealed: T
|
||||
reveal_type(T.__name__) # revealed: Literal["T"]
|
||||
reveal_type(T.__bound__) # revealed: None
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(T.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(U) # revealed: U
|
||||
reveal_type(U.__name__) # revealed: Literal["U"]
|
||||
reveal_type(U.__bound__) # revealed: type[A]
|
||||
reveal_type(U.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(U.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(V) # revealed: V
|
||||
reveal_type(V.__name__) # revealed: Literal["V"]
|
||||
reveal_type(V.__bound__) # revealed: None
|
||||
reveal_type(V.__constraints__) # revealed: tuple[type[A], type[B]]
|
||||
reveal_type(V.__default__) # revealed: NoDefault
|
||||
|
||||
reveal_type(W) # revealed: W
|
||||
reveal_type(W.__name__) # revealed: Literal["W"]
|
||||
reveal_type(W.__bound__) # revealed: None
|
||||
reveal_type(W.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(W.__default__) # revealed: type[A]
|
||||
|
||||
reveal_type(X) # revealed: X
|
||||
reveal_type(X.__name__) # revealed: Literal["X"]
|
||||
reveal_type(X.__bound__) # revealed: type[A]
|
||||
reveal_type(X.__constraints__) # revealed: tuple[()]
|
||||
reveal_type(X.__default__) # revealed: type[A1]
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
class A1(A): ...
|
||||
```
|
||||
|
||||
## Minimum two constraints
|
||||
|
||||
A typevar with less than two constraints emits a diagnostic and is treated as unconstrained:
|
||||
|
||||
```py
|
||||
# error: [invalid-typevar-constraints] "TypeVar must have at least two constrained types"
|
||||
def f[T: (int,)]():
|
||||
reveal_type(T.__constraints__) # revealed: tuple[()]
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ reveal_type(y)
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound` is possibly unbound"
|
||||
from maybe_unbound import x, y
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
@@ -50,6 +51,7 @@ reveal_type(y)
|
||||
Importing an annotated name prefers the declared type over the inferred type:
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import] "Member `y` of module `maybe_unbound_annotated` is possibly unbound"
|
||||
from maybe_unbound_annotated import x, y
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
|
||||
@@ -238,7 +238,7 @@ class Test:
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# TODO: we should emit a diagnostic here (it might not be iterable)
|
||||
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
|
||||
for x in Test() if coinflip() else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
196
crates/red_knot_python_semantic/resources/mdtest/metaclass.md
Normal file
196
crates/red_knot_python_semantic/resources/mdtest/metaclass.md
Normal file
@@ -0,0 +1,196 @@
|
||||
## Default
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
|
||||
reveal_type(M.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## `object`
|
||||
|
||||
```py
|
||||
reveal_type(object.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## `type`
|
||||
|
||||
```py
|
||||
reveal_type(type.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class B(metaclass=M): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Invalid metaclass
|
||||
|
||||
A class which doesn't inherit `type` (and/or doesn't implement a custom `__new__` accepting the same
|
||||
arguments as `type.__new__`) isn't a valid metaclass.
|
||||
|
||||
```py
|
||||
class M: ...
|
||||
class A(metaclass=M): ...
|
||||
|
||||
# TODO: emit a diagnostic for the invalid metaclass
|
||||
reveal_type(A.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Linear inheritance
|
||||
|
||||
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that
|
||||
metaclass.
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Conflict (1)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
|
||||
subclass or the class itself.)
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type): ...
|
||||
class A(metaclass=M1): ...
|
||||
class B(metaclass=M2): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Conflict (2)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
|
||||
subclass or the class itself.)
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type): ...
|
||||
class A(metaclass=M1): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` (metaclass of `B`) and `M1` (metaclass of base class `A`) have no subclass relationship"
|
||||
class B(A, metaclass=M2): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Common metaclass
|
||||
|
||||
A class has two explicit bases, both of which have the same metaclass.
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(metaclass=M): ...
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Metaclass metaclass
|
||||
|
||||
A class has an explicit base with a custom metaclass. That metaclass itself has a custom metaclass.
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type, metaclass=M1): ...
|
||||
class M3(M2): ...
|
||||
class A(metaclass=M3): ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(A.__class__) # revealed: Literal[M3]
|
||||
```
|
||||
|
||||
## Diamond inheritance
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class M1(M): ...
|
||||
class M2(M): ...
|
||||
class M12(M1, M2): ...
|
||||
class A(metaclass=M1): ...
|
||||
class B(metaclass=M2): ...
|
||||
class C(metaclass=M12): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
|
||||
class D(A, B, C): ...
|
||||
|
||||
reveal_type(D.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Unknown
|
||||
|
||||
```py
|
||||
from nonexistent_module import UnknownClass # error: [unresolved-import]
|
||||
|
||||
class C(UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[type] & Unknown`
|
||||
reveal_type(C.__class__) # revealed: Literal[type]
|
||||
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A, UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[M] & Unknown`
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Duplicate
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Non-class
|
||||
|
||||
When a class has an explicit `metaclass` that is not a class, but is a callable that accepts
|
||||
`type.__new__` arguments, we should return the meta type of its return type.
|
||||
|
||||
```py
|
||||
def f(*args, **kwargs) -> int: ...
|
||||
|
||||
class A(metaclass=f): ...
|
||||
|
||||
# TODO should be `type[int]`
|
||||
reveal_type(A.__class__) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Cyclic
|
||||
|
||||
Retrieving the metaclass of a cyclically defined class should not cause an infinite loop.
|
||||
|
||||
```py path=a.pyi
|
||||
class A(B): ... # error: [cyclic-class-def]
|
||||
class B(C): ... # error: [cyclic-class-def]
|
||||
class C(A): ... # error: [cyclic-class-def]
|
||||
|
||||
reveal_type(A.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## PEP 695 generic
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A[T: str](metaclass=M): ...
|
||||
|
||||
reveal_type(A.__class__) # revealed: Literal[M]
|
||||
```
|
||||
@@ -0,0 +1,247 @@
|
||||
# Narrowing for `issubclass` checks
|
||||
|
||||
Narrowing for `issubclass(class, classinfo)` expressions.
|
||||
|
||||
## `classinfo` is a single type
|
||||
|
||||
### Basic example
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
if issubclass(t, bytes):
|
||||
reveal_type(t) # revealed: Never
|
||||
|
||||
if issubclass(t, object):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
|
||||
if issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Never
|
||||
```
|
||||
|
||||
### Proper narrowing in `elif` and `else` branches
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str, bytes]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
elif issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[bytes]
|
||||
```
|
||||
|
||||
### Multiple derived classes
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
class Derived1(Base): ...
|
||||
class Derived2(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t1 = Derived1 if flag() else Derived2
|
||||
|
||||
if issubclass(t1, Base):
|
||||
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
|
||||
|
||||
if issubclass(t1, Derived1):
|
||||
reveal_type(t1) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t1) # revealed: Literal[Derived2]
|
||||
|
||||
t2 = Derived1 if flag() else Base
|
||||
|
||||
if issubclass(t2, Base):
|
||||
reveal_type(t2) # revealed: Literal[Derived1, Base]
|
||||
|
||||
t3 = Derived1 if flag() else Unrelated
|
||||
|
||||
if issubclass(t3, Base):
|
||||
reveal_type(t3) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t3) # revealed: Literal[Unrelated]
|
||||
```
|
||||
|
||||
### Narrowing for non-literals
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def get_class() -> type[object]: ...
|
||||
|
||||
t = get_class()
|
||||
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
if issubclass(t, B):
|
||||
reveal_type(t) # revealed: type[A] & type[B]
|
||||
else:
|
||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
||||
```
|
||||
|
||||
### Handling of `None`
|
||||
|
||||
```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: ...
|
||||
|
||||
t = int if flag() else NoneType
|
||||
|
||||
if issubclass(t, NoneType):
|
||||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
|
||||
if issubclass(t, type(None)):
|
||||
# TODO: this should be just `Literal[NoneType]`
|
||||
reveal_type(t) # revealed: Literal[int, NoneType]
|
||||
```
|
||||
|
||||
## `classinfo` contains multiple types
|
||||
|
||||
### (Nested) tuples of types
|
||||
|
||||
```py
|
||||
class Unrelated: ...
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, (int, (Unrelated, (bytes,)))):
|
||||
reveal_type(t) # revealed: Literal[int, bytes]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
## Special cases
|
||||
|
||||
### Emit a diagnostic if the first argument is of wrong type
|
||||
|
||||
#### Too wide
|
||||
|
||||
`type[object]` is a subtype of `object`, but not every `object` can be passed as the first argument
|
||||
to `issubclass`:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
t = get_object()
|
||||
|
||||
# TODO: we should emit a diagnostic here
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
```
|
||||
|
||||
#### Wrong
|
||||
|
||||
`Literal[1]` and `type` are entirely disjoint, so the inferred type of `Literal[1] & type[int]` is
|
||||
eagerly simplified to `Never` as a result of the type narrowing in the `if issubclass(t, int)`
|
||||
branch:
|
||||
|
||||
```py
|
||||
t = 1
|
||||
|
||||
# TODO: we should emit a diagnostic here
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Never
|
||||
```
|
||||
|
||||
### Do not use custom `issubclass` for narrowing
|
||||
|
||||
```py
|
||||
def issubclass(c, ci):
|
||||
return True
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
|
||||
### Do support narrowing if `issubclass` is aliased
|
||||
|
||||
```py
|
||||
issubclass_alias = issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass_alias(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
```
|
||||
|
||||
### Do support narrowing if `issubclass` is imported
|
||||
|
||||
```py
|
||||
from builtins import issubclass as imported_issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if imported_issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
```
|
||||
|
||||
### Do not narrow if second argument is not a proper `classinfo` argument
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, "str"):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, (bytes, "str")):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, Any):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
|
||||
### Do not narrow if there are keyword arguments
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic
|
||||
# (`issubclass` has no `foo` parameter)
|
||||
if issubclass(t, int, foo="bar"):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
# Regression test for #14334
|
||||
|
||||
Regression test for [this issue](https://github.com/astral-sh/ruff/issues/14334).
|
||||
|
||||
```py path=base.py
|
||||
# error: [invalid-base]
|
||||
class Base(2): ...
|
||||
```
|
||||
|
||||
```py path=a.py
|
||||
# No error here
|
||||
from base import Base
|
||||
```
|
||||
@@ -60,8 +60,7 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
|
||||
# These come from `builtins.object`, not `types.ModuleType`:
|
||||
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
|
||||
|
||||
# TODO: understand properties
|
||||
reveal_type(typing.__class__) # revealed: Literal[__class__]
|
||||
reveal_type(typing.__class__) # revealed: Literal[type]
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties and generics;
|
||||
# should be `dict[str, Any]`
|
||||
@@ -75,6 +74,7 @@ we're dealing with:
|
||||
```py path=__getattr__.py
|
||||
import typing
|
||||
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(typing.__getattr__) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# `sys.version_info`
|
||||
|
||||
## The type of `sys.version_info`
|
||||
|
||||
The type of `sys.version_info` is `sys._version_info`, at least according to typeshed's stubs (which
|
||||
we treat as the single source of truth for the standard library). This is quite a complicated type
|
||||
in typeshed, so there are many things we don't fully understand about the type yet; this is the
|
||||
source of several TODOs in this test file. Many of these TODOs should be naturally fixed as we
|
||||
implement more type-system features in the future.
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info) # revealed: _version_info
|
||||
```
|
||||
|
||||
## Literal types from comparisons
|
||||
|
||||
Comparing `sys.version_info` with a 2-element tuple of literal integers always produces a `Literal`
|
||||
type:
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info >= (3, 8)) # revealed: Literal[True]
|
||||
reveal_type((3, 8) <= sys.version_info) # revealed: Literal[True]
|
||||
|
||||
reveal_type(sys.version_info > (3, 8)) # revealed: Literal[True]
|
||||
reveal_type((3, 8) < sys.version_info) # revealed: Literal[True]
|
||||
|
||||
reveal_type(sys.version_info < (3, 8)) # revealed: Literal[False]
|
||||
reveal_type((3, 8) > sys.version_info) # revealed: Literal[False]
|
||||
|
||||
reveal_type(sys.version_info <= (3, 8)) # revealed: Literal[False]
|
||||
reveal_type((3, 8) >= sys.version_info) # revealed: Literal[False]
|
||||
|
||||
reveal_type(sys.version_info == (3, 8)) # revealed: Literal[False]
|
||||
reveal_type((3, 8) == sys.version_info) # revealed: Literal[False]
|
||||
|
||||
reveal_type(sys.version_info != (3, 8)) # revealed: Literal[True]
|
||||
reveal_type((3, 8) != sys.version_info) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Non-literal types from comparisons
|
||||
|
||||
Comparing `sys.version_info` with tuples of other lengths will sometimes produce `Literal` types,
|
||||
sometimes not:
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info >= (3, 8, 1)) # revealed: bool
|
||||
reveal_type(sys.version_info >= (3, 8, 1, "final", 0)) # revealed: bool
|
||||
|
||||
# TODO: While this won't fail at runtime, the user has probably made a mistake
|
||||
# if they're comparing a tuple of length >5 with `sys.version_info`
|
||||
# (`sys.version_info` is a tuple of length 5). It might be worth
|
||||
# emitting a lint diagnostic of some kind warning them about the probable error?
|
||||
reveal_type(sys.version_info >= (3, 8, 1, "final", 0, 5)) # revealed: bool
|
||||
|
||||
# TODO: this should be `Literal[False]`; see #14279
|
||||
reveal_type(sys.version_info == (3, 8, 1, "finallllll", 0)) # revealed: bool
|
||||
```
|
||||
|
||||
## Imports and aliases
|
||||
|
||||
Comparisons with `sys.version_info` still produce literal types, even if the symbol is aliased to
|
||||
another name:
|
||||
|
||||
```py
|
||||
from sys import version_info
|
||||
from sys import version_info as foo
|
||||
|
||||
reveal_type(version_info >= (3, 8)) # revealed: Literal[True]
|
||||
reveal_type(foo >= (3, 8)) # revealed: Literal[True]
|
||||
|
||||
bar = version_info
|
||||
reveal_type(bar >= (3, 8)) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Non-stdlib modules named `sys`
|
||||
|
||||
Only comparisons with the symbol `version_info` from the `sys` module produce literal types:
|
||||
|
||||
```py path=package/__init__.py
|
||||
```
|
||||
|
||||
```py path=package/sys.py
|
||||
version_info: tuple[int, int] = (4, 2)
|
||||
```
|
||||
|
||||
```py path=package/script.py
|
||||
from .sys import version_info
|
||||
|
||||
reveal_type(version_info >= (3, 8)) # revealed: bool
|
||||
```
|
||||
|
||||
## Accessing fields by name
|
||||
|
||||
The fields of `sys.version_info` can be accessed by name:
|
||||
|
||||
```py path=a.py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.major >= 3) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 8) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info.minor >= 9) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
But the `micro`, `releaselevel` and `serial` fields are inferred as `@Todo` until we support
|
||||
properties on instance types:
|
||||
|
||||
```py path=b.py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info.micro) # revealed: @Todo
|
||||
reveal_type(sys.version_info.releaselevel) # revealed: @Todo
|
||||
reveal_type(sys.version_info.serial) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Accessing fields by index/slice
|
||||
|
||||
The fields of `sys.version_info` can be accessed by index or by slice:
|
||||
|
||||
```py
|
||||
import sys
|
||||
|
||||
reveal_type(sys.version_info[0] < 3) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[1] > 8) # revealed: Literal[False]
|
||||
|
||||
# revealed: tuple[Literal[3], Literal[8], int, Literal["alpha", "beta", "candidate", "final"], int]
|
||||
reveal_type(sys.version_info[:5])
|
||||
|
||||
reveal_type(sys.version_info[:2] >= (3, 8)) # revealed: Literal[True]
|
||||
reveal_type(sys.version_info[0:2] >= (3, 9)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[:3] >= (3, 9, 1)) # revealed: Literal[False]
|
||||
reveal_type(sys.version_info[3] == "final") # revealed: bool
|
||||
reveal_type(sys.version_info[3] == "finalllllll") # revealed: Literal[False]
|
||||
```
|
||||
@@ -1,4 +1,6 @@
|
||||
# Unary Operations
|
||||
# Invert, UAdd, USub
|
||||
|
||||
## Instance
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
@@ -10,8 +10,6 @@ reveal_type(not not None) # revealed: Literal[False]
|
||||
## Function
|
||||
|
||||
```py
|
||||
from typing import reveal_type
|
||||
|
||||
def f():
|
||||
return 1
|
||||
|
||||
@@ -115,3 +113,101 @@ reveal_type(not ()) # revealed: Literal[True]
|
||||
reveal_type(not ("hello",)) # revealed: Literal[False]
|
||||
reveal_type(not (1, "hello")) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## Instance
|
||||
|
||||
Not operator is inferred based on
|
||||
<https://docs.python.org/3/library/stdtypes.html#truth-value-testing>. An instance is True or False
|
||||
if the `__bool__` method says so.
|
||||
|
||||
At runtime, the `__len__` method is a fallback for `__bool__`, but we can't make use of that. If we
|
||||
have a class that defines `__len__` but not `__bool__`, it is possible that any subclass could add a
|
||||
`__bool__` method that would invalidate whatever conclusion we drew from `__len__`. So instances of
|
||||
classes without a `__bool__` method, with or without `__len__`, must be inferred as unknown
|
||||
truthiness.
|
||||
|
||||
```py
|
||||
class AlwaysTrue:
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
# revealed: Literal[False]
|
||||
reveal_type(not AlwaysTrue())
|
||||
|
||||
class AlwaysFalse:
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
# revealed: Literal[True]
|
||||
reveal_type(not AlwaysFalse())
|
||||
|
||||
# We don't get into a cycle if someone sets their `__bool__` method to the `bool` builtin:
|
||||
class BoolIsBool:
|
||||
__bool__ = bool
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not BoolIsBool())
|
||||
|
||||
# At runtime, no `__bool__` and no `__len__` means truthy, but we can't rely on that, because
|
||||
# a subclass could add a `__bool__` method.
|
||||
class NoBoolMethod: ...
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not NoBoolMethod())
|
||||
|
||||
# And we can't rely on `__len__` for the same reason: a subclass could add `__bool__`.
|
||||
class LenZero:
|
||||
def __len__(self) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not LenZero())
|
||||
|
||||
class LenNonZero:
|
||||
def __len__(self) -> Literal[1]:
|
||||
return 1
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not LenNonZero())
|
||||
|
||||
class WithBothLenAndBool1:
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
def __len__(self) -> Literal[2]:
|
||||
return 2
|
||||
|
||||
# revealed: Literal[True]
|
||||
reveal_type(not WithBothLenAndBool1())
|
||||
|
||||
class WithBothLenAndBool2:
|
||||
def __bool__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
def __len__(self) -> Literal[0]:
|
||||
return 0
|
||||
|
||||
# revealed: Literal[False]
|
||||
reveal_type(not WithBothLenAndBool2())
|
||||
|
||||
# TODO: raise diagnostic when __bool__ method is not valid: [unsupported-operator] "Method __bool__ for type `MethodBoolInvalid` should return `bool`, returned type `int`"
|
||||
# https://docs.python.org/3/reference/datamodel.html#object.__bool__
|
||||
class MethodBoolInvalid:
|
||||
def __bool__(self) -> int:
|
||||
return 0
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not MethodBoolInvalid())
|
||||
|
||||
# Don't trust a possibly-unbound `__bool__` method:
|
||||
def get_flag() -> bool:
|
||||
return True
|
||||
|
||||
class PossiblyUnboundBool:
|
||||
if get_flag():
|
||||
def __bool__(self) -> Literal[False]:
|
||||
return False
|
||||
|
||||
# revealed: bool
|
||||
reveal_type(not PossiblyUnboundBool())
|
||||
```
|
||||
|
||||
@@ -145,13 +145,8 @@ reveal_type(f) # revealed: Unknown
|
||||
|
||||
### Non-iterable unpacking
|
||||
|
||||
TODO: Remove duplicate diagnostics. This is happening because for a sequence-like assignment target,
|
||||
multiple definitions are created and the inference engine runs on each of them which results in
|
||||
duplicate diagnostics.
|
||||
|
||||
```py
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
a, b = 1
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use ruff_python_ast::{AnyNodeRef, NodeKind};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
|
||||
/// Compact key for a node for use in a hash map.
|
||||
///
|
||||
/// Compares two nodes by their kind and text range.
|
||||
/// Stores the memory address of the node, because using the range and the kind
|
||||
/// of the node is not enough to uniquely identify them in ASTs resulting from
|
||||
/// invalid syntax. For example, parsing the input `for` results in a `StmtFor`
|
||||
/// AST node where both the `target` and the `iter` field are `ExprName` nodes
|
||||
/// with the same (empty) range `3..3`.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub(super) struct NodeKey {
|
||||
kind: NodeKind,
|
||||
range: TextRange,
|
||||
}
|
||||
pub(super) struct NodeKey(usize);
|
||||
|
||||
impl NodeKey {
|
||||
pub(super) fn from_node<'a, N>(node: N) -> Self
|
||||
@@ -16,9 +16,6 @@ impl NodeKey {
|
||||
N: Into<AnyNodeRef<'a>>,
|
||||
{
|
||||
let node = node.into();
|
||||
NodeKey {
|
||||
kind: node.kind(),
|
||||
range: node.range(),
|
||||
}
|
||||
NodeKey(node.as_ptr().as_ptr() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,8 +281,12 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
debug_assert!(popped_assignment.is_some());
|
||||
}
|
||||
|
||||
fn current_assignment(&self) -> Option<&CurrentAssignment<'db>> {
|
||||
self.current_assignments.last()
|
||||
fn current_assignment(&self) -> Option<CurrentAssignment<'db>> {
|
||||
self.current_assignments.last().copied()
|
||||
}
|
||||
|
||||
fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'db>> {
|
||||
self.current_assignments.last_mut()
|
||||
}
|
||||
|
||||
fn add_pattern_constraint(
|
||||
@@ -369,6 +373,11 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
if let Some(default) = default {
|
||||
self.visit_expr(default);
|
||||
}
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,6 +636,7 @@ where
|
||||
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
|
||||
Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
first: true,
|
||||
unpack: Some(Unpack::new(
|
||||
self.db,
|
||||
self.file,
|
||||
@@ -640,9 +650,11 @@ where
|
||||
)),
|
||||
})
|
||||
}
|
||||
ast::Expr::Name(_) => {
|
||||
Some(CurrentAssignment::Assign { node, unpack: None })
|
||||
}
|
||||
ast::Expr::Name(_) => Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
unpack: None,
|
||||
first: false,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -664,9 +676,18 @@ where
|
||||
if let Some(value) = &node.value {
|
||||
self.visit_expr(value);
|
||||
}
|
||||
self.push_assignment(node.into());
|
||||
self.visit_expr(&node.target);
|
||||
self.pop_assignment();
|
||||
|
||||
// See https://docs.python.org/3/library/ast.html#ast.AnnAssign
|
||||
if matches!(
|
||||
*node.target,
|
||||
ast::Expr::Attribute(_) | ast::Expr::Subscript(_) | ast::Expr::Name(_)
|
||||
) {
|
||||
self.push_assignment(node.into());
|
||||
self.visit_expr(&node.target);
|
||||
self.pop_assignment();
|
||||
} else {
|
||||
self.visit_expr(&node.target);
|
||||
}
|
||||
}
|
||||
ast::Stmt::AugAssign(
|
||||
aug_assign @ ast::StmtAugAssign {
|
||||
@@ -678,9 +699,18 @@ where
|
||||
) => {
|
||||
debug_assert_eq!(&self.current_assignments, &[]);
|
||||
self.visit_expr(value);
|
||||
self.push_assignment(aug_assign.into());
|
||||
self.visit_expr(target);
|
||||
self.pop_assignment();
|
||||
|
||||
// See https://docs.python.org/3/library/ast.html#ast.AugAssign
|
||||
if matches!(
|
||||
**target,
|
||||
ast::Expr::Attribute(_) | ast::Expr::Subscript(_) | ast::Expr::Name(_)
|
||||
) {
|
||||
self.push_assignment(aug_assign.into());
|
||||
self.visit_expr(target);
|
||||
self.pop_assignment();
|
||||
} else {
|
||||
self.visit_expr(target);
|
||||
}
|
||||
}
|
||||
ast::Stmt::If(node) => {
|
||||
self.visit_expr(&node.test);
|
||||
@@ -987,14 +1017,19 @@ where
|
||||
}
|
||||
|
||||
if is_definition {
|
||||
match self.current_assignment().copied() {
|
||||
Some(CurrentAssignment::Assign { node, unpack }) => {
|
||||
match self.current_assignment() {
|
||||
Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
first,
|
||||
unpack,
|
||||
}) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
AssignmentDefinitionNodeRef {
|
||||
unpack,
|
||||
value: &node.value,
|
||||
name: name_node,
|
||||
first,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1045,14 +1080,25 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
|
||||
{
|
||||
*first = false;
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
ast::Expr::Named(node) => {
|
||||
// TODO walrus in comprehensions is implicitly nonlocal
|
||||
self.visit_expr(&node.value);
|
||||
self.push_assignment(node.into());
|
||||
self.visit_expr(&node.target);
|
||||
self.pop_assignment();
|
||||
|
||||
// See https://peps.python.org/pep-0572/#differences-between-assignment-expressions-and-assignment-statements
|
||||
if node.target.is_name_expr() {
|
||||
self.push_assignment(node.into());
|
||||
self.visit_expr(&node.target);
|
||||
self.pop_assignment();
|
||||
} else {
|
||||
self.visit_expr(&node.target);
|
||||
}
|
||||
}
|
||||
ast::Expr::Lambda(lambda) => {
|
||||
if let Some(parameters) = &lambda.parameters {
|
||||
@@ -1245,6 +1291,7 @@ where
|
||||
enum CurrentAssignment<'a> {
|
||||
Assign {
|
||||
node: &'a ast::StmtAssign,
|
||||
first: bool,
|
||||
unpack: Option<Unpack<'a>>,
|
||||
},
|
||||
AnnAssign(&'a ast::StmtAnnAssign),
|
||||
|
||||
@@ -92,6 +92,9 @@ pub(crate) enum DefinitionNodeRef<'a> {
|
||||
WithItem(WithItemDefinitionNodeRef<'a>),
|
||||
MatchPattern(MatchPatternDefinitionNodeRef<'a>),
|
||||
ExceptHandler(ExceptHandlerDefinitionNodeRef<'a>),
|
||||
TypeVar(&'a ast::TypeParamTypeVar),
|
||||
ParamSpec(&'a ast::TypeParamParamSpec),
|
||||
TypeVarTuple(&'a ast::TypeParamTypeVarTuple),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtFunctionDef> for DefinitionNodeRef<'a> {
|
||||
@@ -130,6 +133,24 @@ impl<'a> From<&'a ast::Alias> for DefinitionNodeRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamTypeVar> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamTypeVar) -> Self {
|
||||
Self::TypeVar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamParamSpec> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamParamSpec) -> Self {
|
||||
Self::ParamSpec(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::TypeParamTypeVarTuple> for DefinitionNodeRef<'a> {
|
||||
fn from(value: &'a ast::TypeParamTypeVarTuple) -> Self {
|
||||
Self::TypeVarTuple(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<ImportFromDefinitionNodeRef<'a>> for DefinitionNodeRef<'a> {
|
||||
fn from(node_ref: ImportFromDefinitionNodeRef<'a>) -> Self {
|
||||
Self::ImportFrom(node_ref)
|
||||
@@ -183,6 +204,7 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
|
||||
pub(crate) unpack: Option<Unpack<'a>>,
|
||||
pub(crate) value: &'a ast::Expr,
|
||||
pub(crate) name: &'a ast::ExprName,
|
||||
pub(crate) first: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -250,10 +272,12 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
unpack,
|
||||
value,
|
||||
name,
|
||||
first,
|
||||
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
|
||||
target: TargetKind::from(unpack),
|
||||
value: AstNodeRef::new(parsed.clone(), value),
|
||||
name: AstNodeRef::new(parsed, name),
|
||||
first,
|
||||
}),
|
||||
DefinitionNodeRef::AnnotatedAssignment(assign) => {
|
||||
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
|
||||
@@ -314,6 +338,15 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
handler: AstNodeRef::new(parsed, handler),
|
||||
is_star,
|
||||
}),
|
||||
DefinitionNodeRef::TypeVar(node) => {
|
||||
DefinitionKind::TypeVar(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
DefinitionNodeRef::ParamSpec(node) => {
|
||||
DefinitionKind::ParamSpec(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
DefinitionNodeRef::TypeVarTuple(node) => {
|
||||
DefinitionKind::TypeVarTuple(AstNodeRef::new(parsed, node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +363,7 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
value: _,
|
||||
unpack: _,
|
||||
name,
|
||||
first: _,
|
||||
}) => name.into(),
|
||||
Self::AnnotatedAssignment(node) => node.into(),
|
||||
Self::AugmentedAssignment(node) => node.into(),
|
||||
@@ -352,6 +386,9 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
identifier.into()
|
||||
}
|
||||
Self::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler, .. }) => handler.into(),
|
||||
Self::TypeVar(node) => node.into(),
|
||||
Self::ParamSpec(node) => node.into(),
|
||||
Self::TypeVarTuple(node) => node.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,6 +445,9 @@ pub enum DefinitionKind<'db> {
|
||||
WithItem(WithItemDefinitionKind),
|
||||
MatchPattern(MatchPatternDefinitionKind),
|
||||
ExceptHandler(ExceptHandlerDefinitionKind),
|
||||
TypeVar(AstNodeRef<ast::TypeParamTypeVar>),
|
||||
ParamSpec(AstNodeRef<ast::TypeParamParamSpec>),
|
||||
TypeVarTuple(AstNodeRef<ast::TypeParamTypeVarTuple>),
|
||||
}
|
||||
|
||||
impl DefinitionKind<'_> {
|
||||
@@ -417,7 +457,10 @@ impl DefinitionKind<'_> {
|
||||
DefinitionKind::Function(_)
|
||||
| DefinitionKind::Class(_)
|
||||
| DefinitionKind::Import(_)
|
||||
| DefinitionKind::ImportFrom(_) => DefinitionCategory::DeclarationAndBinding,
|
||||
| DefinitionKind::ImportFrom(_)
|
||||
| DefinitionKind::TypeVar(_)
|
||||
| DefinitionKind::ParamSpec(_)
|
||||
| DefinitionKind::TypeVarTuple(_) => DefinitionCategory::DeclarationAndBinding,
|
||||
// a parameter always binds a value, but is only a declaration if annotated
|
||||
DefinitionKind::Parameter(parameter) => {
|
||||
if parameter.annotation.is_some() {
|
||||
@@ -535,10 +578,11 @@ pub struct AssignmentDefinitionKind<'db> {
|
||||
target: TargetKind<'db>,
|
||||
value: AstNodeRef<ast::Expr>,
|
||||
name: AstNodeRef<ast::ExprName>,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl AssignmentDefinitionKind<'_> {
|
||||
pub(crate) fn target(&self) -> TargetKind {
|
||||
impl<'db> AssignmentDefinitionKind<'db> {
|
||||
pub(crate) fn target(&self) -> TargetKind<'db> {
|
||||
self.target
|
||||
}
|
||||
|
||||
@@ -549,6 +593,10 @@ impl AssignmentDefinitionKind<'_> {
|
||||
pub(crate) fn name(&self) -> &ast::ExprName {
|
||||
self.name.node()
|
||||
}
|
||||
|
||||
pub(crate) fn is_first(&self) -> bool {
|
||||
self.first
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -687,3 +735,21 @@ impl From<&ast::ExceptHandlerExceptHandler> for DefinitionNodeKey {
|
||||
Self(NodeKey::from_node(handler))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamTypeVar> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamTypeVar) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamParamSpec> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamParamSpec) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey {
|
||||
fn from(value: &ast::TypeParamTypeVarTuple) -> Self {
|
||||
Self(NodeKey::from_node(value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ impl<'db> UseDefMap<'db> {
|
||||
|
||||
pub(crate) fn use_boundness(&self, use_id: ScopedUseId) -> Boundness {
|
||||
if self.bindings_by_use[use_id].may_be_unbound() {
|
||||
Boundness::MayBeUnbound
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
@@ -292,7 +292,7 @@ impl<'db> UseDefMap<'db> {
|
||||
|
||||
pub(crate) fn public_boundness(&self, symbol: ScopedSymbolId) -> Boundness {
|
||||
if self.public_symbols[symbol].may_be_unbound() {
|
||||
Boundness::MayBeUnbound
|
||||
Boundness::PossiblyUnbound
|
||||
} else {
|
||||
Boundness::Bound
|
||||
}
|
||||
|
||||
@@ -8,32 +8,38 @@ use crate::Db;
|
||||
|
||||
/// Enumeration of various core stdlib modules, for which we have dedicated Salsa queries.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum CoreStdlibModule {
|
||||
pub(crate) enum CoreStdlibModule {
|
||||
Builtins,
|
||||
Types,
|
||||
Typeshed,
|
||||
TypingExtensions,
|
||||
Typing,
|
||||
Sys,
|
||||
}
|
||||
|
||||
impl CoreStdlibModule {
|
||||
fn name(self) -> ModuleName {
|
||||
let module_name = match self {
|
||||
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",
|
||||
};
|
||||
ModuleName::new_static(module_name)
|
||||
.unwrap_or_else(|| panic!("{module_name} should be a valid module name!"))
|
||||
Self::Sys => "sys",
|
||||
}
|
||||
}
|
||||
|
||||
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 core module cannot be resolved for some reason
|
||||
fn core_module_symbol<'db>(
|
||||
pub(crate) fn core_module_symbol<'db>(
|
||||
db: &'db dyn Db,
|
||||
core_module: CoreStdlibModule,
|
||||
symbol: &str,
|
||||
@@ -51,29 +57,14 @@ pub(crate) fn builtins_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db>
|
||||
core_module_symbol(db, CoreStdlibModule::Builtins, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `types` module namespace.
|
||||
///
|
||||
/// Returns `Symbol::Unbound` if the `types` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn types_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Types, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `typing` module namespace.
|
||||
///
|
||||
/// Returns `Symbol::Unbound` if the `typing` module isn't available for some reason.
|
||||
#[inline]
|
||||
#[allow(dead_code)] // currently only used in tests
|
||||
#[cfg(test)]
|
||||
pub(crate) fn typing_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Typing, symbol)
|
||||
}
|
||||
/// Lookup the type of `symbol` in the `_typeshed` module namespace.
|
||||
///
|
||||
/// Returns `Symbol::Unbound` if the `_typeshed` module isn't available for some reason.
|
||||
#[inline]
|
||||
pub(crate) fn typeshed_symbol<'db>(db: &'db dyn Db, symbol: &str) -> Symbol<'db> {
|
||||
core_module_symbol(db, CoreStdlibModule::Typeshed, symbol)
|
||||
}
|
||||
|
||||
/// Lookup the type of `symbol` in the `typing_extensions` module namespace.
|
||||
///
|
||||
|
||||
@@ -3,10 +3,19 @@ use crate::{
|
||||
Db,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum Boundness {
|
||||
Bound,
|
||||
MayBeUnbound,
|
||||
PossiblyUnbound,
|
||||
}
|
||||
|
||||
impl Boundness {
|
||||
pub(crate) fn or(self, other: Boundness) -> Boundness {
|
||||
match (self, other) {
|
||||
(Boundness::Bound, _) | (_, Boundness::Bound) => Boundness::Bound,
|
||||
(Boundness::PossiblyUnbound, Boundness::PossiblyUnbound) => Boundness::PossiblyUnbound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of a symbol lookup, which can either be a (possibly unbound) type
|
||||
@@ -17,14 +26,14 @@ pub(crate) enum Boundness {
|
||||
/// bound = 1
|
||||
///
|
||||
/// if flag:
|
||||
/// maybe_unbound = 2
|
||||
/// possibly_unbound = 2
|
||||
/// ```
|
||||
///
|
||||
/// If we look up symbols in this scope, we would get the following results:
|
||||
/// ```rs
|
||||
/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound),
|
||||
/// maybe_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::MayBeUnbound),
|
||||
/// non_existent: Symbol::Unbound,
|
||||
/// bound: Symbol::Type(Type::IntLiteral(1), Boundness::Bound),
|
||||
/// possibly_unbound: Symbol::Type(Type::IntLiteral(2), Boundness::PossiblyUnbound),
|
||||
/// non_existent: Symbol::Unbound,
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Symbol<'db> {
|
||||
@@ -37,25 +46,18 @@ impl<'db> Symbol<'db> {
|
||||
matches!(self, Symbol::Unbound)
|
||||
}
|
||||
|
||||
pub(crate) fn may_be_unbound(&self) -> bool {
|
||||
pub(crate) fn possibly_unbound(&self) -> bool {
|
||||
match self {
|
||||
Symbol::Type(_, Boundness::MayBeUnbound) | Symbol::Unbound => true,
|
||||
Symbol::Type(_, Boundness::PossiblyUnbound) | Symbol::Unbound => true,
|
||||
Symbol::Type(_, Boundness::Bound) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or(&self, other: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => *ty,
|
||||
Symbol::Unbound => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
|
||||
self.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
|
||||
/// Returns the type of the symbol, ignoring possible unboundness.
|
||||
///
|
||||
/// If the symbol is *definitely* unbound, this function will return `None`. Otherwise,
|
||||
/// if there is at least one control-flow path where the symbol is bound, return the type.
|
||||
pub(crate) fn ignore_possibly_unbound(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => Some(*ty),
|
||||
Symbol::Unbound => None,
|
||||
@@ -65,28 +67,80 @@ impl<'db> Symbol<'db> {
|
||||
#[cfg(test)]
|
||||
#[track_caller]
|
||||
pub(crate) fn expect_type(self) -> Type<'db> {
|
||||
self.as_type()
|
||||
self.ignore_possibly_unbound()
|
||||
.expect("Expected a (possibly unbound) type, not an unbound symbol")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn replace_unbound_with(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
replacement: &Symbol<'db>,
|
||||
) -> Symbol<'db> {
|
||||
match replacement {
|
||||
Symbol::Type(replacement, _) => Symbol::Type(
|
||||
match self {
|
||||
Symbol::Type(ty, Boundness::Bound) => ty,
|
||||
Symbol::Type(ty, Boundness::MayBeUnbound) => {
|
||||
UnionType::from_elements(db, [*replacement, ty])
|
||||
}
|
||||
Symbol::Unbound => *replacement,
|
||||
},
|
||||
Boundness::Bound,
|
||||
),
|
||||
pub(crate) fn or_fall_back_to(self, db: &'db dyn Db, fallback: &Symbol<'db>) -> Symbol<'db> {
|
||||
match fallback {
|
||||
Symbol::Type(fallback_ty, fallback_boundness) => match self {
|
||||
Symbol::Type(_, Boundness::Bound) => self,
|
||||
Symbol::Type(ty, boundness @ Boundness::PossiblyUnbound) => Symbol::Type(
|
||||
UnionType::from_elements(db, [*fallback_ty, ty]),
|
||||
fallback_boundness.or(boundness),
|
||||
),
|
||||
Symbol::Unbound => fallback.clone(),
|
||||
},
|
||||
Symbol::Unbound => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::tests::setup_db;
|
||||
|
||||
#[test]
|
||||
fn test_symbol_or_fall_back_to() {
|
||||
use Boundness::{Bound, PossiblyUnbound};
|
||||
|
||||
let db = setup_db();
|
||||
let ty1 = Type::IntLiteral(1);
|
||||
let ty2 = Type::IntLiteral(2);
|
||||
|
||||
// Start from an unbound symbol
|
||||
assert_eq!(
|
||||
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Unbound),
|
||||
Symbol::Unbound
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, PossiblyUnbound)),
|
||||
Symbol::Type(ty1, PossiblyUnbound)
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Unbound.or_fall_back_to(&db, &Symbol::Type(ty1, Bound)),
|
||||
Symbol::Type(ty1, Bound)
|
||||
);
|
||||
|
||||
// Start from a possibly unbound symbol
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, PossiblyUnbound).or_fall_back_to(&db, &Symbol::Unbound),
|
||||
Symbol::Type(ty1, PossiblyUnbound)
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, PossiblyUnbound)
|
||||
.or_fall_back_to(&db, &Symbol::Type(ty2, PossiblyUnbound)),
|
||||
Symbol::Type(UnionType::from_elements(&db, [ty2, ty1]), PossiblyUnbound)
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, PossiblyUnbound).or_fall_back_to(&db, &Symbol::Type(ty2, Bound)),
|
||||
Symbol::Type(UnionType::from_elements(&db, [ty2, ty1]), Bound)
|
||||
);
|
||||
|
||||
// Start from a definitely bound symbol
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Unbound),
|
||||
Symbol::Type(ty1, Bound)
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Type(ty2, PossiblyUnbound)),
|
||||
Symbol::Type(ty1, Bound)
|
||||
);
|
||||
assert_eq!(
|
||||
Symbol::Type(ty1, Bound).or_fall_back_to(&db, &Symbol::Type(ty2, Bound)),
|
||||
Symbol::Type(ty1, Bound)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,7 +246,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
}
|
||||
} else {
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
if let Type::Instance(InstanceType { class, .. }) = new_positive {
|
||||
if let Type::Instance(InstanceType { class }) = new_positive {
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
if let Some(&Type::BooleanLiteral(value)) = self
|
||||
.negative
|
||||
@@ -317,7 +317,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
// Adding any of these types to the negative side of an intersection
|
||||
// is equivalent to adding it to the positive side. We do this to
|
||||
// simplify the representation.
|
||||
self.positive.insert(ty);
|
||||
self.add_positive(db, ty);
|
||||
}
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
Type::BooleanLiteral(bool)
|
||||
@@ -383,7 +383,7 @@ mod tests {
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::stdlib::typing_symbol;
|
||||
use crate::types::{global_symbol, KnownClass, StringLiteralType, UnionBuilder};
|
||||
use crate::types::{global_symbol, KnownClass, UnionBuilder};
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
@@ -592,6 +592,22 @@ mod tests {
|
||||
assert_eq!(ta_not_i0.display(&db).to_string(), "int & Any | Literal[1]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_negative_any() {
|
||||
let db = setup_db();
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::Any)
|
||||
.build();
|
||||
assert_eq!(ty, Type::Any);
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(Type::Never)
|
||||
.add_negative(Type::Any)
|
||||
.build();
|
||||
assert_eq!(ty, Type::Never);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_distributes_over_union() {
|
||||
let db = setup_db();
|
||||
@@ -759,7 +775,7 @@ mod tests {
|
||||
.build();
|
||||
assert_eq!(ty, s);
|
||||
|
||||
let literal = Type::StringLiteral(StringLiteralType::new(&db, "a"));
|
||||
let literal = Type::string_literal(&db, "a");
|
||||
let expected = IntersectionBuilder::new(&db)
|
||||
.add_positive(s)
|
||||
.add_negative(literal)
|
||||
@@ -862,7 +878,7 @@ mod tests {
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(s)
|
||||
.add_negative(Type::StringLiteral(StringLiteralType::new(&db, "a")))
|
||||
.add_negative(Type::string_literal(&db, "a"))
|
||||
.add_negative(t)
|
||||
.build();
|
||||
assert_eq!(ty, Type::Never);
|
||||
@@ -896,7 +912,7 @@ mod tests {
|
||||
let db = setup_db();
|
||||
|
||||
let t_p = KnownClass::Int.to_instance(&db);
|
||||
let t_n = Type::StringLiteral(StringLiteralType::new(&db, "t_n"));
|
||||
let t_n = Type::string_literal(&db, "t_n");
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(t_p)
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
// TODO: Don't use string keys for rules
|
||||
pub(super) rule: String,
|
||||
@@ -31,6 +32,28 @@ impl TypeCheckDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for TypeCheckDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
TypeCheckDiagnostic::rule(self)
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
TypeCheckDiagnostic::message(self).into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
TypeCheckDiagnostic::file(self)
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(Ranged::range(self))
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for TypeCheckDiagnostic {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
@@ -141,6 +164,23 @@ impl<'db> TypeCheckDiagnosticsBuilder<'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(
|
||||
node,
|
||||
"not-iterable",
|
||||
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,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
//! Display implementations for types.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
|
||||
use ruff_db::display::FormatterJoinExtension;
|
||||
use ruff_python_ast::str::Quote;
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
|
||||
use crate::types::{ClassLiteralType, InstanceType, IntersectionType, KnownClass, Type, UnionType};
|
||||
use crate::types::{
|
||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType,
|
||||
SubclassOfType, Type, UnionType,
|
||||
};
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -64,10 +67,13 @@ impl Display for DisplayRepresentation<'_> {
|
||||
Type::Any => f.write_str("Any"),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::Unknown => f.write_str("Unknown"),
|
||||
Type::Instance(InstanceType { class, .. })
|
||||
if class.is_known(self.db, KnownClass::NoneType) =>
|
||||
{
|
||||
f.write_str("None")
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
let representation = match class.known(self.db) {
|
||||
Some(KnownClass::NoneType) => "None",
|
||||
Some(KnownClass::NoDefaultType) => "NoDefault",
|
||||
_ => class.name(self.db),
|
||||
};
|
||||
f.write_str(representation)
|
||||
}
|
||||
// `[Type::Todo]`'s display should be explicit that is not a valid display of
|
||||
// any other type
|
||||
@@ -77,18 +83,16 @@ impl Display for DisplayRepresentation<'_> {
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::Instance(InstanceType { class, known }) => f.write_str(match known {
|
||||
Some(super::KnownInstance::Literal) => "Literal",
|
||||
_ => class.name(self.db),
|
||||
}),
|
||||
Type::SubclassOf(SubclassOfType { class }) => {
|
||||
write!(f, "type[{}]", class.name(self.db))
|
||||
}
|
||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
||||
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
Type::IntLiteral(n) => n.fmt(f),
|
||||
Type::BooleanLiteral(boolean) => f.write_str(if boolean { "True" } else { "False" }),
|
||||
Type::StringLiteral(string) => {
|
||||
write!(f, r#""{}""#, string.value(self.db).replace('"', r#"\""#))
|
||||
}
|
||||
Type::StringLiteral(string) => string.display(self.db).fmt(f),
|
||||
Type::LiteralString => f.write_str("LiteralString"),
|
||||
Type::BytesLiteral(bytes) => {
|
||||
let escape =
|
||||
@@ -323,15 +327,40 @@ impl<'db> Display for DisplayTypeArray<'_, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> StringLiteralType<'db> {
|
||||
fn display(&'db self, db: &'db dyn Db) -> DisplayStringLiteralType<'db> {
|
||||
DisplayStringLiteralType { db, ty: self }
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayStringLiteralType<'db> {
|
||||
ty: &'db StringLiteralType<'db>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl Display for DisplayStringLiteralType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let value = self.ty.value(self.db);
|
||||
f.write_char('"')?;
|
||||
for ch in value.chars() {
|
||||
match ch {
|
||||
// `escape_debug` will escape even single quotes, which is not necessary for our
|
||||
// use case as we are already using double quotes to wrap the string.
|
||||
'\'' => f.write_char('\'')?,
|
||||
_ => write!(f, "{}", ch.escape_debug())?,
|
||||
}
|
||||
}
|
||||
f.write_char('"')
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::types::{
|
||||
global_symbol, BytesLiteralType, SliceLiteralType, StringLiteralType, Type, UnionType,
|
||||
};
|
||||
use crate::types::{global_symbol, SliceLiteralType, StringLiteralType, Type, UnionType};
|
||||
use crate::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
|
||||
|
||||
fn setup_db() -> TestDb {
|
||||
@@ -377,12 +406,12 @@ mod tests {
|
||||
Type::Unknown,
|
||||
Type::IntLiteral(-1),
|
||||
global_symbol(&db, mod_file, "A").expect_type(),
|
||||
Type::StringLiteral(StringLiteralType::new(&db, "A")),
|
||||
Type::BytesLiteral(BytesLiteralType::new(&db, [0u8].as_slice())),
|
||||
Type::BytesLiteral(BytesLiteralType::new(&db, [7u8].as_slice())),
|
||||
Type::string_literal(&db, "A"),
|
||||
Type::bytes_literal(&db, &[0u8]),
|
||||
Type::bytes_literal(&db, &[7u8]),
|
||||
Type::IntLiteral(0),
|
||||
Type::IntLiteral(1),
|
||||
Type::StringLiteral(StringLiteralType::new(&db, "B")),
|
||||
Type::string_literal(&db, "B"),
|
||||
global_symbol(&db, mod_file, "foo").expect_type(),
|
||||
global_symbol(&db, mod_file, "bar").expect_type(),
|
||||
global_symbol(&db, mod_file, "B").expect_type(),
|
||||
@@ -448,4 +477,28 @@ mod tests {
|
||||
"slice[None, None, Literal[2]]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_literal_display() {
|
||||
let db = setup_db();
|
||||
|
||||
assert_eq!(
|
||||
Type::StringLiteral(StringLiteralType::new(&db, r"\n"))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
r#"Literal["\\n"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
Type::StringLiteral(StringLiteralType::new(&db, "'"))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
r#"Literal["'"]"#
|
||||
);
|
||||
assert_eq!(
|
||||
Type::StringLiteral(StringLiteralType::new(&db, r#"""#))
|
||||
.display(&db)
|
||||
.to_string(),
|
||||
r#"Literal["\""]"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,10 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Deref;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::{Class, ClassLiteralType, KnownClass, Type};
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
@@ -26,22 +25,30 @@ impl<'db> Mro<'db> {
|
||||
/// (We emit a diagnostic warning about the runtime `TypeError` in
|
||||
/// [`super::infer::TypeInferenceBuilder::infer_region_scope`].)
|
||||
pub(super) fn of_class(db: &'db dyn Db, class: Class<'db>) -> Result<Self, MroError<'db>> {
|
||||
Self::of_class_impl(db, class).map_err(|error_kind| {
|
||||
let fallback_mro = Self::from([
|
||||
ClassBase::Class(class),
|
||||
ClassBase::Unknown,
|
||||
ClassBase::object(db),
|
||||
]);
|
||||
MroError {
|
||||
kind: error_kind,
|
||||
fallback_mro,
|
||||
}
|
||||
Self::of_class_impl(db, class).map_err(|error_kind| MroError {
|
||||
kind: error_kind,
|
||||
fallback_mro: Self::from_error(db, class),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn from_error(db: &'db dyn Db, class: Class<'db>) -> Self {
|
||||
Self::from([
|
||||
ClassBase::Class(class),
|
||||
ClassBase::Unknown,
|
||||
ClassBase::object(db),
|
||||
])
|
||||
}
|
||||
|
||||
fn of_class_impl(db: &'db dyn Db, class: Class<'db>) -> Result<Self, MroErrorKind<'db>> {
|
||||
let class_bases = class.explicit_bases(db);
|
||||
|
||||
if !class_bases.is_empty() && class.is_cyclically_defined(db) {
|
||||
// We emit errors for cyclically defined classes elsewhere.
|
||||
// It's important that we don't even try to infer the MRO for a cyclically defined class,
|
||||
// or we'll end up in an infinite loop.
|
||||
return Ok(Mro::from_error(db, class));
|
||||
}
|
||||
|
||||
match class_bases {
|
||||
// `builtins.object` is the special case:
|
||||
// the only class in Python that has an MRO with length <2
|
||||
@@ -77,11 +84,6 @@ impl<'db> Mro<'db> {
|
||||
Err(MroErrorKind::InvalidBases(bases_info))
|
||||
},
|
||||
|single_base| {
|
||||
if let ClassBase::Class(class_base) = single_base {
|
||||
if class_is_cyclically_defined(db, class_base) {
|
||||
return Err(MroErrorKind::CyclicClassDefinition);
|
||||
}
|
||||
}
|
||||
let mro = std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db))
|
||||
.collect();
|
||||
@@ -96,10 +98,6 @@ impl<'db> Mro<'db> {
|
||||
// what MRO Python will give this class at runtime
|
||||
// (if an MRO is indeed resolvable at all!)
|
||||
multiple_bases => {
|
||||
if class_is_cyclically_defined(db, class) {
|
||||
return Err(MroErrorKind::CyclicClassDefinition);
|
||||
}
|
||||
|
||||
let mut valid_bases = vec![];
|
||||
let mut invalid_bases = vec![];
|
||||
|
||||
@@ -282,13 +280,6 @@ pub(super) enum MroErrorKind<'db> {
|
||||
/// Each index is the index of a node representing an invalid base.
|
||||
InvalidBases(Box<[(usize, Type<'db>)]>),
|
||||
|
||||
/// The class inherits from itself!
|
||||
///
|
||||
/// This is very unlikely to happen in working real-world code,
|
||||
/// but it's important to explicitly account for it.
|
||||
/// If we don't, there's a possibility of an infinite loop and a panic.
|
||||
CyclicClassDefinition,
|
||||
|
||||
/// The class has one or more duplicate bases.
|
||||
///
|
||||
/// This variant records the indices and [`Class`]es
|
||||
@@ -349,7 +340,7 @@ impl<'db> ClassBase<'db> {
|
||||
/// Return a `ClassBase` representing the class `builtins.object`
|
||||
fn object(db: &'db dyn Db) -> Self {
|
||||
KnownClass::Object
|
||||
.to_class(db)
|
||||
.to_class_literal(db)
|
||||
.into_class_literal()
|
||||
.map_or(Self::Unknown, |ClassLiteralType { class }| {
|
||||
Self::Class(class)
|
||||
@@ -377,7 +368,12 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::ModuleLiteral(_) => None,
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SubclassOf(_) => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::Literal => None,
|
||||
KnownInstanceType::TypeVar(_) => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -410,7 +406,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
ClassBase::Any => Type::Any,
|
||||
ClassBase::Todo => Type::Todo,
|
||||
ClassBase::Unknown => Type::Unknown,
|
||||
ClassBase::Class(class) => Type::ClassLiteral(ClassLiteralType { class }),
|
||||
ClassBase::Class(class) => Type::class_literal(class),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,46 +452,3 @@ fn c3_merge(mut sequences: Vec<VecDeque<ClassBase>>) -> Option<Mro> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if this class appears to be a cyclic definition,
|
||||
/// i.e., it inherits either directly or indirectly from itself.
|
||||
///
|
||||
/// A class definition like this will fail at runtime,
|
||||
/// but we must be resilient to it or we could panic.
|
||||
fn class_is_cyclically_defined(db: &dyn Db, class: Class) -> bool {
|
||||
fn is_cyclically_defined_recursive<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: Class<'db>,
|
||||
classes_to_watch: &mut IndexSet<Class<'db>>,
|
||||
) -> bool {
|
||||
if !classes_to_watch.insert(class) {
|
||||
return true;
|
||||
}
|
||||
for explicit_base_class in class
|
||||
.explicit_bases(db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::into_class_literal)
|
||||
.map(|ClassLiteralType { class }| class)
|
||||
{
|
||||
// Each base must be considered in isolation.
|
||||
// This is due to the fact that if a class uses multiple inheritance,
|
||||
// there could easily be a situation where two bases have the same class in their MROs;
|
||||
// that isn't enough to constitute the class being cyclically defined.
|
||||
let classes_to_watch_len = classes_to_watch.len();
|
||||
if is_cyclically_defined_recursive(db, explicit_base_class, classes_to_watch) {
|
||||
return true;
|
||||
}
|
||||
classes_to_watch.truncate(classes_to_watch_len);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
class
|
||||
.explicit_bases(db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::into_class_literal)
|
||||
.map(|ClassLiteralType { class }| class)
|
||||
.any(|base_class| is_cyclically_defined_recursive(db, base_class, &mut IndexSet::default()))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::{
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
|
||||
Truthiness, Type, UnionBuilder,
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass,
|
||||
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
use itertools::Itertools;
|
||||
@@ -78,24 +78,27 @@ fn all_negative_narrowing_constraints_for_expression<'db>(
|
||||
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), false).finish()
|
||||
}
|
||||
|
||||
/// Generate a constraint from the *type* of the second argument of an `isinstance` call.
|
||||
/// Generate a constraint from the type of a `classinfo` argument to `isinstance` or `issubclass`.
|
||||
///
|
||||
/// Example: for `isinstance(…, str)`, we would infer `Type::ClassLiteral(str)` from the
|
||||
/// second argument, but we need to generate a `Type::Instance(str)` constraint that can
|
||||
/// be used to narrow down the type of the first argument.
|
||||
fn generate_isinstance_constraint<'db>(
|
||||
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||
fn generate_classinfo_constraint<'db, F>(
|
||||
db: &'db dyn Db,
|
||||
classinfo: &Type<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
to_constraint: F,
|
||||
) -> Option<Type<'db>>
|
||||
where
|
||||
F: Fn(ClassLiteralType<'db>) -> Type<'db> + Copy,
|
||||
{
|
||||
match classinfo {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Some(Type::anonymous_instance(*class)),
|
||||
Type::Tuple(tuple) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for element in tuple.elements(db) {
|
||||
builder = builder.add(generate_isinstance_constraint(db, element)?);
|
||||
builder = builder.add(generate_classinfo_constraint(db, element, to_constraint)?);
|
||||
}
|
||||
Some(builder.build())
|
||||
}
|
||||
Type::ClassLiteral(class_literal_type) => Some(to_constraint(*class_literal_type)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -330,34 +333,49 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
let scope = self.scope();
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
|
||||
if let Some(func_type) = inference
|
||||
// TODO: add support for PEP 604 union types on the right hand side of `isinstance`
|
||||
// and `issubclass`, for example `isinstance(x, str | (int | float))`.
|
||||
match inference
|
||||
.expression_ty(expr_call.func.scoped_ast_id(self.db, scope))
|
||||
.into_function_literal()
|
||||
.and_then(|f| f.known(self.db))
|
||||
.and_then(KnownFunction::constraint_function)
|
||||
{
|
||||
if func_type.is_known(self.db, KnownFunction::IsInstance)
|
||||
&& expr_call.arguments.keywords.is_empty()
|
||||
{
|
||||
if let [ast::Expr::Name(ast::ExprName { id, .. }), rhs] = &*expr_call.arguments.args
|
||||
Some(function) if expr_call.arguments.keywords.is_empty() => {
|
||||
if let [ast::Expr::Name(ast::ExprName { id, .. }), class_info] =
|
||||
&*expr_call.arguments.args
|
||||
{
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
|
||||
let rhs_type = inference.expression_ty(rhs.scoped_ast_id(self.db, scope));
|
||||
let class_info_ty =
|
||||
inference.expression_ty(class_info.scoped_ast_id(self.db, scope));
|
||||
|
||||
// TODO: add support for PEP 604 union types on the right hand side:
|
||||
// isinstance(x, str | (int | float))
|
||||
if let Some(mut constraint) = generate_isinstance_constraint(self.db, &rhs_type)
|
||||
{
|
||||
if !is_positive {
|
||||
constraint = constraint.negate(self.db);
|
||||
let to_constraint = match function {
|
||||
KnownConstraintFunction::IsInstance => {
|
||||
|class_literal: ClassLiteralType<'db>| {
|
||||
Type::instance(class_literal.class)
|
||||
}
|
||||
}
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
constraints.insert(symbol, constraint);
|
||||
return Some(constraints);
|
||||
}
|
||||
KnownConstraintFunction::IsSubclass => {
|
||||
|class_literal: ClassLiteralType<'db>| {
|
||||
Type::subclass_of(class_literal.class)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
generate_classinfo_constraint(self.db, &class_info_ty, to_constraint).map(
|
||||
|constraint| {
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
constraints.insert(symbol, constraint.negate_if(self.db, !is_positive));
|
||||
constraints
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn evaluate_match_pattern_singleton(
|
||||
|
||||
480
crates/red_knot_python_semantic/src/types/signatures.rs
Normal file
480
crates/red_knot_python_semantic/src/types/signatures.rs
Normal file
@@ -0,0 +1,480 @@
|
||||
#![allow(dead_code)]
|
||||
use super::{definition_expression_ty, Type};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::Db;
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
||||
/// A typed callable signature.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct Signature<'db> {
|
||||
parameters: Parameters<'db>,
|
||||
|
||||
/// Annotated return type (Unknown if no annotation.)
|
||||
pub(crate) return_ty: Type<'db>,
|
||||
}
|
||||
|
||||
impl<'db> Signature<'db> {
|
||||
/// Return a todo signature: (*args: Todo, **kwargs: Todo) -> Todo
|
||||
pub(crate) fn todo() -> Self {
|
||||
Self {
|
||||
parameters: Parameters::todo(),
|
||||
return_ty: Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a typed signature from a function definition.
|
||||
pub(super) fn from_function(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
function_node: &'db ast::StmtFunctionDef,
|
||||
) -> Self {
|
||||
let return_ty = function_node
|
||||
.returns
|
||||
.as_ref()
|
||||
.map(|returns| {
|
||||
if function_node.is_async {
|
||||
// TODO: generic `types.CoroutineType`!
|
||||
Type::Todo
|
||||
} else {
|
||||
definition_expression_ty(db, definition, returns.as_ref())
|
||||
}
|
||||
})
|
||||
.unwrap_or(Type::Unknown);
|
||||
|
||||
Self {
|
||||
parameters: Parameters::from_parameters(
|
||||
db,
|
||||
definition,
|
||||
function_node.parameters.as_ref(),
|
||||
),
|
||||
return_ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameters portion of a typed signature.
|
||||
///
|
||||
/// The ordering of parameters is always as given in this struct: first positional-only parameters,
|
||||
/// then positional-or-keyword, then optionally the variadic parameter, then keyword-only
|
||||
/// parameters, and last, optionally the variadic keywords parameter.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub(super) struct Parameters<'db> {
|
||||
/// Parameters which may only be filled by positional arguments.
|
||||
positional_only: Box<[ParameterWithDefault<'db>]>,
|
||||
|
||||
/// Parameters which may be filled by positional or keyword arguments.
|
||||
positional_or_keyword: Box<[ParameterWithDefault<'db>]>,
|
||||
|
||||
/// The `*args` variadic parameter, if any.
|
||||
variadic: Option<Parameter<'db>>,
|
||||
|
||||
/// Parameters which may only be filled by keyword arguments.
|
||||
keyword_only: Box<[ParameterWithDefault<'db>]>,
|
||||
|
||||
/// The `**kwargs` variadic keywords parameter, if any.
|
||||
keywords: Option<Parameter<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> Parameters<'db> {
|
||||
/// Return todo parameters: (*args: Todo, **kwargs: Todo)
|
||||
fn todo() -> Self {
|
||||
Self {
|
||||
variadic: Some(Parameter {
|
||||
name: Some(Name::new_static("args")),
|
||||
annotated_ty: Type::Todo,
|
||||
}),
|
||||
keywords: Some(Parameter {
|
||||
name: Some(Name::new_static("kwargs")),
|
||||
annotated_ty: Type::Todo,
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_parameters(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
parameters: &'db ast::Parameters,
|
||||
) -> Self {
|
||||
let ast::Parameters {
|
||||
posonlyargs,
|
||||
args,
|
||||
vararg,
|
||||
kwonlyargs,
|
||||
kwarg,
|
||||
range: _,
|
||||
} = parameters;
|
||||
let positional_only = posonlyargs
|
||||
.iter()
|
||||
.map(|arg| ParameterWithDefault::from_node(db, definition, arg))
|
||||
.collect();
|
||||
let positional_or_keyword = args
|
||||
.iter()
|
||||
.map(|arg| ParameterWithDefault::from_node(db, definition, arg))
|
||||
.collect();
|
||||
let variadic = vararg
|
||||
.as_ref()
|
||||
.map(|arg| Parameter::from_node(db, definition, arg));
|
||||
let keyword_only = kwonlyargs
|
||||
.iter()
|
||||
.map(|arg| ParameterWithDefault::from_node(db, definition, arg))
|
||||
.collect();
|
||||
let keywords = kwarg
|
||||
.as_ref()
|
||||
.map(|arg| Parameter::from_node(db, definition, arg));
|
||||
Self {
|
||||
positional_only,
|
||||
positional_or_keyword,
|
||||
variadic,
|
||||
keyword_only,
|
||||
keywords,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single parameter of a typed signature, with optional default value.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct ParameterWithDefault<'db> {
|
||||
parameter: Parameter<'db>,
|
||||
|
||||
/// Type of the default value, if any.
|
||||
default_ty: Option<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> ParameterWithDefault<'db> {
|
||||
fn from_node(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
parameter_with_default: &'db ast::ParameterWithDefault,
|
||||
) -> Self {
|
||||
Self {
|
||||
default_ty: parameter_with_default
|
||||
.default
|
||||
.as_deref()
|
||||
.map(|default| definition_expression_ty(db, definition, default)),
|
||||
parameter: Parameter::from_node(db, definition, ¶meter_with_default.parameter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single parameter of a typed signature.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct Parameter<'db> {
|
||||
/// Parameter name.
|
||||
///
|
||||
/// It is possible for signatures to be defined in ways that leave positional-only parameters
|
||||
/// nameless (e.g. via `Callable` annotations).
|
||||
name: Option<Name>,
|
||||
|
||||
/// Annotated type of the parameter (Unknown if no annotation.)
|
||||
annotated_ty: Type<'db>,
|
||||
}
|
||||
|
||||
impl<'db> Parameter<'db> {
|
||||
fn from_node(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
parameter: &'db ast::Parameter,
|
||||
) -> Self {
|
||||
Parameter {
|
||||
name: Some(parameter.name.id.clone()),
|
||||
annotated_ty: parameter
|
||||
.annotation
|
||||
.as_deref()
|
||||
.map(|annotation| definition_expression_ty(db, definition, annotation))
|
||||
.unwrap_or(Type::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::program::{Program, SearchPathSettings};
|
||||
use crate::python_version::PythonVersion;
|
||||
use crate::types::{global_symbol, FunctionType};
|
||||
use crate::ProgramSettings;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
|
||||
pub(crate) fn setup_db() -> TestDb {
|
||||
let db = TestDb::new();
|
||||
|
||||
let src_root = SystemPathBuf::from("/src");
|
||||
db.memory_file_system()
|
||||
.create_directory_all(&src_root)
|
||||
.unwrap();
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
&ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings::new(src_root),
|
||||
},
|
||||
)
|
||||
.expect("Valid search path settings");
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn get_function_f<'db>(db: &'db TestDb, file: &'static str) -> FunctionType<'db> {
|
||||
let module = ruff_db::files::system_path_to_file(db, file).unwrap();
|
||||
global_symbol(db, module, "f")
|
||||
.expect_type()
|
||||
.expect_function_literal()
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_param_with_default<'db>(
|
||||
db: &'db TestDb,
|
||||
param_with_default: &ParameterWithDefault<'db>,
|
||||
expected_name: &'static str,
|
||||
expected_annotation_ty_display: &'static str,
|
||||
expected_default_ty_display: Option<&'static str>,
|
||||
) {
|
||||
assert_eq!(
|
||||
param_with_default
|
||||
.default_ty
|
||||
.map(|ty| ty.display(db).to_string()),
|
||||
expected_default_ty_display.map(ToString::to_string)
|
||||
);
|
||||
assert_param(
|
||||
db,
|
||||
¶m_with_default.parameter,
|
||||
expected_name,
|
||||
expected_annotation_ty_display,
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_param<'db>(
|
||||
db: &'db TestDb,
|
||||
param: &Parameter<'db>,
|
||||
expected_name: &'static str,
|
||||
expected_annotation_ty_display: &'static str,
|
||||
) {
|
||||
assert_eq!(param.name.as_ref().unwrap(), expected_name);
|
||||
assert_eq!(
|
||||
param.annotated_ty.display(db).to_string(),
|
||||
expected_annotation_ty_display
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented("/src/a.py", "def f(): ...").unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
assert_eq!(sig.return_ty.display(&db).to_string(), "Unknown");
|
||||
let params = sig.parameters;
|
||||
assert!(params.positional_only.is_empty());
|
||||
assert!(params.positional_or_keyword.is_empty());
|
||||
assert!(params.variadic.is_none());
|
||||
assert!(params.keyword_only.is_empty());
|
||||
assert!(params.keywords.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn full() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
def f(a, b: int, c = 1, d: int = 2, /,
|
||||
e = 3, f: Literal[4] = 4, *args: object,
|
||||
g = 5, h: Literal[6] = 6, **kwargs: str) -> bytes: ...
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
assert_eq!(sig.return_ty.display(&db).to_string(), "bytes");
|
||||
let params = sig.parameters;
|
||||
let [a, b, c, d] = ¶ms.positional_only[..] else {
|
||||
panic!("expected four positional-only parameters");
|
||||
};
|
||||
let [e, f] = ¶ms.positional_or_keyword[..] else {
|
||||
panic!("expected two positional-or-keyword parameters");
|
||||
};
|
||||
let Some(args) = params.variadic else {
|
||||
panic!("expected a variadic parameter");
|
||||
};
|
||||
let [g, h] = ¶ms.keyword_only[..] else {
|
||||
panic!("expected two keyword-only parameters");
|
||||
};
|
||||
let Some(kwargs) = params.keywords else {
|
||||
panic!("expected a kwargs parameter");
|
||||
};
|
||||
|
||||
assert_param_with_default(&db, a, "a", "Unknown", None);
|
||||
assert_param_with_default(&db, b, "b", "int", None);
|
||||
assert_param_with_default(&db, c, "c", "Unknown", Some("Literal[1]"));
|
||||
assert_param_with_default(&db, d, "d", "int", Some("Literal[2]"));
|
||||
assert_param_with_default(&db, e, "e", "Unknown", Some("Literal[3]"));
|
||||
assert_param_with_default(&db, f, "f", "Literal[4]", Some("Literal[4]"));
|
||||
assert_param_with_default(&db, g, "g", "Unknown", Some("Literal[5]"));
|
||||
assert_param_with_default(&db, h, "h", "Literal[6]", Some("Literal[6]"));
|
||||
assert_param(&db, &args, "args", "object");
|
||||
assert_param(&db, &kwargs, "kwargs", "str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_deferred() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
alias = A
|
||||
|
||||
def f(a: alias): ...
|
||||
|
||||
alias = B
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
let [a] = &sig.parameters.positional_or_keyword[..] else {
|
||||
panic!("expected one positional-or-keyword parameter");
|
||||
};
|
||||
// Parameter resolution not deferred; we should see A not B
|
||||
assert_param_with_default(&db, a, "a", "A", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deferred_in_stub() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.pyi",
|
||||
"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
alias = A
|
||||
|
||||
def f(a: alias): ...
|
||||
|
||||
alias = B
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.pyi");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
let [a] = &sig.parameters.positional_or_keyword[..] else {
|
||||
panic!("expected one positional-or-keyword parameter");
|
||||
};
|
||||
// Parameter resolution deferred; we should see B
|
||||
assert_param_with_default(&db, a, "a", "B", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_not_deferred() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
alias = A
|
||||
|
||||
def f[T](a: alias, b: T) -> T: ...
|
||||
|
||||
alias = B
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
let [a, b] = &sig.parameters.positional_or_keyword[..] else {
|
||||
panic!("expected two positional-or-keyword parameters");
|
||||
};
|
||||
// TODO resolution should not be deferred; we should see A not B
|
||||
assert_param_with_default(&db, a, "a", "B", None);
|
||||
assert_param_with_default(&db, b, "b", "T", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_deferred_in_stub() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.pyi",
|
||||
"
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
alias = A
|
||||
|
||||
def f[T](a: alias, b: T) -> T: ...
|
||||
|
||||
alias = B
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.pyi");
|
||||
|
||||
let sig = func.internal_signature(&db);
|
||||
|
||||
let [a, b] = &sig.parameters.positional_or_keyword[..] else {
|
||||
panic!("expected two positional-or-keyword parameters");
|
||||
};
|
||||
// Parameter resolution deferred; we should see B
|
||||
assert_param_with_default(&db, a, "a", "B", None);
|
||||
assert_param_with_default(&db, b, "b", "T", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_signature_no_decorator() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
def f(a: int) -> int: ...
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let expected_sig = func.internal_signature(&db);
|
||||
|
||||
// With no decorators, internal and external signature are the same
|
||||
assert_eq!(func.signature(&db), &expected_sig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_signature_decorated() {
|
||||
let mut db = setup_db();
|
||||
db.write_dedented(
|
||||
"/src/a.py",
|
||||
"
|
||||
def deco(func): ...
|
||||
|
||||
@deco
|
||||
def f(a: int) -> int: ...
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
let func = get_function_f(&db, "/src/a.py");
|
||||
|
||||
let expected_sig = Signature::todo();
|
||||
|
||||
// With no decorators, internal and external signature are the same
|
||||
assert_eq!(func.signature(&db), &expected_sig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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::types::diagnostic::{TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::Db;
|
||||
|
||||
type AnnotationParseResult = Result<Parsed<ModExpression>, TypeCheckDiagnostics>;
|
||||
|
||||
/// Parses the given expression as a string annotation.
|
||||
pub(crate) fn parse_string_annotation(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
string_expr: &ast::ExprStringLiteral,
|
||||
) -> 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() {
|
||||
diagnostics.add(
|
||||
string_literal.into(),
|
||||
"annotation-raw-string",
|
||||
format_args!("Type expressions cannot use raw string literal"),
|
||||
);
|
||||
// Compare the raw contents (without quotes) of the expression with the parsed contents
|
||||
// contained in the string literal.
|
||||
} else if raw_contents(node_text)
|
||||
.is_some_and(|raw_contents| raw_contents == string_literal.as_str())
|
||||
{
|
||||
let range_excluding_quotes = string_literal
|
||||
.range()
|
||||
.add_start(string_literal.flags.opener_len())
|
||||
.sub_end(string_literal.flags.closer_len());
|
||||
|
||||
// TODO: Support multiline strings like:
|
||||
// ```py
|
||||
// x: """
|
||||
// int
|
||||
// | float
|
||||
// """ = 1
|
||||
// ```
|
||||
match parse_expression_range(source.as_str(), range_excluding_quotes) {
|
||||
Ok(parsed) => return Ok(parsed),
|
||||
Err(parse_error) => diagnostics.add(
|
||||
string_literal.into(),
|
||||
"forward-annotation-syntax-error",
|
||||
format_args!("Syntax error in forward annotation: {}", parse_error.error),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
// The raw contents of the string doesn't match the parsed content. This could be the
|
||||
// case for annotations that contain escape sequences.
|
||||
diagnostics.add(
|
||||
string_expr.into(),
|
||||
"annotation-escape-character",
|
||||
format_args!("Type expressions cannot contain escape characters"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// String is implicitly concatenated.
|
||||
diagnostics.add(
|
||||
string_expr.into(),
|
||||
"annotation-implicit-concat",
|
||||
format_args!("Type expressions cannot span multiple string literals"),
|
||||
);
|
||||
}
|
||||
|
||||
Err(diagnostics.finish())
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::ast_ids::{HasScopedAstId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{TupleType, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::types::{Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::Db;
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
@@ -93,11 +93,10 @@ impl<'db> Unpacker<'db> {
|
||||
// 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(TupleType::new(
|
||||
let value_ty = Type::tuple(
|
||||
self.db,
|
||||
vec![Type::LiteralString; string_literal_ty.len(self.db)]
|
||||
.into_boxed_slice(),
|
||||
));
|
||||
&vec![Type::LiteralString; string_literal_ty.len(self.db)],
|
||||
);
|
||||
self.unpack(target, value_ty, scope);
|
||||
}
|
||||
_ => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use dir_test::{dir_test, Fixture};
|
||||
use red_knot_test::run;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
|
||||
use dir_test::{dir_test, Fixture};
|
||||
|
||||
/// See `crates/red_knot_test/README.md` for documentation on these tests.
|
||||
#[dir_test(
|
||||
dir: "$CARGO_MANIFEST_DIR/resources/mdtest",
|
||||
@@ -9,16 +10,16 @@ use std::path::Path;
|
||||
)]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn mdtest(fixture: Fixture<&str>) {
|
||||
let path = fixture.path();
|
||||
let fixture_path = Path::new(fixture.path());
|
||||
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let workspace_root = crate_dir.parent().and_then(Path::parent).unwrap();
|
||||
|
||||
let crate_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("resources/mdtest")
|
||||
.canonicalize()
|
||||
let long_title = fixture_path
|
||||
.strip_prefix(workspace_root)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
let short_title = fixture_path.file_name().and_then(OsStr::to_str).unwrap();
|
||||
|
||||
let relative_path = path
|
||||
.strip_prefix(crate_dir.to_str().unwrap())
|
||||
.unwrap_or(path);
|
||||
|
||||
run(Path::new(path), relative_path);
|
||||
red_knot_test::run(fixture_path, long_title, short_title);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ mod text_document;
|
||||
|
||||
use lsp_types::{PositionEncodingKind, Url};
|
||||
pub use notebook::NotebookDocument;
|
||||
pub(crate) use range::RangeExt;
|
||||
pub(crate) use range::{RangeExt, ToRangeExt};
|
||||
pub(crate) use text_document::DocumentVersion;
|
||||
pub use text_document::TextDocument;
|
||||
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
use super::notebook;
|
||||
use super::PositionEncoding;
|
||||
use ruff_source_file::LineIndex;
|
||||
use lsp_types as types;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_source_file::{LineIndex, SourceLocation};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
pub(crate) struct NotebookRange {
|
||||
pub(crate) cell: notebook::CellId,
|
||||
pub(crate) range: types::Range,
|
||||
}
|
||||
|
||||
pub(crate) trait RangeExt {
|
||||
fn to_text_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding)
|
||||
-> TextRange;
|
||||
}
|
||||
|
||||
pub(crate) trait ToRangeExt {
|
||||
fn to_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> types::Range;
|
||||
fn to_notebook_range(
|
||||
&self,
|
||||
text: &str,
|
||||
source_index: &LineIndex,
|
||||
notebook_index: &NotebookIndex,
|
||||
encoding: PositionEncoding,
|
||||
) -> NotebookRange;
|
||||
}
|
||||
|
||||
fn u32_index_to_usize(index: u32) -> usize {
|
||||
usize::try_from(index).expect("u32 fits in usize")
|
||||
}
|
||||
@@ -75,6 +94,61 @@ impl RangeExt for lsp_types::Range {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToRangeExt for TextRange {
|
||||
fn to_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> types::Range {
|
||||
types::Range {
|
||||
start: source_location_to_position(&offset_to_source_location(
|
||||
self.start(),
|
||||
text,
|
||||
index,
|
||||
encoding,
|
||||
)),
|
||||
end: source_location_to_position(&offset_to_source_location(
|
||||
self.end(),
|
||||
text,
|
||||
index,
|
||||
encoding,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_notebook_range(
|
||||
&self,
|
||||
text: &str,
|
||||
source_index: &LineIndex,
|
||||
notebook_index: &NotebookIndex,
|
||||
encoding: PositionEncoding,
|
||||
) -> NotebookRange {
|
||||
let start = offset_to_source_location(self.start(), text, source_index, encoding);
|
||||
let mut end = offset_to_source_location(self.end(), text, source_index, encoding);
|
||||
let starting_cell = notebook_index.cell(start.row);
|
||||
|
||||
// weird edge case here - if the end of the range is where the newline after the cell got added (making it 'out of bounds')
|
||||
// we need to move it one character back (which should place it at the end of the last line).
|
||||
// we test this by checking if the ending offset is in a different (or nonexistent) cell compared to the cell of the starting offset.
|
||||
if notebook_index.cell(end.row) != starting_cell {
|
||||
end.row = end.row.saturating_sub(1);
|
||||
end.column = offset_to_source_location(
|
||||
self.end().checked_sub(1.into()).unwrap_or_default(),
|
||||
text,
|
||||
source_index,
|
||||
encoding,
|
||||
)
|
||||
.column;
|
||||
}
|
||||
|
||||
let start = source_location_to_position(¬ebook_index.translate_location(&start));
|
||||
let end = source_location_to_position(¬ebook_index.translate_location(&end));
|
||||
|
||||
NotebookRange {
|
||||
cell: starting_cell
|
||||
.map(OneIndexed::to_zero_indexed)
|
||||
.unwrap_or_default(),
|
||||
range: types::Range { start, end },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a UTF-16 code unit offset for a given line into a UTF-8 column number.
|
||||
fn utf8_column_offset(utf16_code_unit_offset: u32, line: &str) -> TextSize {
|
||||
let mut utf8_code_unit_offset = TextSize::new(0);
|
||||
@@ -96,3 +170,46 @@ fn utf8_column_offset(utf16_code_unit_offset: u32, line: &str) -> TextSize {
|
||||
|
||||
utf8_code_unit_offset
|
||||
}
|
||||
|
||||
fn offset_to_source_location(
|
||||
offset: TextSize,
|
||||
text: &str,
|
||||
index: &LineIndex,
|
||||
encoding: PositionEncoding,
|
||||
) -> SourceLocation {
|
||||
match encoding {
|
||||
PositionEncoding::UTF8 => {
|
||||
let row = index.line_index(offset);
|
||||
let column = offset - index.line_start(row, text);
|
||||
|
||||
SourceLocation {
|
||||
column: OneIndexed::from_zero_indexed(column.to_usize()),
|
||||
row,
|
||||
}
|
||||
}
|
||||
PositionEncoding::UTF16 => {
|
||||
let row = index.line_index(offset);
|
||||
|
||||
let column = if index.is_ascii() {
|
||||
(offset - index.line_start(row, text)).to_usize()
|
||||
} else {
|
||||
let up_to_line = &text[TextRange::new(index.line_start(row, text), offset)];
|
||||
up_to_line.encode_utf16().count()
|
||||
};
|
||||
|
||||
SourceLocation {
|
||||
column: OneIndexed::from_zero_indexed(column),
|
||||
row,
|
||||
}
|
||||
}
|
||||
PositionEncoding::UTF32 => index.source_location(offset, text),
|
||||
}
|
||||
}
|
||||
|
||||
fn source_location_to_position(location: &SourceLocation) -> types::Position {
|
||||
types::Position {
|
||||
line: u32::try_from(location.row.to_zero_indexed()).expect("row usize fits in u32"),
|
||||
character: u32::try_from(location.column.to_zero_indexed())
|
||||
.expect("character usize fits in u32"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,17 @@ use std::borrow::Cow;
|
||||
use lsp_types::request::DocumentDiagnosticRequest;
|
||||
use lsp_types::{
|
||||
Diagnostic, DiagnosticSeverity, DocumentDiagnosticParams, DocumentDiagnosticReport,
|
||||
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, Position, Range,
|
||||
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, NumberOrString, Range,
|
||||
RelatedFullDocumentDiagnosticReport, Url,
|
||||
};
|
||||
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
|
||||
use crate::edit::ToRangeExt;
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::{client::Notifier, Result};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use red_knot_workspace::db::{Db, RootDatabase};
|
||||
use ruff_db::diagnostic::Severity;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
|
||||
pub(crate) struct DocumentDiagnosticRequestHandler;
|
||||
|
||||
@@ -64,36 +66,37 @@ fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &RootDatabase) -> Vec<Di
|
||||
diagnostics
|
||||
.as_slice()
|
||||
.iter()
|
||||
.map(|message| to_lsp_diagnostic(message))
|
||||
.map(|message| to_lsp_diagnostic(db, message, snapshot.encoding()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_lsp_diagnostic(message: &str) -> Diagnostic {
|
||||
let words = message.split(':').collect::<Vec<_>>();
|
||||
fn to_lsp_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &dyn ruff_db::diagnostic::Diagnostic,
|
||||
encoding: crate::PositionEncoding,
|
||||
) -> Diagnostic {
|
||||
let range = if let Some(range) = diagnostic.range() {
|
||||
let index = line_index(db.upcast(), diagnostic.file());
|
||||
let source = source_text(db.upcast(), diagnostic.file());
|
||||
|
||||
let (range, message) = match words.as_slice() {
|
||||
[_, _, line, column, message] | [_, line, column, message] => {
|
||||
let line = line.parse::<u32>().unwrap_or_default().saturating_sub(1);
|
||||
let column = column.parse::<u32>().unwrap_or_default();
|
||||
(
|
||||
Range::new(
|
||||
Position::new(line, column.saturating_sub(1)),
|
||||
Position::new(line, column),
|
||||
),
|
||||
message.trim(),
|
||||
)
|
||||
}
|
||||
_ => (Range::default(), message),
|
||||
range.to_range(&source, &index, encoding)
|
||||
} else {
|
||||
Range::default()
|
||||
};
|
||||
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => DiagnosticSeverity::INFORMATION,
|
||||
Severity::Error => DiagnosticSeverity::ERROR,
|
||||
};
|
||||
|
||||
Diagnostic {
|
||||
range,
|
||||
severity: Some(DiagnosticSeverity::ERROR),
|
||||
severity: Some(severity),
|
||||
tags: None,
|
||||
code: None,
|
||||
code: Some(NumberOrString::String(diagnostic.rule().to_string())),
|
||||
code_description: None,
|
||||
source: Some("red-knot".into()),
|
||||
message: message.to_string(),
|
||||
message: diagnostic.message().into_owned(),
|
||||
related_information: None,
|
||||
data: None,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//!
|
||||
//! We don't assume that we will get the diagnostics in source order.
|
||||
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
use std::ops::{Deref, Range};
|
||||
|
||||
/// All diagnostics for one embedded Python file, sorted and grouped by start line number.
|
||||
@@ -19,13 +19,17 @@ pub(crate) struct SortedDiagnostics<T> {
|
||||
|
||||
impl<T> SortedDiagnostics<T>
|
||||
where
|
||||
T: Ranged + Clone,
|
||||
T: Diagnostic,
|
||||
{
|
||||
pub(crate) fn new(diagnostics: impl IntoIterator<Item = T>, line_index: &LineIndex) -> Self {
|
||||
let mut diagnostics: Vec<_> = diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| DiagnosticWithLine {
|
||||
line_number: line_index.line_index(diagnostic.start()),
|
||||
line_number: diagnostic
|
||||
.range()
|
||||
.map_or(OneIndexed::from_zero_indexed(0), |range| {
|
||||
line_index.line_index(range.start())
|
||||
}),
|
||||
diagnostic,
|
||||
})
|
||||
.collect();
|
||||
@@ -94,7 +98,7 @@ pub(crate) struct LineDiagnosticsIterator<'a, T> {
|
||||
|
||||
impl<'a, T> Iterator for LineDiagnosticsIterator<'a, T>
|
||||
where
|
||||
T: Ranged + Clone,
|
||||
T: Diagnostic,
|
||||
{
|
||||
type Item = LineDiagnostics<'a, T>;
|
||||
|
||||
@@ -110,7 +114,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::iter::FusedIterator for LineDiagnosticsIterator<'_, T> where T: Clone + Ranged {}
|
||||
impl<T> std::iter::FusedIterator for LineDiagnosticsIterator<'_, T> where T: Diagnostic {}
|
||||
|
||||
/// All diagnostics that start on a single line of source code in one embedded Python file.
|
||||
#[derive(Debug)]
|
||||
@@ -139,11 +143,14 @@ struct DiagnosticWithLine<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::Db;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use ruff_db::diagnostic::Severity;
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[test]
|
||||
fn sort_and_group() {
|
||||
@@ -152,13 +159,18 @@ mod tests {
|
||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||
let lines = line_index(&db, file);
|
||||
|
||||
let ranges = vec![
|
||||
let ranges = [
|
||||
TextRange::new(TextSize::new(0), TextSize::new(1)),
|
||||
TextRange::new(TextSize::new(5), TextSize::new(10)),
|
||||
TextRange::new(TextSize::new(1), TextSize::new(7)),
|
||||
];
|
||||
|
||||
let sorted = super::SortedDiagnostics::new(&ranges, &lines);
|
||||
let diagnostics: Vec<_> = ranges
|
||||
.into_iter()
|
||||
.map(|range| DummyDiagnostic { range, file })
|
||||
.collect();
|
||||
|
||||
let sorted = super::SortedDiagnostics::new(diagnostics, &lines);
|
||||
let grouped = sorted.iter_lines().collect::<Vec<_>>();
|
||||
|
||||
let [line1, line2] = &grouped[..] else {
|
||||
@@ -170,4 +182,32 @@ mod tests {
|
||||
assert_eq!(line2.line_number, OneIndexed::from_zero_indexed(1));
|
||||
assert_eq!(line2.diagnostics.len(), 1);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DummyDiagnostic {
|
||||
range: TextRange,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for DummyDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
"dummy"
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
"dummy".into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use colored::Colorize;
|
||||
use parser as test_parser;
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic};
|
||||
use ruff_db::files::{system_path_to_file, File, Files};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
@@ -18,9 +19,9 @@ mod parser;
|
||||
///
|
||||
/// Panic on test failure, and print failure details.
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub fn run(path: &Path, title: &str) {
|
||||
pub fn run(path: &Path, long_title: &str, short_title: &str) {
|
||||
let source = std::fs::read_to_string(path).unwrap();
|
||||
let suite = match test_parser::parse(title, &source) {
|
||||
let suite = match test_parser::parse(short_title, &source) {
|
||||
Ok(suite) => suite,
|
||||
Err(err) => {
|
||||
panic!("Error parsing `{}`: {err}", path.to_str().unwrap())
|
||||
@@ -48,8 +49,8 @@ pub fn run(path: &Path, title: &str) {
|
||||
for failure in failures {
|
||||
let absolute_line_number =
|
||||
backtick_line.checked_add(relative_line_number).unwrap();
|
||||
let line_info = format!("{title}:{absolute_line_number}").cyan();
|
||||
println!(" {line_info} {failure}");
|
||||
let line_info = format!("{long_title}:{absolute_line_number}").cyan();
|
||||
println!(" {line_info} {failure}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,16 +88,24 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
|
||||
.filter_map(|test_file| {
|
||||
let parsed = parsed_module(db, test_file.file);
|
||||
|
||||
// TODO allow testing against code with syntax errors
|
||||
assert!(
|
||||
parsed.errors().is_empty(),
|
||||
"Python syntax errors in {}, {}: {:?}",
|
||||
test.name(),
|
||||
test_file.file.path(db),
|
||||
parsed.errors()
|
||||
);
|
||||
let mut diagnostics: Vec<Box<_>> = parsed
|
||||
.errors()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|error| {
|
||||
let diagnostic: Box<dyn Diagnostic> =
|
||||
Box::new(ParseDiagnostic::new(test_file.file, error));
|
||||
diagnostic
|
||||
})
|
||||
.collect();
|
||||
|
||||
match matcher::match_file(db, test_file.file, check_types(db, test_file.file)) {
|
||||
let type_diagnostics = check_types(db, test_file.file);
|
||||
diagnostics.extend(type_diagnostics.into_iter().map(|diagnostic| {
|
||||
let diagnostic: Box<dyn Diagnostic> = Box::new((*diagnostic).clone());
|
||||
diagnostic
|
||||
}));
|
||||
|
||||
match matcher::match_file(db, test_file.file, diagnostics) {
|
||||
Ok(()) => None,
|
||||
Err(line_failures) => Some(FileFailures {
|
||||
backtick_offset: test_file.backtick_offset,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
//! Match [`TypeCheckDiagnostic`]s against [`Assertion`]s and produce test failure messages for any
|
||||
//! Match [`Diagnostic`]s against [`Assertion`]s and produce test failure messages for any
|
||||
//! mismatches.
|
||||
use crate::assertion::{Assertion, ErrorAssertion, InlineFileAssertions};
|
||||
use crate::db::Db;
|
||||
use crate::diagnostic::SortedDiagnostics;
|
||||
use colored::Colorize;
|
||||
use red_knot_python_semantic::types::TypeCheckDiagnostic;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::source::{line_index, source_text, SourceText};
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
use ruff_text_size::Ranged;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct FailuresByLine {
|
||||
@@ -55,7 +53,7 @@ pub(super) fn match_file<T>(
|
||||
diagnostics: impl IntoIterator<Item = T>,
|
||||
) -> Result<(), FailuresByLine>
|
||||
where
|
||||
T: Diagnostic + Clone,
|
||||
T: Diagnostic,
|
||||
{
|
||||
// Parse assertions from comments in the file, and get diagnostics from the file; both
|
||||
// ordered by line number.
|
||||
@@ -126,22 +124,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait Diagnostic: Ranged {
|
||||
fn rule(&self) -> &str;
|
||||
|
||||
fn message(&self) -> &str;
|
||||
}
|
||||
|
||||
impl Diagnostic for Arc<TypeCheckDiagnostic> {
|
||||
fn rule(&self) -> &str {
|
||||
self.as_ref().rule()
|
||||
}
|
||||
|
||||
fn message(&self) -> &str {
|
||||
self.as_ref().message()
|
||||
}
|
||||
}
|
||||
|
||||
trait Unmatched {
|
||||
fn unmatched(&self) -> String;
|
||||
}
|
||||
@@ -253,10 +235,15 @@ impl Matcher {
|
||||
}
|
||||
}
|
||||
|
||||
fn column<T: Ranged>(&self, ranged: &T) -> OneIndexed {
|
||||
self.line_index
|
||||
.source_location(ranged.start(), &self.source)
|
||||
.column
|
||||
fn column<T: Diagnostic>(&self, diagnostic: &T) -> OneIndexed {
|
||||
diagnostic
|
||||
.range()
|
||||
.map(|range| {
|
||||
self.line_index
|
||||
.source_location(range.start(), &self.source)
|
||||
.column
|
||||
})
|
||||
.unwrap_or(OneIndexed::from_zero_indexed(0))
|
||||
}
|
||||
|
||||
/// Check if `assertion` matches any [`Diagnostic`]s in `unmatched`.
|
||||
@@ -323,20 +310,21 @@ impl Matcher {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FailuresByLine;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_python_trivia::textwrap::dedent;
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_text_size::TextRange;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestDiagnostic {
|
||||
struct ExpectedDiagnostic {
|
||||
rule: &'static str,
|
||||
message: &'static str,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl TestDiagnostic {
|
||||
impl ExpectedDiagnostic {
|
||||
fn new(rule: &'static str, message: &'static str, offset: usize) -> Self {
|
||||
let offset: u32 = offset.try_into().unwrap();
|
||||
Self {
|
||||
@@ -345,32 +333,64 @@ mod tests {
|
||||
range: TextRange::new(offset.into(), (offset + 1).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_diagnostic(self, file: File) -> TestDiagnostic {
|
||||
TestDiagnostic {
|
||||
rule: self.rule,
|
||||
message: self.message,
|
||||
range: self.range,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Diagnostic for TestDiagnostic {
|
||||
#[derive(Debug)]
|
||||
struct TestDiagnostic {
|
||||
rule: &'static str,
|
||||
message: &'static str,
|
||||
range: TextRange,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for TestDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
self.rule
|
||||
}
|
||||
|
||||
fn message(&self) -> &str {
|
||||
self.message
|
||||
fn message(&self) -> Cow<str> {
|
||||
self.message.into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for TestDiagnostic {
|
||||
fn range(&self) -> ruff_text_size::TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
fn get_result(source: &str, diagnostics: Vec<TestDiagnostic>) -> Result<(), FailuresByLine> {
|
||||
fn get_result(
|
||||
source: &str,
|
||||
diagnostics: Vec<ExpectedDiagnostic>,
|
||||
) -> Result<(), FailuresByLine> {
|
||||
colored::control::set_override(false);
|
||||
|
||||
let mut db = crate::db::Db::setup(SystemPathBuf::from("/src"));
|
||||
db.write_file("/src/test.py", source).unwrap();
|
||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||
|
||||
super::match_file(&db, file, diagnostics)
|
||||
super::match_file(
|
||||
&db,
|
||||
file,
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.into_diagnostic(file)),
|
||||
)
|
||||
}
|
||||
|
||||
fn assert_fail(result: Result<(), FailuresByLine>, messages: &[(usize, &[&str])]) {
|
||||
@@ -403,7 +423,7 @@ mod tests {
|
||||
fn revealed_match() {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![TestDiagnostic::new(
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"revealed-type",
|
||||
"Revealed type is `Foo`",
|
||||
0,
|
||||
@@ -417,7 +437,7 @@ mod tests {
|
||||
fn revealed_wrong_rule() {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![TestDiagnostic::new(
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"not-revealed-type",
|
||||
"Revealed type is `Foo`",
|
||||
0,
|
||||
@@ -440,7 +460,11 @@ mod tests {
|
||||
fn revealed_wrong_message() {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![TestDiagnostic::new("revealed-type", "Something else", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"revealed-type",
|
||||
"Something else",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -467,8 +491,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![
|
||||
TestDiagnostic::new("revealed-type", "Revealed type is `Foo`", 0),
|
||||
TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Foo`", 0),
|
||||
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -479,7 +503,11 @@ mod tests {
|
||||
fn revealed_match_with_only_undefined() {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"undefined-reveal",
|
||||
"Doesn't matter",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_fail(result, &[(0, &["unmatched assertion: revealed: Foo"])]);
|
||||
@@ -490,8 +518,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
"x # revealed: Foo",
|
||||
vec![
|
||||
TestDiagnostic::new("revealed-type", "Revealed type is `Bar`", 0),
|
||||
TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Bar`", 0),
|
||||
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -512,8 +540,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
"reveal_type(1)",
|
||||
vec![
|
||||
TestDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
||||
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
||||
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -535,8 +563,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
"reveal_type(1) # error: [something-else]",
|
||||
vec![
|
||||
TestDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
||||
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
||||
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
|
||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -565,7 +593,7 @@ mod tests {
|
||||
fn error_match_rule() {
|
||||
let result = get_result(
|
||||
"x # error: [some-rule]",
|
||||
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -575,7 +603,7 @@ mod tests {
|
||||
fn error_wrong_rule() {
|
||||
let result = get_result(
|
||||
"x # error: [some-rule]",
|
||||
vec![TestDiagnostic::new("anything", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -594,7 +622,11 @@ mod tests {
|
||||
fn error_match_message() {
|
||||
let result = get_result(
|
||||
r#"x # error: "contains this""#,
|
||||
vec![TestDiagnostic::new("anything", "message contains this", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"anything",
|
||||
"message contains this",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -604,7 +636,7 @@ mod tests {
|
||||
fn error_wrong_message() {
|
||||
let result = get_result(
|
||||
r#"x # error: "contains this""#,
|
||||
vec![TestDiagnostic::new("anything", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -623,7 +655,7 @@ mod tests {
|
||||
fn error_match_column_and_rule() {
|
||||
let result = get_result(
|
||||
"x # error: 1 [some-rule]",
|
||||
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -633,7 +665,7 @@ mod tests {
|
||||
fn error_wrong_column() {
|
||||
let result = get_result(
|
||||
"x # error: 2 [rule]",
|
||||
vec![TestDiagnostic::new("rule", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("rule", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -652,7 +684,11 @@ mod tests {
|
||||
fn error_match_column_and_message() {
|
||||
let result = get_result(
|
||||
r#"x # error: 1 "contains this""#,
|
||||
vec![TestDiagnostic::new("anything", "message contains this", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"anything",
|
||||
"message contains this",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -662,7 +698,11 @@ mod tests {
|
||||
fn error_match_rule_and_message() {
|
||||
let result = get_result(
|
||||
r#"x # error: [a-rule] "contains this""#,
|
||||
vec![TestDiagnostic::new("a-rule", "message contains this", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"a-rule",
|
||||
"message contains this",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -672,7 +712,11 @@ mod tests {
|
||||
fn error_match_all() {
|
||||
let result = get_result(
|
||||
r#"x # error: 1 [a-rule] "contains this""#,
|
||||
vec![TestDiagnostic::new("a-rule", "message contains this", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"a-rule",
|
||||
"message contains this",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_ok(&result);
|
||||
@@ -682,7 +726,11 @@ mod tests {
|
||||
fn error_match_all_wrong_column() {
|
||||
let result = get_result(
|
||||
r#"x # error: 2 [some-rule] "contains this""#,
|
||||
vec![TestDiagnostic::new("some-rule", "message contains this", 0)],
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"some-rule",
|
||||
"message contains this",
|
||||
0,
|
||||
)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -701,7 +749,7 @@ mod tests {
|
||||
fn error_match_all_wrong_rule() {
|
||||
let result = get_result(
|
||||
r#"x # error: 1 [some-rule] "contains this""#,
|
||||
vec![TestDiagnostic::new(
|
||||
vec![ExpectedDiagnostic::new(
|
||||
"other-rule",
|
||||
"message contains this",
|
||||
0,
|
||||
@@ -724,7 +772,7 @@ mod tests {
|
||||
fn error_match_all_wrong_message() {
|
||||
let result = get_result(
|
||||
r#"x # error: 1 [some-rule] "contains this""#,
|
||||
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
|
||||
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -757,9 +805,9 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("line-two", "msg", two),
|
||||
TestDiagnostic::new("line-three", "msg", three),
|
||||
TestDiagnostic::new("line-five", "msg", five),
|
||||
ExpectedDiagnostic::new("line-two", "msg", two),
|
||||
ExpectedDiagnostic::new("line-three", "msg", three),
|
||||
ExpectedDiagnostic::new("line-five", "msg", five),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -788,8 +836,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("line-one", "msg", one),
|
||||
TestDiagnostic::new("line-two", "msg", two),
|
||||
ExpectedDiagnostic::new("line-one", "msg", one),
|
||||
ExpectedDiagnostic::new("line-two", "msg", two),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -809,8 +857,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("one-rule", "msg", x),
|
||||
TestDiagnostic::new("other-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("other-rule", "msg", x),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -830,8 +878,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("one-rule", "msg", x),
|
||||
TestDiagnostic::new("one-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -851,9 +899,9 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("one-rule", "msg", x),
|
||||
TestDiagnostic::new("other-rule", "msg", x),
|
||||
TestDiagnostic::new("third-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("one-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("other-rule", "msg", x),
|
||||
ExpectedDiagnostic::new("third-rule", "msg", x),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -877,8 +925,8 @@ mod tests {
|
||||
let result = get_result(
|
||||
&source,
|
||||
vec![
|
||||
TestDiagnostic::new("undefined-reveal", "msg", reveal),
|
||||
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[5]`", reveal),
|
||||
ExpectedDiagnostic::new("undefined-reveal", "msg", reveal),
|
||||
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[5]`", reveal),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -891,7 +939,7 @@ mod tests {
|
||||
let x = source.find('x').unwrap();
|
||||
let result = get_result(
|
||||
source,
|
||||
vec![TestDiagnostic::new("some-rule", "some message", x)],
|
||||
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
@@ -912,7 +960,7 @@ mod tests {
|
||||
let x = source.find('x').unwrap();
|
||||
let result = get_result(
|
||||
source,
|
||||
vec![TestDiagnostic::new("some-rule", "some message", x)],
|
||||
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
|
||||
);
|
||||
|
||||
assert_fail(
|
||||
|
||||
@@ -1 +1 @@
|
||||
d262beb07502cda412db2179fb406d45d1a9486f
|
||||
5052fa2f18db4493892e0f2775030683c9d06531
|
||||
|
||||
@@ -24,18 +24,22 @@ _asyncio: 3.0-
|
||||
_bisect: 3.0-
|
||||
_blake2: 3.6-
|
||||
_bootlocale: 3.4-3.9
|
||||
_bz2: 3.3-
|
||||
_codecs: 3.0-
|
||||
_collections_abc: 3.3-
|
||||
_compat_pickle: 3.1-
|
||||
_compression: 3.5-
|
||||
_contextvars: 3.7-
|
||||
_csv: 3.0-
|
||||
_ctypes: 3.0-
|
||||
_curses: 3.0-
|
||||
_dbm: 3.0-
|
||||
_decimal: 3.3-
|
||||
_dummy_thread: 3.0-3.8
|
||||
_dummy_threading: 3.0-3.8
|
||||
_frozen_importlib: 3.0-
|
||||
_frozen_importlib_external: 3.5-
|
||||
_gdbm: 3.0-
|
||||
_heapq: 3.0-
|
||||
_imp: 3.0-
|
||||
_interpchannels: 3.13-
|
||||
@@ -45,6 +49,7 @@ _io: 3.0-
|
||||
_json: 3.0-
|
||||
_locale: 3.0-
|
||||
_lsprof: 3.0-
|
||||
_lzma: 3.3-
|
||||
_markupbase: 3.0-
|
||||
_msi: 3.0-3.12
|
||||
_operator: 3.4-
|
||||
@@ -52,12 +57,14 @@ _osx_support: 3.0-
|
||||
_posixsubprocess: 3.2-
|
||||
_py_abc: 3.7-
|
||||
_pydecimal: 3.5-
|
||||
_queue: 3.7-
|
||||
_random: 3.0-
|
||||
_sitebuiltins: 3.4-
|
||||
_socket: 3.0- # present in 3.0 at runtime, but not in typeshed
|
||||
_sqlite3: 3.0-
|
||||
_ssl: 3.0-
|
||||
_stat: 3.4-
|
||||
_struct: 3.0-
|
||||
_thread: 3.0-
|
||||
_threading_local: 3.0-
|
||||
_tkinter: 3.0-
|
||||
|
||||
18
crates/red_knot_vendored/vendor/typeshed/stdlib/_bz2.pyi
vendored
Normal file
18
crates/red_knot_vendored/vendor/typeshed/stdlib/_bz2.pyi
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
from _typeshed import ReadableBuffer
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class BZ2Compressor:
|
||||
def __init__(self, compresslevel: int = 9) -> None: ...
|
||||
def compress(self, data: ReadableBuffer, /) -> bytes: ...
|
||||
def flush(self) -> bytes: ...
|
||||
|
||||
@final
|
||||
class BZ2Decompressor:
|
||||
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes: ...
|
||||
@property
|
||||
def eof(self) -> bool: ...
|
||||
@property
|
||||
def needs_input(self) -> bool: ...
|
||||
@property
|
||||
def unused_data(self) -> bytes: ...
|
||||
61
crates/red_knot_vendored/vendor/typeshed/stdlib/_contextvars.pyi
vendored
Normal file
61
crates/red_knot_vendored/vendor/typeshed/stdlib/_contextvars.pyi
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
import sys
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
from typing import Any, ClassVar, Generic, TypeVar, final, overload
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from types import GenericAlias
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_D = TypeVar("_D")
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
@final
|
||||
class ContextVar(Generic[_T]):
|
||||
@overload
|
||||
def __init__(self, name: str) -> None: ...
|
||||
@overload
|
||||
def __init__(self, name: str, *, default: _T) -> None: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@overload
|
||||
def get(self) -> _T: ...
|
||||
@overload
|
||||
def get(self, default: _T, /) -> _T: ...
|
||||
@overload
|
||||
def get(self, default: _D, /) -> _D | _T: ...
|
||||
def set(self, value: _T, /) -> Token[_T]: ...
|
||||
def reset(self, token: Token[_T], /) -> None: ...
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
@final
|
||||
class Token(Generic[_T]):
|
||||
@property
|
||||
def var(self) -> ContextVar[_T]: ...
|
||||
@property
|
||||
def old_value(self) -> Any: ... # returns either _T or MISSING, but that's hard to express
|
||||
MISSING: ClassVar[object]
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
def copy_context() -> Context: ...
|
||||
|
||||
# It doesn't make sense to make this generic, because for most Contexts each ContextVar will have
|
||||
# a different value.
|
||||
@final
|
||||
class Context(Mapping[ContextVar[Any], Any]):
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def get(self, key: ContextVar[_T], default: None = None, /) -> _T | None: ...
|
||||
@overload
|
||||
def get(self, key: ContextVar[_T], default: _T, /) -> _T: ...
|
||||
@overload
|
||||
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: ...
|
||||
def __getitem__(self, key: ContextVar[_T], /) -> _T: ...
|
||||
def __iter__(self) -> Iterator[ContextVar[Any]]: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __eq__(self, value: object, /) -> bool: ...
|
||||
43
crates/red_knot_vendored/vendor/typeshed/stdlib/_dbm.pyi
vendored
Normal file
43
crates/red_knot_vendored/vendor/typeshed/stdlib/_dbm.pyi
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.platform != "win32":
|
||||
_T = TypeVar("_T")
|
||||
_KeyType: TypeAlias = str | ReadOnlyBuffer
|
||||
_ValueType: TypeAlias = str | ReadOnlyBuffer
|
||||
|
||||
class error(OSError): ...
|
||||
library: str
|
||||
|
||||
# Actual typename dbm, not exposed by the implementation
|
||||
class _dbm:
|
||||
def close(self) -> None: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
def clear(self) -> None: ...
|
||||
|
||||
def __getitem__(self, item: _KeyType) -> bytes: ...
|
||||
def __setitem__(self, key: _KeyType, value: _ValueType) -> None: ...
|
||||
def __delitem__(self, key: _KeyType) -> None: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __del__(self) -> None: ...
|
||||
def __enter__(self) -> Self: ...
|
||||
def __exit__(
|
||||
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
||||
) -> None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType) -> bytes | None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType, default: _T) -> bytes | _T: ...
|
||||
def keys(self) -> list[bytes]: ...
|
||||
def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ...
|
||||
# Don't exist at runtime
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
@@ -107,9 +107,9 @@ class FileLoader:
|
||||
def get_filename(self, name: str | None = None) -> str: ...
|
||||
def load_module(self, name: str | None = None) -> types.ModuleType: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
def get_resource_reader(self, module: types.ModuleType) -> importlib.readers.FileReader: ...
|
||||
def get_resource_reader(self, name: str | None = None) -> importlib.readers.FileReader: ...
|
||||
else:
|
||||
def get_resource_reader(self, module: types.ModuleType) -> Self | None: ...
|
||||
def get_resource_reader(self, name: str | None = None) -> Self | None: ...
|
||||
def open_resource(self, resource: str) -> _io.FileIO: ...
|
||||
def resource_path(self, resource: str) -> str: ...
|
||||
def is_resource(self, name: str) -> bool: ...
|
||||
|
||||
47
crates/red_knot_vendored/vendor/typeshed/stdlib/_gdbm.pyi
vendored
Normal file
47
crates/red_knot_vendored/vendor/typeshed/stdlib/_gdbm.pyi
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.platform != "win32":
|
||||
_T = TypeVar("_T")
|
||||
_KeyType: TypeAlias = str | ReadOnlyBuffer
|
||||
_ValueType: TypeAlias = str | ReadOnlyBuffer
|
||||
|
||||
open_flags: str
|
||||
|
||||
class error(OSError): ...
|
||||
# Actual typename gdbm, not exposed by the implementation
|
||||
class _gdbm:
|
||||
def firstkey(self) -> bytes | None: ...
|
||||
def nextkey(self, key: _KeyType) -> bytes | None: ...
|
||||
def reorganize(self) -> None: ...
|
||||
def sync(self) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
def clear(self) -> None: ...
|
||||
|
||||
def __getitem__(self, item: _KeyType) -> bytes: ...
|
||||
def __setitem__(self, key: _KeyType, value: _ValueType) -> None: ...
|
||||
def __delitem__(self, key: _KeyType) -> None: ...
|
||||
def __contains__(self, key: _KeyType) -> bool: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __enter__(self) -> Self: ...
|
||||
def __exit__(
|
||||
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
||||
) -> None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType) -> bytes | None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType, default: _T) -> bytes | _T: ...
|
||||
def keys(self) -> list[bytes]: ...
|
||||
def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ...
|
||||
# Don't exist at runtime
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
@@ -33,10 +33,10 @@ class _IOBase:
|
||||
def readable(self) -> bool: ...
|
||||
read: Callable[..., Any]
|
||||
def readlines(self, hint: int = -1, /) -> list[bytes]: ...
|
||||
def seek(self, offset: int, whence: int = ..., /) -> int: ...
|
||||
def seek(self, offset: int, whence: int = 0, /) -> int: ...
|
||||
def seekable(self) -> bool: ...
|
||||
def tell(self) -> int: ...
|
||||
def truncate(self, size: int | None = ..., /) -> int: ...
|
||||
def truncate(self, size: int | None = None, /) -> int: ...
|
||||
def writable(self) -> bool: ...
|
||||
write: Callable[..., Any]
|
||||
def writelines(self, lines: Iterable[ReadableBuffer], /) -> None: ...
|
||||
@@ -59,8 +59,8 @@ class _BufferedIOBase(_IOBase):
|
||||
def readinto(self, buffer: WriteableBuffer, /) -> int: ...
|
||||
def write(self, buffer: ReadableBuffer, /) -> int: ...
|
||||
def readinto1(self, buffer: WriteableBuffer, /) -> int: ...
|
||||
def read(self, size: int | None = ..., /) -> bytes: ...
|
||||
def read1(self, size: int = ..., /) -> bytes: ...
|
||||
def read(self, size: int | None = -1, /) -> bytes: ...
|
||||
def read1(self, size: int = -1, /) -> bytes: ...
|
||||
|
||||
class FileIO(RawIOBase, _RawIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
|
||||
mode: str
|
||||
@@ -69,13 +69,15 @@ class FileIO(RawIOBase, _RawIOBase, BinaryIO): # type: ignore[misc] # incompat
|
||||
# "name" is a str. In the future, making FileIO generic might help.
|
||||
name: Any
|
||||
def __init__(
|
||||
self, file: FileDescriptorOrPath, mode: str = ..., closefd: bool = ..., opener: _Opener | None = ...
|
||||
self, file: FileDescriptorOrPath, mode: str = "r", closefd: bool = True, opener: _Opener | None = None
|
||||
) -> None: ...
|
||||
@property
|
||||
def closefd(self) -> bool: ...
|
||||
def seek(self, pos: int, whence: int = 0, /) -> int: ...
|
||||
def read(self, size: int | None = -1, /) -> bytes | MaybeNone: ...
|
||||
|
||||
class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
def __init__(self, initial_bytes: ReadableBuffer = ...) -> None: ...
|
||||
def __init__(self, initial_bytes: ReadableBuffer = b"") -> None: ...
|
||||
# BytesIO does not contain a "name" field. This workaround is necessary
|
||||
# to allow BytesIO sub-classes to add this field, as it is defined
|
||||
# as a read-only property on IO[].
|
||||
@@ -83,16 +85,22 @@ class BytesIO(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc]
|
||||
def getvalue(self) -> bytes: ...
|
||||
def getbuffer(self) -> memoryview: ...
|
||||
def read1(self, size: int | None = -1, /) -> bytes: ...
|
||||
def readlines(self, size: int | None = None, /) -> list[bytes]: ...
|
||||
def seek(self, pos: int, whence: int = 0, /) -> int: ...
|
||||
|
||||
class BufferedReader(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
raw: RawIOBase
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def peek(self, size: int = 0, /) -> bytes: ...
|
||||
def seek(self, target: int, whence: int = 0, /) -> int: ...
|
||||
def truncate(self, pos: int | None = None, /) -> int: ...
|
||||
|
||||
class BufferedWriter(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
|
||||
raw: RawIOBase
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def write(self, buffer: ReadableBuffer, /) -> int: ...
|
||||
def seek(self, target: int, whence: int = 0, /) -> int: ...
|
||||
def truncate(self, pos: int | None = None, /) -> int: ...
|
||||
|
||||
class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
mode: str
|
||||
@@ -101,10 +109,11 @@ class BufferedRandom(BufferedIOBase, _BufferedIOBase, BinaryIO): # type: ignore
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = 8192) -> None: ...
|
||||
def seek(self, target: int, whence: int = 0, /) -> int: ... # stubtest needs this
|
||||
def peek(self, size: int = 0, /) -> bytes: ...
|
||||
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 peek(self, size: int = ..., /) -> bytes: ...
|
||||
def peek(self, size: int = 0, /) -> bytes: ...
|
||||
|
||||
class _TextIOBase(_IOBase):
|
||||
encoding: str
|
||||
@@ -115,9 +124,9 @@ class _TextIOBase(_IOBase):
|
||||
def detach(self) -> BinaryIO: ...
|
||||
def write(self, s: str, /) -> int: ...
|
||||
def writelines(self, lines: Iterable[str], /) -> None: ... # type: ignore[override]
|
||||
def readline(self, size: int = ..., /) -> str: ... # type: ignore[override]
|
||||
def readline(self, size: int = -1, /) -> str: ... # type: ignore[override]
|
||||
def readlines(self, hint: int = -1, /) -> list[str]: ... # type: ignore[override]
|
||||
def read(self, size: int | None = ..., /) -> str: ...
|
||||
def read(self, size: int | None = -1, /) -> str: ...
|
||||
|
||||
@type_check_only
|
||||
class _WrappedBuffer(Protocol):
|
||||
@@ -177,9 +186,10 @@ class TextIOWrapper(TextIOBase, _TextIOBase, TextIO, Generic[_BufferT_co]): # t
|
||||
# TextIOWrapper's version of seek only supports a limited subset of
|
||||
# operations.
|
||||
def seek(self, cookie: int, whence: int = 0, /) -> int: ...
|
||||
def truncate(self, pos: int | None = None, /) -> int: ...
|
||||
|
||||
class StringIO(TextIOBase, _TextIOBase, TextIO): # type: ignore[misc] # incompatible definitions of write in the base classes
|
||||
def __init__(self, initial_value: str | None = ..., newline: str | None = ...) -> None: ...
|
||||
def __init__(self, initial_value: str | None = "", newline: str | None = "\n") -> None: ...
|
||||
# StringIO does not contain a "name" field. This workaround is necessary
|
||||
# to allow StringIO sub-classes to add this field, as it is defined
|
||||
# as a read-only property on IO[].
|
||||
@@ -187,9 +197,11 @@ class StringIO(TextIOBase, _TextIOBase, TextIO): # type: ignore[misc] # incomp
|
||||
def getvalue(self) -> str: ...
|
||||
@property
|
||||
def line_buffering(self) -> bool: ...
|
||||
def seek(self, pos: int, whence: int = 0, /) -> int: ...
|
||||
def truncate(self, pos: int | None = None, /) -> int: ...
|
||||
|
||||
class IncrementalNewlineDecoder:
|
||||
def __init__(self, decoder: codecs.IncrementalDecoder | None, translate: bool, errors: str = ...) -> None: ...
|
||||
def __init__(self, decoder: codecs.IncrementalDecoder | None, translate: bool, errors: str = "strict") -> None: ...
|
||||
def decode(self, input: ReadableBuffer | str, final: bool = False) -> str: ...
|
||||
@property
|
||||
def newlines(self) -> str | tuple[str, ...] | None: ...
|
||||
|
||||
60
crates/red_knot_vendored/vendor/typeshed/stdlib/_lzma.pyi
vendored
Normal file
60
crates/red_knot_vendored/vendor/typeshed/stdlib/_lzma.pyi
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
from _typeshed import ReadableBuffer
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import Any, Final, final
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
_FilterChain: TypeAlias = Sequence[Mapping[str, Any]]
|
||||
|
||||
FORMAT_AUTO: Final = 0
|
||||
FORMAT_XZ: Final = 1
|
||||
FORMAT_ALONE: Final = 2
|
||||
FORMAT_RAW: Final = 3
|
||||
CHECK_NONE: Final = 0
|
||||
CHECK_CRC32: Final = 1
|
||||
CHECK_CRC64: Final = 4
|
||||
CHECK_SHA256: Final = 10
|
||||
CHECK_ID_MAX: Final = 15
|
||||
CHECK_UNKNOWN: Final = 16
|
||||
FILTER_LZMA1: int # v big number
|
||||
FILTER_LZMA2: Final = 33
|
||||
FILTER_DELTA: Final = 3
|
||||
FILTER_X86: Final = 4
|
||||
FILTER_IA64: Final = 6
|
||||
FILTER_ARM: Final = 7
|
||||
FILTER_ARMTHUMB: Final = 8
|
||||
FILTER_SPARC: Final = 9
|
||||
FILTER_POWERPC: Final = 5
|
||||
MF_HC3: Final = 3
|
||||
MF_HC4: Final = 4
|
||||
MF_BT2: Final = 18
|
||||
MF_BT3: Final = 19
|
||||
MF_BT4: Final = 20
|
||||
MODE_FAST: Final = 1
|
||||
MODE_NORMAL: Final = 2
|
||||
PRESET_DEFAULT: Final = 6
|
||||
PRESET_EXTREME: int # v big number
|
||||
|
||||
@final
|
||||
class LZMADecompressor:
|
||||
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: ...
|
||||
@property
|
||||
def eof(self) -> bool: ...
|
||||
@property
|
||||
def unused_data(self) -> bytes: ...
|
||||
@property
|
||||
def needs_input(self) -> bool: ...
|
||||
|
||||
@final
|
||||
class LZMACompressor:
|
||||
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: ...
|
||||
|
||||
class LZMAError(Exception): ...
|
||||
|
||||
def is_check_supported(check_id: int, /) -> bool: ...
|
||||
20
crates/red_knot_vendored/vendor/typeshed/stdlib/_queue.pyi
vendored
Normal file
20
crates/red_knot_vendored/vendor/typeshed/stdlib/_queue.pyi
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
import sys
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from types import GenericAlias
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class Empty(Exception): ...
|
||||
|
||||
class SimpleQueue(Generic[_T]):
|
||||
def __init__(self) -> None: ...
|
||||
def empty(self) -> bool: ...
|
||||
def get(self, block: bool = True, timeout: float | None = None) -> _T: ...
|
||||
def get_nowait(self) -> _T: ...
|
||||
def put(self, item: _T, block: bool = True, timeout: float | None = None) -> None: ...
|
||||
def put_nowait(self, item: _T) -> None: ...
|
||||
def qsize(self) -> int: ...
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
22
crates/red_knot_vendored/vendor/typeshed/stdlib/_struct.pyi
vendored
Normal file
22
crates/red_knot_vendored/vendor/typeshed/stdlib/_struct.pyi
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
from _typeshed import ReadableBuffer, WriteableBuffer
|
||||
from collections.abc import Iterator
|
||||
from typing import Any
|
||||
|
||||
def pack(fmt: str | bytes, /, *v: Any) -> bytes: ...
|
||||
def pack_into(fmt: str | bytes, buffer: WriteableBuffer, offset: int, /, *v: Any) -> None: ...
|
||||
def unpack(format: str | bytes, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ...
|
||||
def unpack_from(format: str | bytes, /, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ...
|
||||
def iter_unpack(format: str | bytes, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ...
|
||||
def calcsize(format: str | bytes, /) -> int: ...
|
||||
|
||||
class Struct:
|
||||
@property
|
||||
def format(self) -> str: ...
|
||||
@property
|
||||
def size(self) -> int: ...
|
||||
def __init__(self, format: str | bytes) -> None: ...
|
||||
def pack(self, *v: Any) -> bytes: ...
|
||||
def pack_into(self, buffer: WriteableBuffer, offset: int, *v: Any) -> None: ...
|
||||
def unpack(self, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ...
|
||||
def unpack_from(self, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ...
|
||||
def iter_unpack(self, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ...
|
||||
@@ -1,5 +1,5 @@
|
||||
from typing import Any
|
||||
from typing_extensions import TypeAlias
|
||||
from typing_extensions import Self, TypeAlias
|
||||
from weakref import ReferenceType
|
||||
|
||||
__all__ = ["local"]
|
||||
@@ -12,6 +12,7 @@ class _localimpl:
|
||||
def create_dict(self) -> _LocalDict: ...
|
||||
|
||||
class local:
|
||||
def __new__(cls, /, *args: Any, **kw: Any) -> Self: ...
|
||||
def __getattribute__(self, name: str) -> Any: ...
|
||||
def __setattr__(self, name: str, value: Any) -> None: ...
|
||||
def __delattr__(self, name: str) -> None: ...
|
||||
|
||||
@@ -1284,9 +1284,7 @@ class property:
|
||||
|
||||
@final
|
||||
class _NotImplementedType(Any):
|
||||
# A little weird, but typing the __call__ as NotImplemented makes the error message
|
||||
# for NotImplemented() much better
|
||||
__call__: NotImplemented # type: ignore[valid-type] # pyright: ignore[reportInvalidTypeForm]
|
||||
__call__: None
|
||||
|
||||
NotImplemented: _NotImplementedType
|
||||
|
||||
@@ -1917,7 +1915,7 @@ class StopIteration(Exception):
|
||||
|
||||
class OSError(Exception):
|
||||
errno: int | None
|
||||
strerror: str
|
||||
strerror: str | None
|
||||
# filename, filename2 are actually str | bytes | None
|
||||
filename: Any
|
||||
filename2: Any
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import _compression
|
||||
import sys
|
||||
from _bz2 import BZ2Compressor as BZ2Compressor, BZ2Decompressor as BZ2Decompressor
|
||||
from _compression import BaseStream
|
||||
from _typeshed import ReadableBuffer, StrOrBytesPath, WriteableBuffer
|
||||
from collections.abc import Iterable
|
||||
from typing import IO, Any, Literal, Protocol, SupportsIndex, TextIO, final, overload
|
||||
from typing import IO, Any, Literal, Protocol, SupportsIndex, TextIO, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
__all__ = ["BZ2File", "BZ2Compressor", "BZ2Decompressor", "open", "compress", "decompress"]
|
||||
@@ -128,19 +129,3 @@ class BZ2File(BaseStream, IO[bytes]):
|
||||
def seek(self, offset: int, whence: int = 0) -> int: ...
|
||||
def write(self, data: ReadableBuffer) -> int: ...
|
||||
def writelines(self, seq: Iterable[ReadableBuffer]) -> None: ...
|
||||
|
||||
@final
|
||||
class BZ2Compressor:
|
||||
def __init__(self, compresslevel: int = 9) -> None: ...
|
||||
def compress(self, data: ReadableBuffer, /) -> bytes: ...
|
||||
def flush(self) -> bytes: ...
|
||||
|
||||
@final
|
||||
class BZ2Decompressor:
|
||||
def decompress(self, data: ReadableBuffer, max_length: int = -1) -> bytes: ...
|
||||
@property
|
||||
def eof(self) -> bool: ...
|
||||
@property
|
||||
def needs_input(self) -> bool: ...
|
||||
@property
|
||||
def unused_data(self) -> bytes: ...
|
||||
|
||||
@@ -1,63 +1,3 @@
|
||||
import sys
|
||||
from collections.abc import Callable, Iterator, Mapping
|
||||
from typing import Any, ClassVar, Generic, TypeVar, final, overload
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from types import GenericAlias
|
||||
from _contextvars import Context as Context, ContextVar as ContextVar, Token as Token, copy_context as copy_context
|
||||
|
||||
__all__ = ("Context", "ContextVar", "Token", "copy_context")
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_D = TypeVar("_D")
|
||||
_P = ParamSpec("_P")
|
||||
|
||||
@final
|
||||
class ContextVar(Generic[_T]):
|
||||
@overload
|
||||
def __init__(self, name: str) -> None: ...
|
||||
@overload
|
||||
def __init__(self, name: str, *, default: _T) -> None: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
@overload
|
||||
def get(self) -> _T: ...
|
||||
@overload
|
||||
def get(self, default: _T, /) -> _T: ...
|
||||
@overload
|
||||
def get(self, default: _D, /) -> _D | _T: ...
|
||||
def set(self, value: _T, /) -> Token[_T]: ...
|
||||
def reset(self, token: Token[_T], /) -> None: ...
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
@final
|
||||
class Token(Generic[_T]):
|
||||
@property
|
||||
def var(self) -> ContextVar[_T]: ...
|
||||
@property
|
||||
def old_value(self) -> Any: ... # returns either _T or MISSING, but that's hard to express
|
||||
MISSING: ClassVar[object]
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
def copy_context() -> Context: ...
|
||||
|
||||
# It doesn't make sense to make this generic, because for most Contexts each ContextVar will have
|
||||
# a different value.
|
||||
@final
|
||||
class Context(Mapping[ContextVar[Any], Any]):
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def get(self, key: ContextVar[_T], default: None = None, /) -> _T | None: ...
|
||||
@overload
|
||||
def get(self, key: ContextVar[_T], default: _T, /) -> _T: ...
|
||||
@overload
|
||||
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: ...
|
||||
def __getitem__(self, key: ContextVar[_T], /) -> _T: ...
|
||||
def __iter__(self) -> Iterator[ContextVar[Any]]: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __eq__(self, value: object, /) -> bool: ...
|
||||
|
||||
@@ -1,47 +1 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.platform != "win32":
|
||||
_T = TypeVar("_T")
|
||||
_KeyType: TypeAlias = str | ReadOnlyBuffer
|
||||
_ValueType: TypeAlias = str | ReadOnlyBuffer
|
||||
|
||||
open_flags: str
|
||||
|
||||
class error(OSError): ...
|
||||
# Actual typename gdbm, not exposed by the implementation
|
||||
class _gdbm:
|
||||
def firstkey(self) -> bytes | None: ...
|
||||
def nextkey(self, key: _KeyType) -> bytes | None: ...
|
||||
def reorganize(self) -> None: ...
|
||||
def sync(self) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
def clear(self) -> None: ...
|
||||
|
||||
def __getitem__(self, item: _KeyType) -> bytes: ...
|
||||
def __setitem__(self, key: _KeyType, value: _ValueType) -> None: ...
|
||||
def __delitem__(self, key: _KeyType) -> None: ...
|
||||
def __contains__(self, key: _KeyType) -> bool: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __enter__(self) -> Self: ...
|
||||
def __exit__(
|
||||
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
||||
) -> None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType) -> bytes | None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType, default: _T) -> bytes | _T: ...
|
||||
def keys(self) -> list[bytes]: ...
|
||||
def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ...
|
||||
# Don't exist at runtime
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _gdbm: ...
|
||||
from _gdbm import *
|
||||
|
||||
@@ -1,43 +1 @@
|
||||
import sys
|
||||
from _typeshed import ReadOnlyBuffer, StrOrBytesPath
|
||||
from types import TracebackType
|
||||
from typing import TypeVar, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
if sys.platform != "win32":
|
||||
_T = TypeVar("_T")
|
||||
_KeyType: TypeAlias = str | ReadOnlyBuffer
|
||||
_ValueType: TypeAlias = str | ReadOnlyBuffer
|
||||
|
||||
class error(OSError): ...
|
||||
library: str
|
||||
|
||||
# Actual typename dbm, not exposed by the implementation
|
||||
class _dbm:
|
||||
def close(self) -> None: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
def clear(self) -> None: ...
|
||||
|
||||
def __getitem__(self, item: _KeyType) -> bytes: ...
|
||||
def __setitem__(self, key: _KeyType, value: _ValueType) -> None: ...
|
||||
def __delitem__(self, key: _KeyType) -> None: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __del__(self) -> None: ...
|
||||
def __enter__(self) -> Self: ...
|
||||
def __exit__(
|
||||
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
||||
) -> None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType) -> bytes | None: ...
|
||||
@overload
|
||||
def get(self, k: _KeyType, default: _T) -> bytes | _T: ...
|
||||
def keys(self) -> list[bytes]: ...
|
||||
def setdefault(self, k: _KeyType, default: _ValueType = ...) -> bytes: ...
|
||||
# Don't exist at runtime
|
||||
__new__: None # type: ignore[assignment]
|
||||
__init__: None # type: ignore[assignment]
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def open(filename: StrOrBytesPath, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
else:
|
||||
def open(filename: str, flags: str = "r", mode: int = 0o666, /) -> _dbm: ...
|
||||
from _dbm import *
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from _typeshed import BytesPath, StrPath, Unused
|
||||
from collections.abc import Callable, Iterable
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from distutils.file_util import _BytesPathT, _StrPathT
|
||||
from typing import Literal, overload
|
||||
from typing_extensions import TypeAlias, TypeVarTuple, Unpack
|
||||
@@ -63,7 +63,7 @@ class CCompiler:
|
||||
def set_executables(self, **args: str) -> None: ...
|
||||
def compile(
|
||||
self,
|
||||
sources: list[str],
|
||||
sources: Sequence[StrPath],
|
||||
output_dir: str | None = None,
|
||||
macros: list[_Macro] | None = None,
|
||||
include_dirs: list[str] | None = None,
|
||||
|
||||
@@ -2,7 +2,7 @@ import sys
|
||||
from collections.abc import Callable
|
||||
from decimal import Decimal
|
||||
from numbers import Integral, Rational, Real
|
||||
from typing import Any, Literal, SupportsIndex, overload
|
||||
from typing import Any, Literal, Protocol, SupportsIndex, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
_ComparableNum: TypeAlias = int | float | Decimal | Real
|
||||
@@ -20,11 +20,19 @@ else:
|
||||
@overload
|
||||
def gcd(a: Integral, b: Integral) -> Integral: ...
|
||||
|
||||
class _ConvertibleToIntegerRatio(Protocol):
|
||||
def as_integer_ratio(self) -> tuple[int | Rational, int | Rational]: ...
|
||||
|
||||
class Fraction(Rational):
|
||||
@overload
|
||||
def __new__(cls, numerator: int | Rational = 0, denominator: int | Rational | None = None) -> Self: ...
|
||||
@overload
|
||||
def __new__(cls, value: float | Decimal | str, /) -> Self: ...
|
||||
|
||||
if sys.version_info >= (3, 14):
|
||||
@overload
|
||||
def __new__(cls, value: _ConvertibleToIntegerRatio) -> Self: ...
|
||||
|
||||
@classmethod
|
||||
def from_float(cls, f: float) -> Self: ...
|
||||
@classmethod
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from collections.abc import Iterable
|
||||
|
||||
__all__ = ["GetoptError", "error", "getopt", "gnu_getopt"]
|
||||
|
||||
def getopt(args: list[str], shortopts: str, longopts: list[str] = []) -> tuple[list[tuple[str, str]], list[str]]: ...
|
||||
def gnu_getopt(args: list[str], shortopts: str, longopts: list[str] = []) -> tuple[list[tuple[str, str]], list[str]]: ...
|
||||
def getopt(args: list[str], shortopts: str, longopts: Iterable[str] | str = []) -> tuple[list[tuple[str, str]], list[str]]: ...
|
||||
def gnu_getopt(
|
||||
args: list[str], shortopts: str, longopts: Iterable[str] | str = []
|
||||
) -> tuple[list[tuple[str, str]], list[str]]: ...
|
||||
|
||||
class GetoptError(Exception):
|
||||
msg: str
|
||||
|
||||
@@ -128,19 +128,6 @@ class _BaseNetwork(_IPAddressBase, Generic[_A]):
|
||||
@property
|
||||
def hostmask(self) -> _A: ...
|
||||
|
||||
class _BaseInterface(_BaseAddress, Generic[_A, _N]):
|
||||
hostmask: _A
|
||||
netmask: _A
|
||||
network: _N
|
||||
@property
|
||||
def ip(self) -> _A: ...
|
||||
@property
|
||||
def with_hostmask(self) -> str: ...
|
||||
@property
|
||||
def with_netmask(self) -> str: ...
|
||||
@property
|
||||
def with_prefixlen(self) -> str: ...
|
||||
|
||||
class _BaseV4:
|
||||
@property
|
||||
def version(self) -> Literal[4]: ...
|
||||
@@ -154,9 +141,21 @@ class IPv4Address(_BaseV4, _BaseAddress):
|
||||
|
||||
class IPv4Network(_BaseV4, _BaseNetwork[IPv4Address]): ...
|
||||
|
||||
class IPv4Interface(IPv4Address, _BaseInterface[IPv4Address, IPv4Network]):
|
||||
class IPv4Interface(IPv4Address):
|
||||
netmask: IPv4Address
|
||||
network: IPv4Network
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@property
|
||||
def hostmask(self) -> IPv4Address: ...
|
||||
@property
|
||||
def ip(self) -> IPv4Address: ...
|
||||
@property
|
||||
def with_hostmask(self) -> str: ...
|
||||
@property
|
||||
def with_netmask(self) -> str: ...
|
||||
@property
|
||||
def with_prefixlen(self) -> str: ...
|
||||
|
||||
class _BaseV6:
|
||||
@property
|
||||
@@ -184,9 +183,21 @@ class IPv6Network(_BaseV6, _BaseNetwork[IPv6Address]):
|
||||
@property
|
||||
def is_site_local(self) -> bool: ...
|
||||
|
||||
class IPv6Interface(IPv6Address, _BaseInterface[IPv6Address, IPv6Network]):
|
||||
class IPv6Interface(IPv6Address):
|
||||
netmask: IPv6Address
|
||||
network: IPv6Network
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@property
|
||||
def hostmask(self) -> IPv6Address: ...
|
||||
@property
|
||||
def ip(self) -> IPv6Address: ...
|
||||
@property
|
||||
def with_hostmask(self) -> str: ...
|
||||
@property
|
||||
def with_netmask(self) -> str: ...
|
||||
@property
|
||||
def with_prefixlen(self) -> str: ...
|
||||
|
||||
def v4_int_to_packed(address: int) -> bytes: ...
|
||||
def v6_int_to_packed(address: int) -> bytes: ...
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath
|
||||
from collections.abc import Callable, Hashable, Iterable, Sequence
|
||||
from collections.abc import Callable, Hashable, Iterable, Mapping, Sequence
|
||||
from configparser import RawConfigParser
|
||||
from re import Pattern
|
||||
from threading import Thread
|
||||
@@ -63,7 +63,7 @@ def dictConfig(config: _DictConfigArgs | dict[str, Any]) -> None: ...
|
||||
if sys.version_info >= (3, 10):
|
||||
def fileConfig(
|
||||
fname: StrOrBytesPath | IO[str] | RawConfigParser,
|
||||
defaults: dict[str, str] | None = None,
|
||||
defaults: Mapping[str, str] | None = None,
|
||||
disable_existing_loggers: bool = True,
|
||||
encoding: str | None = None,
|
||||
) -> None: ...
|
||||
@@ -71,7 +71,7 @@ if sys.version_info >= (3, 10):
|
||||
else:
|
||||
def fileConfig(
|
||||
fname: StrOrBytesPath | IO[str] | RawConfigParser,
|
||||
defaults: dict[str, str] | None = None,
|
||||
defaults: Mapping[str, str] | None = None,
|
||||
disable_existing_loggers: bool = True,
|
||||
) -> None: ...
|
||||
|
||||
|
||||
@@ -260,6 +260,8 @@ class QueueHandler(Handler):
|
||||
def __init__(self, queue: _QueueLike[Any]) -> None: ...
|
||||
def prepare(self, record: LogRecord) -> Any: ...
|
||||
def enqueue(self, record: LogRecord) -> None: ...
|
||||
if sys.version_info >= (3, 12):
|
||||
listener: QueueListener | None
|
||||
|
||||
class QueueListener:
|
||||
handlers: tuple[Handler, ...] # undocumented
|
||||
|
||||
@@ -1,7 +1,41 @@
|
||||
from _compression import BaseStream
|
||||
from _lzma import (
|
||||
CHECK_CRC32 as CHECK_CRC32,
|
||||
CHECK_CRC64 as CHECK_CRC64,
|
||||
CHECK_ID_MAX as CHECK_ID_MAX,
|
||||
CHECK_NONE as CHECK_NONE,
|
||||
CHECK_SHA256 as CHECK_SHA256,
|
||||
CHECK_UNKNOWN as CHECK_UNKNOWN,
|
||||
FILTER_ARM as FILTER_ARM,
|
||||
FILTER_ARMTHUMB as FILTER_ARMTHUMB,
|
||||
FILTER_DELTA as FILTER_DELTA,
|
||||
FILTER_IA64 as FILTER_IA64,
|
||||
FILTER_LZMA1 as FILTER_LZMA1,
|
||||
FILTER_LZMA2 as FILTER_LZMA2,
|
||||
FILTER_POWERPC as FILTER_POWERPC,
|
||||
FILTER_SPARC as FILTER_SPARC,
|
||||
FILTER_X86 as FILTER_X86,
|
||||
FORMAT_ALONE as FORMAT_ALONE,
|
||||
FORMAT_AUTO as FORMAT_AUTO,
|
||||
FORMAT_RAW as FORMAT_RAW,
|
||||
FORMAT_XZ as FORMAT_XZ,
|
||||
MF_BT2 as MF_BT2,
|
||||
MF_BT3 as MF_BT3,
|
||||
MF_BT4 as MF_BT4,
|
||||
MF_HC3 as MF_HC3,
|
||||
MF_HC4 as MF_HC4,
|
||||
MODE_FAST as MODE_FAST,
|
||||
MODE_NORMAL as MODE_NORMAL,
|
||||
PRESET_DEFAULT as PRESET_DEFAULT,
|
||||
PRESET_EXTREME as PRESET_EXTREME,
|
||||
LZMACompressor as LZMACompressor,
|
||||
LZMADecompressor as LZMADecompressor,
|
||||
LZMAError as LZMAError,
|
||||
_FilterChain,
|
||||
is_check_supported as is_check_supported,
|
||||
)
|
||||
from _typeshed import ReadableBuffer, StrOrBytesPath
|
||||
from collections.abc import Mapping, Sequence
|
||||
from typing import IO, Any, Final, Literal, TextIO, final, overload
|
||||
from typing import IO, Literal, TextIO, overload
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
__all__ = [
|
||||
@@ -48,62 +82,6 @@ _OpenTextWritingMode: TypeAlias = Literal["wt", "xt", "at"]
|
||||
|
||||
_PathOrFile: TypeAlias = StrOrBytesPath | IO[bytes]
|
||||
|
||||
_FilterChain: TypeAlias = Sequence[Mapping[str, Any]]
|
||||
|
||||
FORMAT_AUTO: Final = 0
|
||||
FORMAT_XZ: Final = 1
|
||||
FORMAT_ALONE: Final = 2
|
||||
FORMAT_RAW: Final = 3
|
||||
CHECK_NONE: Final = 0
|
||||
CHECK_CRC32: Final = 1
|
||||
CHECK_CRC64: Final = 4
|
||||
CHECK_SHA256: Final = 10
|
||||
CHECK_ID_MAX: Final = 15
|
||||
CHECK_UNKNOWN: Final = 16
|
||||
FILTER_LZMA1: int # v big number
|
||||
FILTER_LZMA2: Final = 33
|
||||
FILTER_DELTA: Final = 3
|
||||
FILTER_X86: Final = 4
|
||||
FILTER_IA64: Final = 6
|
||||
FILTER_ARM: Final = 7
|
||||
FILTER_ARMTHUMB: Final = 8
|
||||
FILTER_SPARC: Final = 9
|
||||
FILTER_POWERPC: Final = 5
|
||||
MF_HC3: Final = 3
|
||||
MF_HC4: Final = 4
|
||||
MF_BT2: Final = 18
|
||||
MF_BT3: Final = 19
|
||||
MF_BT4: Final = 20
|
||||
MODE_FAST: Final = 1
|
||||
MODE_NORMAL: Final = 2
|
||||
PRESET_DEFAULT: Final = 6
|
||||
PRESET_EXTREME: int # v big number
|
||||
|
||||
# from _lzma.c
|
||||
@final
|
||||
class LZMADecompressor:
|
||||
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: ...
|
||||
@property
|
||||
def eof(self) -> bool: ...
|
||||
@property
|
||||
def unused_data(self) -> bytes: ...
|
||||
@property
|
||||
def needs_input(self) -> bool: ...
|
||||
|
||||
# from _lzma.c
|
||||
@final
|
||||
class LZMACompressor:
|
||||
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: ...
|
||||
|
||||
class LZMAError(Exception): ...
|
||||
|
||||
class LZMAFile(BaseStream, IO[bytes]): # type: ignore[misc] # incompatible definitions of writelines in the base classes
|
||||
def __init__(
|
||||
self,
|
||||
@@ -194,4 +172,3 @@ def compress(
|
||||
def decompress(
|
||||
data: ReadableBuffer, format: int = 0, memlimit: int | None = None, filters: _FilterChain | None = None
|
||||
) -> bytes: ...
|
||||
def is_check_supported(check_id: int, /) -> bool: ...
|
||||
|
||||
@@ -34,9 +34,22 @@ class mmap:
|
||||
if sys.platform == "win32":
|
||||
def __init__(self, fileno: int, length: int, tagname: str | None = ..., access: int = ..., offset: int = ...) -> None: ...
|
||||
else:
|
||||
def __init__(
|
||||
self, fileno: int, length: int, flags: int = ..., prot: int = ..., access: int = ..., offset: int = ...
|
||||
) -> None: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
def __init__(
|
||||
self,
|
||||
fileno: int,
|
||||
length: int,
|
||||
flags: int = ...,
|
||||
prot: int = ...,
|
||||
access: int = ...,
|
||||
offset: int = ...,
|
||||
*,
|
||||
trackfd: bool = True,
|
||||
) -> None: ...
|
||||
else:
|
||||
def __init__(
|
||||
self, fileno: int, length: int, flags: int = ..., prot: int = ..., access: int = ..., offset: int = ...
|
||||
) -> None: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
def flush(self, offset: int = ..., size: int = ...) -> None: ...
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing_extensions import Self, TypeAlias
|
||||
from .connection import Connection
|
||||
from .context import BaseContext
|
||||
from .shared_memory import _SLT, ShareableList as _ShareableList, SharedMemory as _SharedMemory
|
||||
from .util import Finalize as _Finalize
|
||||
|
||||
__all__ = ["BaseManager", "SyncManager", "BaseProxy", "Token", "SharedMemoryManager"]
|
||||
|
||||
@@ -60,31 +61,58 @@ class ValueProxy(BaseProxy, Generic[_T]):
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
class DictProxy(BaseProxy, MutableMapping[_KT, _VT]):
|
||||
__builtins__: ClassVar[dict[str, Any]]
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, key: _KT, /) -> _VT: ...
|
||||
def __setitem__(self, key: _KT, value: _VT, /) -> None: ...
|
||||
def __delitem__(self, key: _KT, /) -> None: ...
|
||||
def __iter__(self) -> Iterator[_KT]: ...
|
||||
def copy(self) -> dict[_KT, _VT]: ...
|
||||
@overload # type: ignore[override]
|
||||
def get(self, key: _KT, /) -> _VT | None: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
def keys(self) -> list[_KT]: ... # type: ignore[override]
|
||||
def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override]
|
||||
def values(self) -> list[_VT]: ... # type: ignore[override]
|
||||
if sys.version_info >= (3, 13):
|
||||
def __class_getitem__(cls, args: Any, /) -> Any: ...
|
||||
if sys.version_info >= (3, 13):
|
||||
class _BaseDictProxy(BaseProxy, MutableMapping[_KT, _VT]):
|
||||
__builtins__: ClassVar[dict[str, Any]]
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, key: _KT, /) -> _VT: ...
|
||||
def __setitem__(self, key: _KT, value: _VT, /) -> None: ...
|
||||
def __delitem__(self, key: _KT, /) -> None: ...
|
||||
def __iter__(self) -> Iterator[_KT]: ...
|
||||
def copy(self) -> dict[_KT, _VT]: ...
|
||||
@overload # type: ignore[override]
|
||||
def get(self, key: _KT, /) -> _VT | None: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
def keys(self) -> list[_KT]: ... # type: ignore[override]
|
||||
def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override]
|
||||
def values(self) -> list[_VT]: ... # type: ignore[override]
|
||||
|
||||
class DictProxy(_BaseDictProxy[_KT, _VT]):
|
||||
def __class_getitem__(cls, args: Any, /) -> GenericAlias: ...
|
||||
|
||||
else:
|
||||
class DictProxy(BaseProxy, MutableMapping[_KT, _VT]):
|
||||
__builtins__: ClassVar[dict[str, Any]]
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, key: _KT, /) -> _VT: ...
|
||||
def __setitem__(self, key: _KT, value: _VT, /) -> None: ...
|
||||
def __delitem__(self, key: _KT, /) -> None: ...
|
||||
def __iter__(self) -> Iterator[_KT]: ...
|
||||
def copy(self) -> dict[_KT, _VT]: ...
|
||||
@overload # type: ignore[override]
|
||||
def get(self, key: _KT, /) -> _VT | None: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def get(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _VT, /) -> _VT: ...
|
||||
@overload
|
||||
def pop(self, key: _KT, default: _T, /) -> _VT | _T: ...
|
||||
def keys(self) -> list[_KT]: ... # type: ignore[override]
|
||||
def items(self) -> list[tuple[_KT, _VT]]: ... # type: ignore[override]
|
||||
def values(self) -> list[_VT]: ... # type: ignore[override]
|
||||
|
||||
class BaseListProxy(BaseProxy, MutableSequence[_T]):
|
||||
__builtins__: ClassVar[dict[str, Any]]
|
||||
@@ -156,7 +184,7 @@ class BaseManager:
|
||||
def get_server(self) -> Server: ...
|
||||
def connect(self) -> None: ...
|
||||
def start(self, initializer: Callable[..., object] | None = None, initargs: Iterable[Any] = ()) -> None: ...
|
||||
def shutdown(self) -> None: ... # only available after start() was called
|
||||
shutdown: _Finalize # only available after start() was called
|
||||
def join(self, timeout: float | None = None) -> None: ... # undocumented
|
||||
@property
|
||||
def address(self) -> Any: ...
|
||||
|
||||
@@ -23,7 +23,7 @@ def get_command_line(**kwds: Any) -> list[str]: ...
|
||||
def spawn_main(pipe_handle: int, parent_pid: int | None = None, tracker_fd: int | None = None) -> None: ...
|
||||
|
||||
# undocumented
|
||||
def _main(fd: int) -> Any: ...
|
||||
def _main(fd: int, parent_sentinel: int) -> int: ...
|
||||
def get_preparation_data(name: str) -> dict[str, Any]: ...
|
||||
|
||||
old_main_modules: list[ModuleType]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
from _queue import Empty as Empty, SimpleQueue as SimpleQueue
|
||||
from threading import Condition, Lock
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
@@ -11,7 +12,6 @@ if sys.version_info >= (3, 13):
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class Empty(Exception): ...
|
||||
class Full(Exception): ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
@@ -55,14 +55,3 @@ class PriorityQueue(Queue[_T]):
|
||||
|
||||
class LifoQueue(Queue[_T]):
|
||||
queue: list[_T]
|
||||
|
||||
class SimpleQueue(Generic[_T]):
|
||||
def __init__(self) -> None: ...
|
||||
def empty(self) -> bool: ...
|
||||
def get(self, block: bool = True, timeout: float | None = None) -> _T: ...
|
||||
def get_nowait(self) -> _T: ...
|
||||
def put(self, item: _T, block: bool = True, timeout: float | None = None) -> None: ...
|
||||
def put_nowait(self, item: _T) -> None: ...
|
||||
def qsize(self) -> int: ...
|
||||
if sys.version_info >= (3, 9):
|
||||
def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
from _typeshed import ReadableBuffer, WriteableBuffer
|
||||
from collections.abc import Iterator
|
||||
from typing import Any
|
||||
from _struct import *
|
||||
|
||||
__all__ = ["calcsize", "pack", "pack_into", "unpack", "unpack_from", "iter_unpack", "Struct", "error"]
|
||||
|
||||
class error(Exception): ...
|
||||
|
||||
def pack(fmt: str | bytes, /, *v: Any) -> bytes: ...
|
||||
def pack_into(fmt: str | bytes, buffer: WriteableBuffer, offset: int, /, *v: Any) -> None: ...
|
||||
def unpack(format: str | bytes, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ...
|
||||
def unpack_from(format: str | bytes, /, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ...
|
||||
def iter_unpack(format: str | bytes, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ...
|
||||
def calcsize(format: str | bytes, /) -> int: ...
|
||||
|
||||
class Struct:
|
||||
@property
|
||||
def format(self) -> str: ...
|
||||
@property
|
||||
def size(self) -> int: ...
|
||||
def __init__(self, format: str | bytes) -> None: ...
|
||||
def pack(self, *v: Any) -> bytes: ...
|
||||
def pack_into(self, buffer: WriteableBuffer, offset: int, *v: Any) -> None: ...
|
||||
def unpack(self, buffer: ReadableBuffer, /) -> tuple[Any, ...]: ...
|
||||
def unpack_from(self, buffer: ReadableBuffer, offset: int = 0) -> tuple[Any, ...]: ...
|
||||
def iter_unpack(self, buffer: ReadableBuffer, /) -> Iterator[tuple[Any, ...]]: ...
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import bz2
|
||||
import io
|
||||
import sys
|
||||
from _typeshed import StrOrBytesPath, StrPath
|
||||
from _typeshed import StrOrBytesPath, StrPath, SupportsRead
|
||||
from builtins import list as _list # aliases to avoid name clashes with fields named "type" or "list"
|
||||
from collections.abc import Callable, Iterable, Iterator, Mapping
|
||||
from gzip import _ReadableFileobj as _GzipReadableFileobj, _WritableFileobj as _GzipWritableFileobj
|
||||
@@ -481,7 +481,7 @@ class TarFile:
|
||||
*,
|
||||
filter: Callable[[TarInfo], TarInfo | None] | None = None,
|
||||
) -> None: ...
|
||||
def addfile(self, tarinfo: TarInfo, fileobj: IO[bytes] | None = None) -> None: ...
|
||||
def addfile(self, tarinfo: TarInfo, fileobj: SupportsRead[bytes] | None = None) -> None: ...
|
||||
def gettarinfo(
|
||||
self, name: StrOrBytesPath | None = None, arcname: str | None = None, fileobj: IO[bytes] | None = None
|
||||
) -> TarInfo: ...
|
||||
|
||||
@@ -6,6 +6,7 @@ use wasm_bindgen::prelude::*;
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
use red_knot_workspace::workspace::settings::Configuration;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
|
||||
use ruff_db::system::{
|
||||
@@ -110,14 +111,20 @@ impl Workspace {
|
||||
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<String>, Error> {
|
||||
let result = self.db.check_file(file_id.file).map_err(into_error)?;
|
||||
|
||||
Ok(result)
|
||||
Ok(result
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.display(&self.db).to_string())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Checks all open files
|
||||
pub fn check(&self) -> Result<Vec<String>, Error> {
|
||||
let result = self.db.check().map_err(into_error)?;
|
||||
|
||||
Ok(result)
|
||||
Ok(result
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.display(&self.db).to_string())
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Returns the parsed AST for `path`
|
||||
|
||||
@@ -19,6 +19,6 @@ fn check() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec!["/test.py:1:8: Cannot resolve import `random22`"]
|
||||
vec!["error[unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../../../ruff_python_parser/resources/invalid/statements/invalid_assignment_targets.py
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user