Compare commits
72 Commits
dcreager/f
...
0.11.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7b48510b5 | ||
|
|
9937064761 | ||
|
|
8d2c79276d | ||
|
|
0f47810768 | ||
|
|
eb1d2518c1 | ||
|
|
a45a0a92bd | ||
|
|
43bd043755 | ||
|
|
9a54ee3a1c | ||
|
|
25c3be51d2 | ||
|
|
e71f3ed2c5 | ||
|
|
ac6219ec38 | ||
|
|
e93fa7062c | ||
|
|
21fd28d713 | ||
|
|
a01f25107a | ||
|
|
48a85c4ed4 | ||
|
|
1796ca97d5 | ||
|
|
e897f37911 | ||
|
|
00e73dc331 | ||
|
|
7b6222700b | ||
|
|
bfc1650198 | ||
|
|
d5410ef9fe | ||
|
|
9db63fc58c | ||
|
|
61e73481fe | ||
|
|
e170fe493d | ||
|
|
e91e2f49db | ||
|
|
b537552927 | ||
|
|
5a719f2d60 | ||
|
|
e7f38fe74b | ||
|
|
624f5c6c22 | ||
|
|
8abf93f5fb | ||
|
|
5407249467 | ||
|
|
0a1f9d090e | ||
|
|
f9c7908bb7 | ||
|
|
99fa850e53 | ||
|
|
a241321735 | ||
|
|
b1b8ca3bcd | ||
|
|
3fae176345 | ||
|
|
f36262d970 | ||
|
|
e45f23b0ec | ||
|
|
aa46047649 | ||
|
|
f9da115fdc | ||
|
|
3872d57463 | ||
|
|
27ada26ddb | ||
|
|
810478f68b | ||
|
|
17f799424a | ||
|
|
c12640fea8 | ||
|
|
3796b13ea2 | ||
|
|
ad5a659f29 | ||
|
|
27a377f077 | ||
|
|
b8b624d890 | ||
|
|
6dc2d29966 | ||
|
|
890ba725d9 | ||
|
|
298f43f34e | ||
|
|
3b300559ab | ||
|
|
14f71ceb83 | ||
|
|
4775719abf | ||
|
|
6bdffc3cbf | ||
|
|
775815ef22 | ||
|
|
0299a52fb1 | ||
|
|
83d5ad8983 | ||
|
|
ae6fde152c | ||
|
|
d2b20f7367 | ||
|
|
38a3b056e3 | ||
|
|
37a0836bd2 | ||
|
|
f83295fe51 | ||
|
|
c4581788b2 | ||
|
|
2894aaa943 | ||
|
|
ed4866a00b | ||
|
|
9b5fe51b32 | ||
|
|
53ffe7143f | ||
|
|
21561000b1 | ||
|
|
9c0772d8f0 |
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -12,6 +12,12 @@ crates/ruff_python_parser/resources/invalid/re_lexing/line_continuation_windows_
|
||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_windows_eol.py text eol=crlf
|
||||
crates/ruff_python_parser/resources/invalid/re_lex_logical_token_mac_eol.py text eol=cr
|
||||
|
||||
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_CR.py text eol=cr
|
||||
crates/ruff_linter/resources/test/fixtures/ruff/RUF046_LF.py text eol=lf
|
||||
|
||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_CR.py text eol=cr
|
||||
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP018_LF.py text eol=lf
|
||||
|
||||
crates/ruff_python_parser/resources/inline linguist-generated=true
|
||||
|
||||
ruff.schema.json -diff linguist-generated=true text=auto eol=lf
|
||||
|
||||
1
.github/actionlint.yaml
vendored
1
.github/actionlint.yaml
vendored
@@ -6,5 +6,6 @@ self-hosted-runner:
|
||||
labels:
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-22.04-16
|
||||
- depot-ubuntu-22.04-32
|
||||
- github-windows-2025-x86_64-8
|
||||
- github-windows-2025-x86_64-16
|
||||
|
||||
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -346,7 +346,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
@@ -821,7 +821,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
|
||||
5
.github/workflows/mypy_primer.yaml
vendored
5
.github/workflows/mypy_primer.yaml
vendored
@@ -21,11 +21,12 @@ env:
|
||||
CARGO_NET_RETRY: 10
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
mypy_primer:
|
||||
name: Run mypy_primer
|
||||
runs-on: depot-ubuntu-22.04-16
|
||||
runs-on: depot-ubuntu-22.04-32
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
@@ -45,7 +46,7 @@ jobs:
|
||||
|
||||
- name: Install mypy_primer
|
||||
run: |
|
||||
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support-v5"
|
||||
uv tool install "git+https://github.com/hauntsaninja/mypy_primer@4c22d192a456e27badf85b3ea0f830707375d2b7"
|
||||
|
||||
- name: Run mypy_primer
|
||||
shell: bash
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
|
||||
|
||||
2
.github/workflows/publish-playground.yml
vendored
2
.github/workflows/publish-playground.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
|
||||
2
.github/workflows/publish-wasm.yml
vendored
2
.github/workflows/publish-wasm.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
|
||||
mv /tmp/package.json crates/ruff_wasm/pkg
|
||||
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
|
||||
- uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
node-version: 20
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
19
.github/workflows/release.yml
vendored
19
.github/workflows/release.yml
vendored
@@ -40,6 +40,7 @@ permissions:
|
||||
# If there's a prerelease-style suffix to the version, then the release(s)
|
||||
# will be marked as a prerelease.
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -60,7 +61,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -68,9 +69,9 @@ jobs:
|
||||
# we specify bash to get pipefail; it guards against the `curl` command
|
||||
# failing. otherwise `sh` won't catch that `curl` returned non-0
|
||||
shell: bash
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4-prerelease.1/cargo-dist-installer.sh | sh"
|
||||
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.4/cargo-dist-installer.sh | sh"
|
||||
- name: Cache dist
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: cargo-dist-cache
|
||||
path: ~/.cargo/bin/dist
|
||||
@@ -86,7 +87,7 @@ jobs:
|
||||
cat plan-dist-manifest.json
|
||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: artifacts-plan-dist-manifest
|
||||
path: plan-dist-manifest.json
|
||||
@@ -123,7 +124,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -153,7 +154,7 @@ jobs:
|
||||
|
||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||
- name: "Upload artifacts"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
name: artifacts-build-global
|
||||
path: |
|
||||
@@ -174,7 +175,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -200,7 +201,7 @@ jobs:
|
||||
cat dist-manifest.json
|
||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: "Upload dist-manifest.json"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
|
||||
with:
|
||||
# Overwrite the previous copy
|
||||
name: artifacts-dist-manifest
|
||||
@@ -250,7 +251,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
||||
# Changelog
|
||||
|
||||
## 0.11.7
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Apply auto fixes to cases where the names have changed in Airflow 3 (`AIR301`) ([#17355](https://github.com/astral-sh/ruff/pull/17355))
|
||||
- \[`perflint`\] Implement fix for `manual-dict-comprehension` (`PERF403`) ([#16719](https://github.com/astral-sh/ruff/pull/16719))
|
||||
- [syntax-errors] Make duplicate parameter names a semantic error ([#17131](https://github.com/astral-sh/ruff/pull/17131))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`airflow`\] Fix typos in provider package names (`AIR302`, `AIR312`) ([#17574](https://github.com/astral-sh/ruff/pull/17574))
|
||||
- \[`flake8-type-checking`\] Visit keyword arguments in checks involving `typing.cast`/`typing.NewType` arguments ([#17538](https://github.com/astral-sh/ruff/pull/17538))
|
||||
- \[`pyupgrade`\] Preserve parenthesis when fixing native literals containing newlines (`UP018`) ([#17220](https://github.com/astral-sh/ruff/pull/17220))
|
||||
- \[`refurb`\] Mark the `FURB161` fix unsafe except for integers and booleans ([#17240](https://github.com/astral-sh/ruff/pull/17240))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`perflint`\] Allow list function calls to be replaced with a comprehension (`PERF401`) ([#17519](https://github.com/astral-sh/ruff/pull/17519))
|
||||
- \[`pycodestyle`\] Auto-fix redundant boolean comparison (`E712`) ([#17090](https://github.com/astral-sh/ruff/pull/17090))
|
||||
- \[`pylint`\] make fix unsafe if delete comments (`PLR1730`) ([#17459](https://github.com/astral-sh/ruff/pull/17459))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add fix safety sections to docs for several rules ([#17410](https://github.com/astral-sh/ruff/pull/17410),[#17440](https://github.com/astral-sh/ruff/pull/17440),[#17441](https://github.com/astral-sh/ruff/pull/17441),[#17443](https://github.com/astral-sh/ruff/pull/17443),[#17444](https://github.com/astral-sh/ruff/pull/17444))
|
||||
|
||||
## 0.11.6
|
||||
|
||||
### Preview features
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2772,7 +2772,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3007,7 +3007,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3333,7 +3333,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -231,6 +231,10 @@ unused_peekable = "warn"
|
||||
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
|
||||
large_stack_arrays = "allow"
|
||||
|
||||
# Salsa generates functions with parameters for each field of a `salsa::interned` struct.
|
||||
# If we don't allow this, we get warnings for structs with too many fields.
|
||||
too_many_arguments = "allow"
|
||||
|
||||
[profile.release]
|
||||
# Note that we set these explicitly, and these values
|
||||
# were chosen based on a trade-off between compile times
|
||||
@@ -272,7 +276,9 @@ inherits = "release"
|
||||
# Config for 'dist'
|
||||
[workspace.metadata.dist]
|
||||
# The preferred dist version to use in CI (Cargo.toml SemVer syntax)
|
||||
cargo-dist-version = "0.28.4-prerelease.1"
|
||||
cargo-dist-version = "0.28.4"
|
||||
# Make distability of apps opt-in instead of opt-out
|
||||
dist = false
|
||||
# CI backends to support
|
||||
ci = "github"
|
||||
# The installers to generate for each app
|
||||
@@ -306,7 +312,7 @@ auto-includes = false
|
||||
# Whether dist should create a Github Release or use an existing draft
|
||||
create-release = true
|
||||
# Which actions to run on pull requests
|
||||
pr-run-mode = "skip"
|
||||
pr-run-mode = "plan"
|
||||
# Whether CI should trigger releases with dispatches instead of tag pushes
|
||||
dispatch-releases = true
|
||||
# Which phase dist should use to create the GitHub release
|
||||
@@ -334,7 +340,7 @@ install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"]
|
||||
global = "depot-ubuntu-latest-4"
|
||||
|
||||
[workspace.metadata.dist.github-action-commits]
|
||||
"actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4
|
||||
"actions/upload-artifact" = "ea165f8d65b6e75b540449e92b4886f43607fa02" # v4.6.2
|
||||
"actions/checkout" = "85e6279cec87321a52edac9c87bce653a07cf6c2" # v4
|
||||
"actions/upload-artifact" = "6027e3dd177782cd8ab9af838c04fd81a07f1d47" # v4.6.2
|
||||
"actions/download-artifact" = "95815c38cf2ff2164869cbab79da8d1f422bc89e" # v4.2.1
|
||||
"actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3
|
||||
|
||||
@@ -149,8 +149,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.11.6/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.6/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.11.7/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.11.7/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -183,7 +183,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.11.6
|
||||
rev: v0.11.7
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
## Basics
|
||||
|
||||
For now, we use our own [fork of mypy primer]. It can be run using `uvx --from "…" mypy_primer`. For example, to see the help message, run:
|
||||
`mypy_primer` can be run using `uvx --from "…" mypy_primer`. For example, to see the help message, run:
|
||||
|
||||
```sh
|
||||
uvx --from "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support" mypy_primer -h
|
||||
uvx --from "git+https://github.com/hauntsaninja/mypy_primer" mypy_primer -h
|
||||
```
|
||||
|
||||
Alternatively, you can install the forked version of `mypy_primer` using:
|
||||
|
||||
```sh
|
||||
uv tool install "git+https://github.com/astral-sh/mypy_primer.git@add-red-knot-support"
|
||||
uv tool install "git+https://github.com/hauntsaninja/mypy_primer"
|
||||
```
|
||||
|
||||
and then run it using `uvx mypy_primer` or just `mypy_primer`, if your `PATH` is set up accordingly (see: [Tool executables]).
|
||||
@@ -56,6 +56,5 @@ mypy_primer --repo /path/to/ruff --old origin/main --new my/local-branch …
|
||||
|
||||
Note that you might need to clean up `/tmp/mypy_primer` in order for this to work correctly.
|
||||
|
||||
[fork of mypy primer]: https://github.com/astral-sh/mypy_primer/tree/add-red-knot-support
|
||||
[full list of ecosystem projects]: https://github.com/astral-sh/mypy_primer/blob/add-red-knot-support/mypy_primer/projects.py
|
||||
[full list of ecosystem projects]: https://github.com/hauntsaninja/mypy_primer/blob/master/mypy_primer/projects.py
|
||||
[tool executables]: https://docs.astral.sh/uv/concepts/tools/#tool-executables
|
||||
|
||||
@@ -32,12 +32,12 @@ fn config_override_python_version() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:unresolved-attribute
|
||||
error: lint:unresolved-attribute: Type `<module 'sys'>` has no attribute `last_exc`
|
||||
--> <temp_dir>/test.py:5:7
|
||||
|
|
||||
4 | # Access `sys.last_exc` that was only added in Python 3.12
|
||||
5 | print(sys.last_exc)
|
||||
| ^^^^^^^^^^^^ Type `<module 'sys'>` has no attribute `last_exc`
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -165,11 +165,11 @@ fn cli_arguments_are_relative_to_the_current_directory() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `utils`
|
||||
--> <temp_dir>/child/test.py:2:6
|
||||
|
|
||||
2 | from utils import add
|
||||
| ^^^^^ Cannot resolve import `utils`
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | stat = add(10, 15)
|
||||
|
|
||||
@@ -265,22 +265,22 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:division-by-zero
|
||||
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
warning: lint:possibly-unresolved-reference
|
||||
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> <temp_dir>/test.py:7:7
|
||||
|
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x) # possibly-unresolved-reference
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -301,11 +301,11 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:division-by-zero
|
||||
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
@@ -341,33 +341,33 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `does_not_exit`
|
||||
--> <temp_dir>/test.py:2:8
|
||||
|
|
||||
2 | import does_not_exit
|
||||
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
|
||||
| ^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
|
|
||||
|
||||
error: lint:division-by-zero
|
||||
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:4:5
|
||||
|
|
||||
2 | import does_not_exit
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
warning: lint:possibly-unresolved-reference
|
||||
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> <temp_dir>/test.py:9:7
|
||||
|
|
||||
7 | x = a
|
||||
8 |
|
||||
9 | print(x) # possibly-unresolved-reference
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 3 diagnostics
|
||||
@@ -388,22 +388,22 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-import
|
||||
warning: lint:unresolved-import: Cannot resolve import `does_not_exit`
|
||||
--> <temp_dir>/test.py:2:8
|
||||
|
|
||||
2 | import does_not_exit
|
||||
| ^^^^^^^^^^^^^ Cannot resolve import `does_not_exit`
|
||||
| ^^^^^^^^^^^^^
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
|
|
||||
|
||||
warning: lint:division-by-zero
|
||||
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:4:5
|
||||
|
|
||||
2 | import does_not_exit
|
||||
3 |
|
||||
4 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
5 |
|
||||
6 | for a in range(0, int(y)):
|
||||
|
|
||||
@@ -439,22 +439,22 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:division-by-zero
|
||||
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
warning: lint:possibly-unresolved-reference
|
||||
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> <temp_dir>/test.py:7:7
|
||||
|
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x) # possibly-unresolved-reference
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -476,11 +476,11 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:division-by-zero
|
||||
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/test.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
@@ -555,11 +555,11 @@ fn exit_code_only_warnings() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -638,11 +638,11 @@ fn exit_code_no_errors_but_error_on_warning_is_true() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -670,11 +670,11 @@ fn exit_code_no_errors_but_error_on_warning_is_enabled_in_configuration() -> any
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:1:7
|
||||
|
|
||||
1 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -699,20 +699,20 @@ fn exit_code_both_warnings_and_errors() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
error: lint:non-subscriptable
|
||||
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
--> <temp_dir>/test.py:3:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -737,20 +737,20 @@ fn exit_code_both_warnings_and_errors_and_error_on_warning_is_true() -> anyhow::
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
error: lint:non-subscriptable
|
||||
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
--> <temp_dir>/test.py:3:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -775,20 +775,20 @@ fn exit_code_exit_zero_is_true() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:unresolved-reference
|
||||
warning: lint:unresolved-reference: Name `x` used when not defined
|
||||
--> <temp_dir>/test.py:2:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
| ^ Name `x` used when not defined
|
||||
| ^
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
|
|
||||
|
||||
error: lint:non-subscriptable
|
||||
error: lint:non-subscriptable: Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
--> <temp_dir>/test.py:3:7
|
||||
|
|
||||
2 | print(x) # [unresolved-reference]
|
||||
3 | print(4[1]) # [non-subscriptable]
|
||||
| ^ Cannot subscript object of type `Literal[4]` with no `__getitem__` method
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -835,22 +835,22 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
warning: lint:division-by-zero
|
||||
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
warning: lint:possibly-unresolved-reference
|
||||
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> <temp_dir>/project/main.py:7:7
|
||||
|
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x)
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -877,22 +877,22 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
warning: lint:division-by-zero
|
||||
warning: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
error: lint:possibly-unresolved-reference
|
||||
error: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> <temp_dir>/project/main.py:7:7
|
||||
|
|
||||
5 | x = a
|
||||
6 |
|
||||
7 | print(x)
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -935,25 +935,25 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
|
||||
--> <temp_dir>/project/tests/test_main.py:2:8
|
||||
|
|
||||
2 | import does_not_exist # error: unresolved-import
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
error: lint:division-by-zero
|
||||
error: lint:division-by-zero: Cannot divide object of type `Literal[4]` by zero
|
||||
--> <temp_dir>/project/main.py:2:5
|
||||
|
|
||||
2 | y = 4 / 0 # error: division-by-zero
|
||||
| ^^^^^ Cannot divide object of type `Literal[4]` by zero
|
||||
| ^^^^^
|
||||
|
|
||||
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `main2`
|
||||
--> <temp_dir>/project/other.py:2:6
|
||||
|
|
||||
2 | from main2 import z # error: unresolved-import
|
||||
| ^^^^^ Cannot resolve import `main2`
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | print(z)
|
||||
|
|
||||
@@ -972,18 +972,18 @@ fn check_specific_paths() -> anyhow::Result<()> {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
|
||||
--> <temp_dir>/project/tests/test_main.py:2:8
|
||||
|
|
||||
2 | import does_not_exist # error: unresolved-import
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `main2`
|
||||
--> <temp_dir>/project/other.py:2:6
|
||||
|
|
||||
2 | from main2 import z # error: unresolved-import
|
||||
| ^^^^^ Cannot resolve import `main2`
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | print(z)
|
||||
|
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
def foo(a: foo()):
|
||||
pass
|
||||
@@ -314,20 +314,23 @@ impl Project {
|
||||
/// * It has a [`SystemPath`] and belongs to a package's `src` files
|
||||
/// * It has a [`SystemVirtualPath`](ruff_db::system::SystemVirtualPath)
|
||||
pub fn is_file_open(self, db: &dyn Db, file: File) -> bool {
|
||||
let path = file.path(db);
|
||||
|
||||
// Try to return early to avoid adding a dependency on `open_files` or `file_set` which
|
||||
// both have a durability of `LOW`.
|
||||
if path.is_vendored_path() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(open_files) = self.open_files(db) {
|
||||
open_files.contains(&file)
|
||||
} else if file.path(db).is_system_path() {
|
||||
self.contains_file(db, file)
|
||||
self.files(db).contains(&file)
|
||||
} else {
|
||||
file.path(db).is_system_virtual_path()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if `file` is a first-party file part of this package.
|
||||
pub fn contains_file(self, db: &dyn Db, file: File) -> bool {
|
||||
self.files(db).contains(&file)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self, db))]
|
||||
pub fn remove_file(self, db: &mut dyn Db, file: File) {
|
||||
tracing::debug!(
|
||||
|
||||
@@ -50,10 +50,9 @@ y: Any = "not an Any" # error: [invalid-assignment]
|
||||
|
||||
The spec allows you to define subclasses of `Any`.
|
||||
|
||||
TODO: Handle assignments correctly. `Subclass` has an unknown superclass, which might be `int`. The
|
||||
assignment to `x` should not be allowed, even when the unknown superclass is `int`. The assignment
|
||||
to `y` should be allowed, since `Subclass` might have `int` as a superclass, and is therefore
|
||||
assignable to `int`.
|
||||
`Subclass` has an unknown superclass, which might be `int`. The assignment to `x` should not be
|
||||
allowed, even when the unknown superclass is `int`. The assignment to `y` should be allowed, since
|
||||
`Subclass` might have `int` as a superclass, and is therefore assignable to `int`.
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
@@ -63,13 +62,33 @@ class Subclass(Any): ...
|
||||
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
|
||||
|
||||
x: Subclass = 1 # error: [invalid-assignment]
|
||||
# TODO: no diagnostic
|
||||
y: int = Subclass() # error: [invalid-assignment]
|
||||
y: int = Subclass()
|
||||
|
||||
def _(s: Subclass):
|
||||
reveal_type(s) # revealed: Subclass
|
||||
```
|
||||
|
||||
`Subclass` should not be assignable to a final class though, because `Subclass` could not possibly
|
||||
be a subclass of `FinalClass`:
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
f: FinalClass = Subclass() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
A use case where this comes up is with mocking libraries, where the mock object should be assignable
|
||||
to any type:
|
||||
|
||||
```py
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
x: int = MagicMock()
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
`Any` cannot be parameterized:
|
||||
|
||||
@@ -56,40 +56,41 @@ def _(
|
||||
def bar() -> None:
|
||||
return None
|
||||
|
||||
def _(
|
||||
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
|
||||
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
|
||||
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
|
||||
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
|
||||
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
|
||||
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
|
||||
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
||||
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
||||
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
|
||||
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
||||
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
|
||||
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: int | Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
reveal_type(h) # revealed: Unknown
|
||||
reveal_type(i) # revealed: Unknown
|
||||
reveal_type(j) # revealed: Unknown
|
||||
reveal_type(k) # revealed: Unknown
|
||||
reveal_type(p) # revealed: Unknown
|
||||
reveal_type(q) # revealed: int | Unknown
|
||||
reveal_type(r) # revealed: @Todo(unknown type subscript)
|
||||
async def outer(): # avoid unrelated syntax errors on yield, yield from, and await
|
||||
def _(
|
||||
a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
|
||||
b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions"
|
||||
c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions"
|
||||
d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression"
|
||||
e: int | b"foo", # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression"
|
||||
f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions"
|
||||
h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions"
|
||||
j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions"
|
||||
k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions"
|
||||
l: await 1, # error: [invalid-type-form] "`await` expressions are not allowed in type expressions"
|
||||
m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
|
||||
n: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
|
||||
o: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions"
|
||||
p: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions"
|
||||
q: int | f"foo", # error: [invalid-type-form] "F-strings are not allowed in type expressions"
|
||||
r: [1, 2, 3][1:2], # error: [invalid-type-form] "Slices are not allowed in type expressions"
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: int | Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
reveal_type(h) # revealed: Unknown
|
||||
reveal_type(i) # revealed: Unknown
|
||||
reveal_type(j) # revealed: Unknown
|
||||
reveal_type(k) # revealed: Unknown
|
||||
reveal_type(p) # revealed: Unknown
|
||||
reveal_type(q) # revealed: int | Unknown
|
||||
reveal_type(r) # revealed: @Todo(unknown type subscript)
|
||||
```
|
||||
|
||||
## Invalid Collection based AST nodes
|
||||
|
||||
@@ -106,13 +106,13 @@ reveal_type(ChainMapSubclass.__mro__)
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
|
||||
# TODO: Should be (CounterSubclass, Counter, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[CounterSubclass], Literal[Counter], @Todo(GenericAlias instance), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(CounterSubclass.__mro__)
|
||||
|
||||
class DefaultDictSubclass(typing.DefaultDict): ...
|
||||
|
||||
# TODO: Should be (DefaultDictSubclass, defaultdict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[DefaultDictSubclass], Literal[defaultdict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
@@ -124,6 +124,6 @@ reveal_type(DequeSubclass.__mro__)
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
# TODO: Should be (OrderedDictSubclass, OrderedDict, dict, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], Unknown, Literal[object]]
|
||||
# revealed: tuple[Literal[OrderedDictSubclass], Literal[OrderedDict], @Todo(GenericAlias instance), Literal[object]]
|
||||
reveal_type(OrderedDictSubclass.__mro__)
|
||||
```
|
||||
|
||||
@@ -292,3 +292,66 @@ reveal_type(a) # revealed: Unknown
|
||||
# Modifications allowed in this case:
|
||||
a = None
|
||||
```
|
||||
|
||||
## In stub files
|
||||
|
||||
In stub files, we have a minor modification to the rules above: we do not union with `Unknown` for
|
||||
undeclared symbols.
|
||||
|
||||
### Undeclared and bound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
MyInt = int
|
||||
|
||||
class C:
|
||||
MyStr = str
|
||||
```
|
||||
|
||||
```py
|
||||
from mod import MyInt, C
|
||||
|
||||
reveal_type(MyInt) # revealed: Literal[int]
|
||||
reveal_type(C.MyStr) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
### Undeclared and possibly unbound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
def flag() -> bool:
|
||||
return True
|
||||
|
||||
if flag():
|
||||
MyInt = int
|
||||
|
||||
class C:
|
||||
MyStr = str
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [possibly-unbound-import]
|
||||
# error: [possibly-unbound-import]
|
||||
from mod import MyInt, C
|
||||
|
||||
reveal_type(MyInt) # revealed: Literal[int]
|
||||
reveal_type(C.MyStr) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
### Undeclared and unbound
|
||||
|
||||
`mod.pyi`:
|
||||
|
||||
```pyi
|
||||
if False:
|
||||
MyInt = int
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [unresolved-import]
|
||||
from mod import MyInt
|
||||
|
||||
reveal_type(MyInt) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -162,6 +162,44 @@ def _(flag: bool):
|
||||
reveal_type(f("string")) # revealed: Literal["string", "'string'"]
|
||||
```
|
||||
|
||||
## Unions with literals and negations
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
from knot_extensions import Not, AlwaysFalsy, static_assert, is_subtype_of, is_assignable_to
|
||||
|
||||
static_assert(is_subtype_of(Literal["a", ""], Literal["a", ""] | Not[AlwaysFalsy]))
|
||||
static_assert(is_subtype_of(Not[AlwaysFalsy], Literal["", "a"] | Not[AlwaysFalsy]))
|
||||
static_assert(is_subtype_of(Literal["a", ""], Not[AlwaysFalsy] | Literal["a", ""]))
|
||||
static_assert(is_subtype_of(Not[AlwaysFalsy], Not[AlwaysFalsy] | Literal["a", ""]))
|
||||
|
||||
static_assert(is_subtype_of(Literal["a", ""], Literal["a", ""] | Not[Literal[""]]))
|
||||
static_assert(is_subtype_of(Not[Literal[""]], Literal["a", ""] | Not[Literal[""]]))
|
||||
static_assert(is_subtype_of(Literal["a", ""], Not[Literal[""]] | Literal["a", ""]))
|
||||
static_assert(is_subtype_of(Not[Literal[""]], Not[Literal[""]] | Literal["a", ""]))
|
||||
|
||||
def _(
|
||||
a: Literal["a", ""] | Not[AlwaysFalsy],
|
||||
b: Literal["a", ""] | Not[Literal[""]],
|
||||
c: Literal[""] | Not[Literal[""]],
|
||||
d: Not[Literal[""]] | Literal[""],
|
||||
e: Literal["a"] | Not[Literal["a"]],
|
||||
f: Literal[b"b"] | Not[Literal[b"b"]],
|
||||
g: Not[Literal[b"b"]] | Literal[b"b"],
|
||||
h: Literal[42] | Not[Literal[42]],
|
||||
i: Not[Literal[42]] | Literal[42],
|
||||
):
|
||||
reveal_type(a) # revealed: Literal[""] | ~AlwaysFalsy
|
||||
reveal_type(b) # revealed: object
|
||||
reveal_type(c) # revealed: object
|
||||
reveal_type(d) # revealed: object
|
||||
reveal_type(e) # revealed: object
|
||||
reveal_type(f) # revealed: object
|
||||
reveal_type(g) # revealed: object
|
||||
reveal_type(h) # revealed: object
|
||||
reveal_type(i) # revealed: object
|
||||
```
|
||||
|
||||
## Cannot use an argument as both a value and a type form
|
||||
|
||||
```py
|
||||
|
||||
@@ -13,7 +13,7 @@ reveal_type(1 is not 1) # revealed: bool
|
||||
reveal_type(1 is 2) # revealed: Literal[False]
|
||||
reveal_type(1 is not 7) # revealed: Literal[True]
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `int` and `str`, in comparing `Literal[1]` with `Literal[""]`"
|
||||
reveal_type(1 <= "" and 0 < 1) # revealed: Unknown & ~AlwaysTruthy | Literal[True]
|
||||
reveal_type(1 <= "" and 0 < 1) # revealed: (Unknown & ~AlwaysTruthy) | Literal[True]
|
||||
```
|
||||
|
||||
## Integer instance
|
||||
|
||||
@@ -37,7 +37,7 @@ class C:
|
||||
return self
|
||||
|
||||
x = A() < B() < C()
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B
|
||||
reveal_type(x) # revealed: (A & ~AlwaysTruthy) | B
|
||||
|
||||
y = 0 < 1 < A() < 3
|
||||
reveal_type(y) # revealed: Literal[False] | A
|
||||
|
||||
@@ -127,8 +127,9 @@ class AsyncIterable:
|
||||
def __aiter__(self) -> AsyncIterator:
|
||||
return AsyncIterator()
|
||||
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in AsyncIterable()]
|
||||
async def _():
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in AsyncIterable()]
|
||||
```
|
||||
|
||||
### Invalid async comprehension
|
||||
@@ -145,6 +146,7 @@ class Iterable:
|
||||
def __iter__(self) -> Iterator:
|
||||
return Iterator()
|
||||
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in Iterable()]
|
||||
async def _():
|
||||
# revealed: @Todo(async iterables/iterators)
|
||||
[reveal_type(x) async for x in Iterable()]
|
||||
```
|
||||
|
||||
@@ -42,6 +42,6 @@ def _(flag: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
3 if NotBoolable() else 4
|
||||
```
|
||||
|
||||
@@ -154,10 +154,10 @@ def _(flag: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
if NotBoolable():
|
||||
...
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
elif NotBoolable():
|
||||
...
|
||||
```
|
||||
|
||||
@@ -292,7 +292,7 @@ class NotBoolable:
|
||||
def _(target: int, flag: NotBoolable):
|
||||
y = 1
|
||||
match target:
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
case 1 if flag:
|
||||
y = 2
|
||||
case 2:
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
# `typing.dataclass_transform`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
`dataclass_transform` is a decorator that can be used to let type checkers know that a function,
|
||||
class, or metaclass is a `dataclass`-like construct.
|
||||
|
||||
## Basic example
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def my_dataclass[T](cls: type[T]) -> type[T]:
|
||||
# modify cls
|
||||
return cls
|
||||
|
||||
@my_dataclass
|
||||
class Person:
|
||||
name: str
|
||||
age: int | None = None
|
||||
|
||||
Person("Alice", 20)
|
||||
Person("Bob", None)
|
||||
Person("Bob")
|
||||
|
||||
# error: [missing-argument]
|
||||
Person()
|
||||
```
|
||||
|
||||
## Decorating decorators that take parameters themselves
|
||||
|
||||
If we want our `dataclass`-like decorator to also take parameters, that is also possible:
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable
|
||||
|
||||
@dataclass_transform()
|
||||
def versioned_class[T](*, version: int = 1):
|
||||
def decorator(cls):
|
||||
# modify cls
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
@versioned_class(version=2)
|
||||
class Person:
|
||||
name: str
|
||||
age: int | None = None
|
||||
|
||||
Person("Alice", 20)
|
||||
|
||||
# error: [missing-argument]
|
||||
Person()
|
||||
```
|
||||
|
||||
We properly type-check the arguments to the decorator:
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, Callable
|
||||
|
||||
# error: [invalid-argument-type]
|
||||
@versioned_class(version="a string")
|
||||
class C:
|
||||
name: str
|
||||
```
|
||||
|
||||
## Types of decorators
|
||||
|
||||
The examples from this section are straight from the Python documentation on
|
||||
[`typing.dataclass_transform`].
|
||||
|
||||
### Decorating a decorator function
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def create_model[T](cls: type[T]) -> type[T]:
|
||||
...
|
||||
return cls
|
||||
|
||||
@create_model
|
||||
class CustomerModel:
|
||||
id: int
|
||||
name: str
|
||||
|
||||
CustomerModel(id=1, name="Test")
|
||||
```
|
||||
|
||||
### Decorating a metaclass
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelMeta(type): ...
|
||||
|
||||
class ModelBase(metaclass=ModelMeta): ...
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
CustomerModel(id=1, name="Test")
|
||||
|
||||
# error: [missing-argument]
|
||||
CustomerModel()
|
||||
```
|
||||
|
||||
### Decorating a base class
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
class ModelBase: ...
|
||||
|
||||
class CustomerModel(ModelBase):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
# TODO: this is not supported yet
|
||||
# error: [unknown-argument]
|
||||
# error: [unknown-argument]
|
||||
CustomerModel(id=1, name="Test")
|
||||
```
|
||||
|
||||
## Arguments to `dataclass_transform`
|
||||
|
||||
### `eq_default`
|
||||
|
||||
`eq=True/False` does not have a observable effect (apart from a minor change regarding whether
|
||||
`other` is positional-only or not, which is not modelled at the moment).
|
||||
|
||||
### `order_default`
|
||||
|
||||
The `order_default` argument controls whether methods such as `__lt__` are generated by default.
|
||||
This can be overwritten using the `order` argument to the custom decorator:
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform
|
||||
|
||||
@dataclass_transform()
|
||||
def normal(*, order: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
@dataclass_transform(order_default=False)
|
||||
def order_default_false(*, order: bool = False):
|
||||
raise NotImplementedError
|
||||
|
||||
@dataclass_transform(order_default=True)
|
||||
def order_default_true(*, order: bool = True):
|
||||
raise NotImplementedError
|
||||
|
||||
@normal
|
||||
class Normal:
|
||||
inner: int
|
||||
|
||||
Normal(1) < Normal(2) # error: [unsupported-operator]
|
||||
|
||||
@normal(order=True)
|
||||
class NormalOverwritten:
|
||||
inner: int
|
||||
|
||||
NormalOverwritten(1) < NormalOverwritten(2)
|
||||
|
||||
@order_default_false
|
||||
class OrderFalse:
|
||||
inner: int
|
||||
|
||||
OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
|
||||
|
||||
@order_default_false(order=True)
|
||||
class OrderFalseOverwritten:
|
||||
inner: int
|
||||
|
||||
OrderFalseOverwritten(1) < OrderFalseOverwritten(2)
|
||||
|
||||
@order_default_true
|
||||
class OrderTrue:
|
||||
inner: int
|
||||
|
||||
OrderTrue(1) < OrderTrue(2)
|
||||
|
||||
@order_default_true(order=False)
|
||||
class OrderTrueOverwritten:
|
||||
inner: int
|
||||
|
||||
# error: [unsupported-operator]
|
||||
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
|
||||
```
|
||||
|
||||
### `kw_only_default`
|
||||
|
||||
To do
|
||||
|
||||
### `field_specifiers`
|
||||
|
||||
To do
|
||||
|
||||
## Overloaded dataclass-like decorators
|
||||
|
||||
In the case of an overloaded decorator, the `dataclass_transform` decorator can be applied to the
|
||||
implementation, or to *one* of the overloads.
|
||||
|
||||
### Applying `dataclass_transform` to the implementation
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, TypeVar, Callable, overload
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
@overload
|
||||
def versioned_class(
|
||||
cls: T,
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> T: ...
|
||||
@overload
|
||||
def versioned_class(
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> Callable[[T], T]: ...
|
||||
@dataclass_transform()
|
||||
def versioned_class(
|
||||
cls: T | None = None,
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> T | Callable[[T], T]:
|
||||
raise NotImplementedError
|
||||
|
||||
@versioned_class
|
||||
class D1:
|
||||
x: str
|
||||
|
||||
@versioned_class(version=2)
|
||||
class D2:
|
||||
x: str
|
||||
|
||||
D1("a")
|
||||
D2("a")
|
||||
|
||||
D1(1.2) # error: [invalid-argument-type]
|
||||
D2(1.2) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
### Applying `dataclass_transform` to an overload
|
||||
|
||||
```py
|
||||
from typing_extensions import dataclass_transform, TypeVar, Callable, overload
|
||||
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
@overload
|
||||
@dataclass_transform()
|
||||
def versioned_class(
|
||||
cls: T,
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> T: ...
|
||||
@overload
|
||||
def versioned_class(
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> Callable[[T], T]: ...
|
||||
def versioned_class(
|
||||
cls: T | None = None,
|
||||
*,
|
||||
version: int = 1,
|
||||
) -> T | Callable[[T], T]:
|
||||
raise NotImplementedError
|
||||
|
||||
@versioned_class
|
||||
class D1:
|
||||
x: str
|
||||
|
||||
@versioned_class(version=2)
|
||||
class D2:
|
||||
x: str
|
||||
|
||||
# TODO: these should not be errors
|
||||
D1("a") # error: [too-many-positional-arguments]
|
||||
D2("a") # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: these should be invalid-argument-type errors
|
||||
D1(1.2) # error: [too-many-positional-arguments]
|
||||
D2(1.2) # error: [too-many-positional-arguments]
|
||||
```
|
||||
|
||||
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
|
||||
@@ -689,7 +689,7 @@ from dataclasses import dataclass
|
||||
|
||||
dataclass_with_order = dataclass(order=True)
|
||||
|
||||
reveal_type(dataclass_with_order) # revealed: <decorator produced by dataclasses.dataclass>
|
||||
reveal_type(dataclass_with_order) # revealed: <decorator produced by dataclass-like function>
|
||||
|
||||
@dataclass_with_order
|
||||
class C:
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
# Semantic syntax error diagnostics
|
||||
|
||||
## `async` comprehensions in synchronous comprehensions
|
||||
|
||||
### Python 3.10
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Before Python 3.11, `async` comprehensions could not be used within outer sync comprehensions, even
|
||||
within an `async` function ([CPython issue](https://github.com/python/cpython/issues/77527)):
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
async def f():
|
||||
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
|
||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
```
|
||||
|
||||
If all of the comprehensions are `async`, on the other hand, the code was still valid:
|
||||
|
||||
```py
|
||||
async def test():
|
||||
return [[x async for x in elements(n)] async for n in range(3)]
|
||||
```
|
||||
|
||||
These are a couple of tricky but valid cases to check that nested scope handling is wired up
|
||||
correctly in the `SemanticSyntaxContext` trait:
|
||||
|
||||
```py
|
||||
async def f():
|
||||
[x for x in [1]] and [x async for x in elements(1)]
|
||||
|
||||
async def f():
|
||||
def g():
|
||||
pass
|
||||
[x async for x in elements(1)]
|
||||
```
|
||||
|
||||
### Python 3.11
|
||||
|
||||
All of these same examples are valid after Python 3.11:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
```py
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
async def f():
|
||||
return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
```
|
||||
|
||||
## Late `__future__` import
|
||||
|
||||
```py
|
||||
from collections import namedtuple
|
||||
|
||||
# error: [invalid-syntax] "__future__ imports must be at the top of the file"
|
||||
from __future__ import print_function
|
||||
```
|
||||
|
||||
## Invalid annotation
|
||||
|
||||
This one might be a bit redundant with the `invalid-type-form` error.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
|
||||
# error: [invalid-syntax] "named expression cannot be used within a type annotation"
|
||||
def f() -> (y := 3): ...
|
||||
```
|
||||
|
||||
## Duplicate `match` key
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
match 2:
|
||||
# error: [invalid-syntax] "mapping pattern checks duplicate key `"x"`"
|
||||
case {"x": 1, "x": 2}:
|
||||
...
|
||||
```
|
||||
|
||||
## `return`, `yield`, `yield from`, and `await` outside function
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax] "`return` statement outside of a function"
|
||||
return
|
||||
|
||||
# error: [invalid-syntax] "`yield` statement outside of a function"
|
||||
yield
|
||||
|
||||
# error: [invalid-syntax] "`yield from` statement outside of a function"
|
||||
yield from []
|
||||
|
||||
# error: [invalid-syntax] "`await` statement outside of a function"
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
|
||||
def f():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
```
|
||||
|
||||
Generators are evaluated lazily, so `await` is allowed, even outside of a function.
|
||||
|
||||
```py
|
||||
async def g():
|
||||
yield 1
|
||||
|
||||
(x async for x in g())
|
||||
```
|
||||
|
||||
## `await` outside async function
|
||||
|
||||
This error includes `await`, `async for`, `async with`, and `async` comprehensions.
|
||||
|
||||
```python
|
||||
async def elements(n):
|
||||
yield n
|
||||
|
||||
def _():
|
||||
# error: [invalid-syntax] "`await` outside of an asynchronous function"
|
||||
await 1
|
||||
# error: [invalid-syntax] "`async for` outside of an asynchronous function"
|
||||
async for _ in elements(1):
|
||||
...
|
||||
# error: [invalid-syntax] "`async with` outside of an asynchronous function"
|
||||
async with elements(1) as x:
|
||||
...
|
||||
# error: [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.9 (syntax was added in 3.11)"
|
||||
# error: [invalid-syntax] "asynchronous comprehension outside of an asynchronous function"
|
||||
[x async for x in elements(1)]
|
||||
```
|
||||
|
||||
## Load before `global` declaration
|
||||
|
||||
This should be an error, but it's not yet.
|
||||
|
||||
TODO implement `SemanticSyntaxContext::global`
|
||||
|
||||
```py
|
||||
def f():
|
||||
x = 1
|
||||
global x
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
# Shadowing
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Implicit class shadowing
|
||||
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
C = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Implicit function shadowing
|
||||
|
||||
```py
|
||||
def f(): ...
|
||||
|
||||
f = 1 # error: [invalid-assignment]
|
||||
```
|
||||
@@ -8,14 +8,20 @@
|
||||
a, b = 1 # error: [not-iterable]
|
||||
```
|
||||
|
||||
## Too many values to unpack
|
||||
## Exactly too many values to unpack
|
||||
|
||||
```py
|
||||
a, b = (1, 2, 3) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Too few values to unpack
|
||||
## Exactly too few values to unpack
|
||||
|
||||
```py
|
||||
a, b = (1,) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Too few values to unpack
|
||||
|
||||
```py
|
||||
[a, *b, c, d] = (1, 2) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
# Different ways that `unsupported-bool-conversion` can occur
|
||||
|
||||
## Has a `__bool__` method, but has incorrect parameters
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
def __bool__(self, foo):
|
||||
return False
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 and a and True
|
||||
```
|
||||
|
||||
## Has a `__bool__` method, but has an incorrect return type
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
def __bool__(self) -> str:
|
||||
return "wat"
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 and a and True
|
||||
```
|
||||
|
||||
## Has a `__bool__` attribute, but it's not callable
|
||||
|
||||
```py
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
a = NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 and a and True
|
||||
```
|
||||
|
||||
## Part of a union where at least one member has incorrect `__bool__` method
|
||||
|
||||
```py
|
||||
class NotBoolable1:
|
||||
def __bool__(self) -> str:
|
||||
return "wat"
|
||||
|
||||
class NotBoolable2:
|
||||
pass
|
||||
|
||||
class NotBoolable3:
|
||||
__bool__: int = 3
|
||||
|
||||
def get() -> NotBoolable1 | NotBoolable2 | NotBoolable3:
|
||||
return NotBoolable2()
|
||||
|
||||
# error: [unsupported-bool-conversion]
|
||||
10 and get() and True
|
||||
```
|
||||
@@ -4,6 +4,6 @@
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
assert NotBoolable()
|
||||
```
|
||||
|
||||
@@ -10,8 +10,8 @@ def _(foo: str):
|
||||
reveal_type(False or "z") # revealed: Literal["z"]
|
||||
reveal_type(False or True) # revealed: Literal[True]
|
||||
reveal_type(False or False) # revealed: Literal[False]
|
||||
reveal_type(foo or False) # revealed: str & ~AlwaysFalsy | Literal[False]
|
||||
reveal_type(foo or True) # revealed: str & ~AlwaysFalsy | Literal[True]
|
||||
reveal_type(foo or False) # revealed: (str & ~AlwaysFalsy) | Literal[False]
|
||||
reveal_type(foo or True) # revealed: (str & ~AlwaysFalsy) | Literal[True]
|
||||
```
|
||||
|
||||
## AND
|
||||
@@ -20,8 +20,8 @@ def _(foo: str):
|
||||
def _(foo: str):
|
||||
reveal_type(True and False) # revealed: Literal[False]
|
||||
reveal_type(False and True) # revealed: Literal[False]
|
||||
reveal_type(foo and False) # revealed: str & ~AlwaysTruthy | Literal[False]
|
||||
reveal_type(foo and True) # revealed: str & ~AlwaysTruthy | Literal[True]
|
||||
reveal_type(foo and False) # revealed: (str & ~AlwaysTruthy) | Literal[False]
|
||||
reveal_type(foo and True) # revealed: (str & ~AlwaysTruthy) | Literal[True]
|
||||
reveal_type("x" and "y" and "z") # revealed: Literal["z"]
|
||||
reveal_type("x" and "y" and "") # revealed: Literal[""]
|
||||
reveal_type("" and "y") # revealed: Literal[""]
|
||||
@@ -123,7 +123,7 @@ if NotBoolable():
|
||||
class NotBoolable:
|
||||
__bool__: None = None
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
if NotBoolable():
|
||||
...
|
||||
```
|
||||
@@ -135,7 +135,7 @@ def test(cond: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int | None = None if cond else 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
if NotBoolable():
|
||||
...
|
||||
```
|
||||
@@ -149,7 +149,7 @@ def test(cond: bool):
|
||||
|
||||
a = 10 if cond else NotBoolable()
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `Literal[10] | NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `Literal[10] | NotBoolable`"
|
||||
if a:
|
||||
...
|
||||
```
|
||||
|
||||
@@ -232,21 +232,11 @@ TODO: These do not currently work yet, because we don't correctly model the nest
|
||||
class C[T]:
|
||||
def __init__[S](self, x: T, y: S) -> None: ...
|
||||
|
||||
# TODO: no error
|
||||
# TODO: revealed: C[Literal[1]]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(C(1, 1)) # revealed: C[Unknown]
|
||||
# TODO: no error
|
||||
# TODO: revealed: C[Literal[1]]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(C(1, "string")) # revealed: C[Unknown]
|
||||
# TODO: no error
|
||||
# TODO: revealed: C[Literal[1]]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(C(1, True)) # revealed: C[Unknown]
|
||||
reveal_type(C(1, 1)) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, "string")) # revealed: C[Literal[1]]
|
||||
reveal_type(C(1, True)) # revealed: C[Literal[1]]
|
||||
|
||||
# TODO: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `S`, found `Literal[1]`"
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
@@ -326,8 +316,6 @@ class Sub(Base[Sub]): ...
|
||||
## Another cyclic case
|
||||
|
||||
```pyi
|
||||
# TODO no error (generics)
|
||||
# error: [invalid-base]
|
||||
class Derived[T](list[Derived[T]]): ...
|
||||
```
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ match 42:
|
||||
...
|
||||
case [O]:
|
||||
...
|
||||
case P | Q:
|
||||
case P | Q: # error: [invalid-syntax] "name capture `P` makes remaining patterns unreachable"
|
||||
...
|
||||
case object(foo=R):
|
||||
...
|
||||
@@ -289,7 +289,7 @@ match 42:
|
||||
...
|
||||
case [D]:
|
||||
...
|
||||
case E | F:
|
||||
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
|
||||
...
|
||||
case object(foo=G):
|
||||
...
|
||||
@@ -357,7 +357,7 @@ match 42:
|
||||
...
|
||||
case [D]:
|
||||
...
|
||||
case E | F:
|
||||
case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable"
|
||||
...
|
||||
case object(foo=G):
|
||||
...
|
||||
|
||||
@@ -191,9 +191,9 @@ def _(
|
||||
i2: Intersection[P | Q | R, S],
|
||||
i3: Intersection[P | Q, R | S],
|
||||
) -> None:
|
||||
reveal_type(i1) # revealed: P & Q | P & R | P & S
|
||||
reveal_type(i2) # revealed: P & S | Q & S | R & S
|
||||
reveal_type(i3) # revealed: P & R | Q & R | P & S | Q & S
|
||||
reveal_type(i1) # revealed: (P & Q) | (P & R) | (P & S)
|
||||
reveal_type(i2) # revealed: (P & S) | (Q & S) | (R & S)
|
||||
reveal_type(i3) # revealed: (P & R) | (Q & R) | (P & S) | (Q & S)
|
||||
|
||||
def simplifications_for_same_elements(
|
||||
i1: Intersection[P, Q | P],
|
||||
@@ -216,7 +216,7 @@ def simplifications_for_same_elements(
|
||||
# = P & Q | P & R | Q | Q & R
|
||||
# = Q | P & R
|
||||
# (again, because Q is a supertype of P & Q and of Q & R)
|
||||
reveal_type(i3) # revealed: Q | P & R
|
||||
reveal_type(i3) # revealed: Q | (P & R)
|
||||
|
||||
# (P | Q) & (P | Q)
|
||||
# = P & P | P & Q | Q & P | Q & Q
|
||||
|
||||
@@ -123,7 +123,7 @@ def _(flag: bool, flag2: bool):
|
||||
class NotBoolable:
|
||||
__bool__: int = 3
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `NotBoolable`"
|
||||
while NotBoolable():
|
||||
...
|
||||
```
|
||||
|
||||
@@ -22,6 +22,7 @@ We can then place custom stub files in `/typeshed/stdlib`, for example:
|
||||
`/typeshed/stdlib/builtins.pyi`:
|
||||
|
||||
```pyi
|
||||
class object: ...
|
||||
class BuiltinClass: ...
|
||||
|
||||
builtin_symbol: BuiltinClass
|
||||
|
||||
@@ -29,7 +29,7 @@ def _(x: Literal[1, 2, 3], y: Literal[1, 2, 3]):
|
||||
assert x is 2
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
assert y == 2
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(y) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## `assert` with `isinstance`
|
||||
|
||||
@@ -10,7 +10,7 @@ def _(x: A | B):
|
||||
if isinstance(x, A) and isinstance(x, B):
|
||||
reveal_type(x) # revealed: A & B
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | A & ~B
|
||||
reveal_type(x) # revealed: (B & ~A) | (A & ~B)
|
||||
```
|
||||
|
||||
## Arms might not add narrowing constraints
|
||||
@@ -131,8 +131,8 @@ def _(x: A | B | C, y: A | B | C):
|
||||
# The same for `y`
|
||||
reveal_type(y) # revealed: A | B | C
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A
|
||||
reveal_type(y) # revealed: B & ~A | C & ~A
|
||||
reveal_type(x) # revealed: (B & ~A) | (C & ~A)
|
||||
reveal_type(y) # revealed: (B & ~A) | (C & ~A)
|
||||
|
||||
if (isinstance(x, A) and isinstance(y, A)) or (isinstance(x, B) and isinstance(y, B)):
|
||||
# Here, types of `x` and `y` can be narrowd since all `or` arms constraint them.
|
||||
@@ -155,7 +155,7 @@ def _(x: A | B | C):
|
||||
reveal_type(x) # revealed: B & ~C
|
||||
else:
|
||||
# ~(B & ~C) -> ~B | C -> (A & ~B) | (C & ~B) | C -> (A & ~B) | C
|
||||
reveal_type(x) # revealed: A & ~B | C
|
||||
reveal_type(x) # revealed: (A & ~B) | C
|
||||
```
|
||||
|
||||
## mixing `or` and `not`
|
||||
@@ -167,7 +167,7 @@ class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, B) or not isinstance(x, C):
|
||||
reveal_type(x) # revealed: B | A & ~C
|
||||
reveal_type(x) # revealed: B | (A & ~C)
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~B
|
||||
```
|
||||
@@ -181,7 +181,7 @@ class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, A) or (isinstance(x, B) and not isinstance(x, C)):
|
||||
reveal_type(x) # revealed: A | B & ~C
|
||||
reveal_type(x) # revealed: A | (B & ~C)
|
||||
else:
|
||||
# ~(A | (B & ~C)) -> ~A & ~(B & ~C) -> ~A & (~B | C) -> (~A & C) | (~A ~ B)
|
||||
reveal_type(x) # revealed: C & ~A
|
||||
@@ -197,7 +197,7 @@ class C: ...
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, A) and (isinstance(x, B) or not isinstance(x, C)):
|
||||
# A & (B | ~C) -> (A & B) | (A & ~C)
|
||||
reveal_type(x) # revealed: A & B | A & ~C
|
||||
reveal_type(x) # revealed: (A & B) | (A & ~C)
|
||||
else:
|
||||
# ~((A & B) | (A & ~C)) ->
|
||||
# ~(A & B) & ~(A & ~C) ->
|
||||
@@ -206,7 +206,7 @@ def _(x: A | B | C):
|
||||
# ~A | (~A & C) | (~B & C) ->
|
||||
# ~A | (C & ~B) ->
|
||||
# ~A | (C & ~B) The positive side of ~A is A | B | C ->
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A | C & ~B
|
||||
reveal_type(x) # revealed: (B & ~A) | (C & ~A) | (C & ~B)
|
||||
```
|
||||
|
||||
## Boolean expression internal narrowing
|
||||
|
||||
@@ -20,11 +20,9 @@ def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
|
||||
if x == 1:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
elif x == 2:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
@@ -38,14 +36,11 @@ def _(flag1: bool, flag2: bool):
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x != 2:
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
elif x == 3:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
@@ -9,8 +9,7 @@ def _(flag: bool):
|
||||
if x != None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
# TODO should be None
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
reveal_type(x) # revealed: None
|
||||
```
|
||||
|
||||
## `!=` for other singleton types
|
||||
@@ -22,8 +21,7 @@ def _(flag: bool):
|
||||
if x != False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
# TODO should be Literal[False]
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## `x != y` where `y` is of literal type
|
||||
@@ -47,8 +45,7 @@ def _(flag: bool):
|
||||
if C != A:
|
||||
reveal_type(C) # revealed: Literal[B]
|
||||
else:
|
||||
# TODO should be Literal[A]
|
||||
reveal_type(C) # revealed: Literal[A, B]
|
||||
reveal_type(C) # revealed: Literal[A]
|
||||
```
|
||||
|
||||
## `x != y` where `y` has multiple single-valued options
|
||||
@@ -61,8 +58,7 @@ def _(flag1: bool, flag2: bool):
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## `!=` for non-single-valued types
|
||||
@@ -101,6 +97,61 @@ def f() -> Literal[1, 2, 3]:
|
||||
if (x := f()) != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
else:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Union with `Any`
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def _(x: Any | None, y: Any | None):
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: (Any & ~Literal[1]) | None
|
||||
if y == 1:
|
||||
reveal_type(y) # revealed: Any & ~None
|
||||
```
|
||||
|
||||
## Booleans and integers
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(b: bool, i: Literal[1, 2]):
|
||||
if b == 1:
|
||||
reveal_type(b) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(b) # revealed: Literal[False]
|
||||
|
||||
if b == 6:
|
||||
reveal_type(b) # revealed: Never
|
||||
else:
|
||||
reveal_type(b) # revealed: bool
|
||||
|
||||
if b == 0:
|
||||
reveal_type(b) # revealed: Literal[False]
|
||||
else:
|
||||
reveal_type(b) # revealed: Literal[True]
|
||||
|
||||
if i == True:
|
||||
reveal_type(i) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(i) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## Narrowing `LiteralString` in union
|
||||
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString, Any
|
||||
|
||||
def _(s: LiteralString | None, t: LiteralString | Any):
|
||||
if s == "foo":
|
||||
reveal_type(s) # revealed: Literal["foo"]
|
||||
|
||||
if s == 1:
|
||||
reveal_type(s) # revealed: Never
|
||||
|
||||
if t == "foo":
|
||||
# TODO could be `Literal["foo"] | Any`
|
||||
reveal_type(t) # revealed: LiteralString | Any
|
||||
```
|
||||
@@ -31,17 +31,14 @@ def _(flag1: bool, flag2: bool):
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x == 2:
|
||||
# TODO should be `Literal[2]`
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
elif x == 3:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
elif x != 2:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(x) # revealed: Never
|
||||
```
|
||||
|
||||
@@ -82,19 +82,19 @@ class B: ...
|
||||
|
||||
def f(x: A | B):
|
||||
if x:
|
||||
reveal_type(x) # revealed: A & ~AlwaysFalsy | B & ~AlwaysFalsy
|
||||
reveal_type(x) # revealed: (A & ~AlwaysFalsy) | (B & ~AlwaysFalsy)
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy | B & ~AlwaysTruthy
|
||||
reveal_type(x) # revealed: (A & ~AlwaysTruthy) | (B & ~AlwaysTruthy)
|
||||
|
||||
if x and not x:
|
||||
reveal_type(x) # revealed: A & ~AlwaysFalsy & ~AlwaysTruthy | B & ~AlwaysFalsy & ~AlwaysTruthy
|
||||
reveal_type(x) # revealed: (A & ~AlwaysFalsy & ~AlwaysTruthy) | (B & ~AlwaysFalsy & ~AlwaysTruthy)
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if x or not x:
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A & ~AlwaysTruthy & ~AlwaysFalsy | B & ~AlwaysTruthy & ~AlwaysFalsy
|
||||
reveal_type(x) # revealed: (A & ~AlwaysTruthy & ~AlwaysFalsy) | (B & ~AlwaysTruthy & ~AlwaysFalsy)
|
||||
```
|
||||
|
||||
### Truthiness of Types
|
||||
@@ -111,9 +111,9 @@ x = int if flag() else str
|
||||
reveal_type(x) # revealed: Literal[int, str]
|
||||
|
||||
if x:
|
||||
reveal_type(x) # revealed: Literal[int] & ~AlwaysFalsy | Literal[str] & ~AlwaysFalsy
|
||||
reveal_type(x) # revealed: (Literal[int] & ~AlwaysFalsy) | (Literal[str] & ~AlwaysFalsy)
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[int] & ~AlwaysTruthy | Literal[str] & ~AlwaysTruthy
|
||||
reveal_type(x) # revealed: (Literal[int] & ~AlwaysTruthy) | (Literal[str] & ~AlwaysTruthy)
|
||||
```
|
||||
|
||||
## Determined Truthiness
|
||||
@@ -176,12 +176,12 @@ if isinstance(x, str) and not isinstance(x, B):
|
||||
|
||||
z = x if flag() else y
|
||||
|
||||
reveal_type(z) # revealed: A & str & ~B | Literal[0, 42, "", "hello"]
|
||||
reveal_type(z) # revealed: (A & str & ~B) | Literal[0, 42, "", "hello"]
|
||||
|
||||
if z:
|
||||
reveal_type(z) # revealed: A & str & ~B & ~AlwaysFalsy | Literal[42, "hello"]
|
||||
reveal_type(z) # revealed: (A & str & ~B & ~AlwaysFalsy) | Literal[42, "hello"]
|
||||
else:
|
||||
reveal_type(z) # revealed: A & str & ~B & ~AlwaysTruthy | Literal[0, ""]
|
||||
reveal_type(z) # revealed: (A & str & ~B & ~AlwaysTruthy) | Literal[0, ""]
|
||||
```
|
||||
|
||||
## Narrowing Multiple Variables
|
||||
@@ -264,13 +264,13 @@ def _(
|
||||
):
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass]
|
||||
if ta:
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass] & ~AlwaysFalsy
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | (type[AmbiguousClass] & ~AlwaysFalsy)
|
||||
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] | type[FalsyClass]
|
||||
if af:
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
|
||||
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MetaDeferred`; the return type of its bool method (`MetaAmbiguous`) isn't assignable to `bool"
|
||||
# error: [unsupported-bool-conversion] "Boolean conversion is unsupported for type `MetaDeferred`"
|
||||
if d:
|
||||
# TODO: Should be `Unknown`
|
||||
reveal_type(d) # revealed: type[DeferredClass] & ~AlwaysFalsy
|
||||
@@ -296,12 +296,12 @@ def _(x: Literal[0, 1]):
|
||||
reveal_type(x and A()) # revealed: Literal[0] | A
|
||||
|
||||
def _(x: str):
|
||||
reveal_type(x or A()) # revealed: str & ~AlwaysFalsy | A
|
||||
reveal_type(x and A()) # revealed: str & ~AlwaysTruthy | A
|
||||
reveal_type(x or A()) # revealed: (str & ~AlwaysFalsy) | A
|
||||
reveal_type(x and A()) # revealed: (str & ~AlwaysTruthy) | A
|
||||
|
||||
def _(x: bool | str):
|
||||
reveal_type(x or A()) # revealed: Literal[True] | str & ~AlwaysFalsy | A
|
||||
reveal_type(x and A()) # revealed: Literal[False] | str & ~AlwaysTruthy | A
|
||||
reveal_type(x or A()) # revealed: Literal[True] | (str & ~AlwaysFalsy) | A
|
||||
reveal_type(x and A()) # revealed: Literal[False] | (str & ~AlwaysTruthy) | A
|
||||
|
||||
class Falsy:
|
||||
def __bool__(self) -> Literal[False]:
|
||||
|
||||
@@ -127,7 +127,7 @@ class B: ...
|
||||
|
||||
def _[T](x: A | B):
|
||||
if type(x) is A[str]:
|
||||
reveal_type(x) # revealed: A[int] & A[Unknown] | B & A[Unknown]
|
||||
reveal_type(x) # revealed: (A[int] & A[Unknown]) | (B & A[Unknown])
|
||||
else:
|
||||
reveal_type(x) # revealed: A[int] | B
|
||||
```
|
||||
|
||||
@@ -242,7 +242,7 @@ def f(
|
||||
Nonetheless, `Protocol` can still be used as the second argument to `issubclass()` at runtime:
|
||||
|
||||
```py
|
||||
# TODO: should be `Literal[True]`
|
||||
# Could also be `Literal[True]`, but `bool` is fine:
|
||||
reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -304,10 +304,12 @@ reveal_type(typing.Protocol is not typing_extensions.Protocol) # revealed: bool
|
||||
|
||||
## Calls to protocol classes
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Neither `Protocol`, nor any protocol class, can be directly instantiated:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from typing_extensions import Protocol, reveal_type
|
||||
|
||||
# error: [call-non-callable]
|
||||
reveal_type(Protocol()) # revealed: Unknown
|
||||
@@ -315,7 +317,7 @@ reveal_type(Protocol()) # revealed: Unknown
|
||||
class MyProtocol(Protocol):
|
||||
x: int
|
||||
|
||||
# error
|
||||
# error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||
reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||
```
|
||||
|
||||
@@ -363,25 +365,8 @@ class Foo(Protocol):
|
||||
def method_member(self) -> bytes:
|
||||
return b"foo"
|
||||
|
||||
# TODO: at runtime, `get_protocol_members` returns a `frozenset`,
|
||||
# but for now we might pretend it returns a `tuple`, as we support heterogeneous `tuple` types
|
||||
# but not yet generic `frozenset`s
|
||||
#
|
||||
# So this should either be
|
||||
#
|
||||
# `tuple[Literal["x"], Literal["y"], Literal["z"], Literal["method_member"]]`
|
||||
#
|
||||
# `frozenset[Literal["x", "y", "z", "method_member"]]`
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
Calling `get_protocol_members` on a non-protocol class raises an error at runtime:
|
||||
|
||||
```py
|
||||
class NotAProtocol: ...
|
||||
|
||||
# TODO: should emit `[invalid-protocol]` error, should reveal `Unknown`
|
||||
reveal_type(get_protocol_members(NotAProtocol)) # revealed: @Todo(specialized non-generic class)
|
||||
# TODO: actually a frozenset (requires support for legacy generics)
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["method_member"], Literal["x"], Literal["y"], Literal["z"]]
|
||||
```
|
||||
|
||||
Certain special attributes and methods are not considered protocol members at runtime, and should
|
||||
@@ -399,8 +384,87 @@ class Lumberjack(Protocol):
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
|
||||
# TODO: `tuple[Literal["x"]]` or `frozenset[Literal["x"]]`
|
||||
reveal_type(get_protocol_members(Lumberjack)) # revealed: @Todo(specialized non-generic class)
|
||||
# TODO: actually a frozenset
|
||||
reveal_type(get_protocol_members(Lumberjack)) # revealed: tuple[Literal["x"]]
|
||||
```
|
||||
|
||||
A sub-protocol inherits and extends the members of its superclass protocol(s):
|
||||
|
||||
```py
|
||||
class Bar(Protocol):
|
||||
spam: str
|
||||
|
||||
class Baz(Bar, Protocol):
|
||||
ham: memoryview
|
||||
|
||||
# TODO: actually a frozenset
|
||||
reveal_type(get_protocol_members(Baz)) # revealed: tuple[Literal["ham"], Literal["spam"]]
|
||||
|
||||
class Baz2(Bar, Foo, Protocol): ...
|
||||
|
||||
# TODO: actually a frozenset
|
||||
# revealed: tuple[Literal["method_member"], Literal["spam"], Literal["x"], Literal["y"], Literal["z"]]
|
||||
reveal_type(get_protocol_members(Baz2))
|
||||
```
|
||||
|
||||
## Protocol members in statically known branches
|
||||
|
||||
The list of protocol members does not include any members declared in branches that are statically
|
||||
known to be unreachable:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing_extensions import Protocol, get_protocol_members
|
||||
|
||||
class Foo(Protocol):
|
||||
if sys.version_info >= (3, 10):
|
||||
a: int
|
||||
b = 42
|
||||
def c(self) -> None: ...
|
||||
else:
|
||||
d: int
|
||||
e = 56
|
||||
def f(self) -> None: ...
|
||||
|
||||
# TODO: actually a frozenset
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["d"], Literal["e"], Literal["f"]]
|
||||
```
|
||||
|
||||
## Invalid calls to `get_protocol_members()`
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
Calling `get_protocol_members` on a non-protocol class raises an error at runtime:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing_extensions import Protocol, get_protocol_members
|
||||
|
||||
class NotAProtocol: ...
|
||||
|
||||
get_protocol_members(NotAProtocol) # error: [invalid-argument-type]
|
||||
|
||||
class AlsoNotAProtocol(NotAProtocol, object): ...
|
||||
|
||||
get_protocol_members(AlsoNotAProtocol) # error: [invalid-argument-type]
|
||||
```
|
||||
|
||||
The original class object must be passed to the function; a specialised version of a generic version
|
||||
does not suffice:
|
||||
|
||||
```py
|
||||
class GenericProtocol[T](Protocol): ...
|
||||
|
||||
get_protocol_members(GenericProtocol[int]) # TODO: should emit a diagnostic here (https://github.com/astral-sh/ruff/issues/17549)
|
||||
```
|
||||
|
||||
## Subtyping of protocols with attribute members
|
||||
@@ -409,6 +473,11 @@ In the following example, the protocol class `HasX` defines an interface such th
|
||||
static type can be said to be a subtype of `HasX` if all inhabitants of that other type have a
|
||||
mutable `x` attribute of type `int`:
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from knot_extensions import static_assert, is_assignable_to, is_subtype_of
|
||||
@@ -548,9 +617,61 @@ def f(arg: HasXWithDefault):
|
||||
reveal_type(type(arg).x) # revealed: int
|
||||
```
|
||||
|
||||
Assignments in a class body of a protocol -- of any kind -- are not permitted by red-knot unless the
|
||||
symbol being assigned to is also explicitly declared in the protocol's class body. Note that this is
|
||||
stricter validation of protocol members than many other type checkers currently apply (as of
|
||||
2025/04/21).
|
||||
|
||||
The reason for this strict validation is that undeclared variables in the class body would lead to
|
||||
an ambiguous interface being declared by the protocol.
|
||||
|
||||
```py
|
||||
from typing_extensions import TypeAlias, get_protocol_members
|
||||
|
||||
class MyContext:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def __exit__(self, *args) -> None: ...
|
||||
|
||||
class LotsOfBindings(Protocol):
|
||||
a: int
|
||||
a = 42 # this is fine, since `a` is declared in the class body
|
||||
b: int = 56 # this is also fine, by the same principle
|
||||
|
||||
type c = str # this is very strange but I can't see a good reason to disallow it
|
||||
d: TypeAlias = bytes # same here
|
||||
|
||||
class Nested: ... # also weird, but we should also probably allow it
|
||||
class NestedProtocol(Protocol): ... # same here...
|
||||
e = 72 # TODO: this should error with `[invalid-protocol]` (`e` is not declared)
|
||||
|
||||
f, g = (1, 2) # TODO: this should error with `[invalid-protocol]` (`f` and `g` are not declared)
|
||||
|
||||
h: int = (i := 3) # TODO: this should error with `[invalid-protocol]` (`i` is not declared)
|
||||
|
||||
for j in range(42): # TODO: this should error with `[invalid-protocol]` (`j` is not declared)
|
||||
pass
|
||||
|
||||
with MyContext() as k: # TODO: this should error with `[invalid-protocol]` (`k` is not declared)
|
||||
pass
|
||||
|
||||
match object():
|
||||
case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared)
|
||||
...
|
||||
|
||||
# TODO: actually a frozenset
|
||||
# revealed: tuple[Literal["Nested"], Literal["NestedProtocol"], Literal["a"], Literal["b"], Literal["c"], Literal["d"], Literal["e"], Literal["f"], Literal["g"], Literal["h"], Literal["i"], Literal["j"], Literal["k"], Literal["l"]]
|
||||
reveal_type(get_protocol_members(LotsOfBindings))
|
||||
```
|
||||
|
||||
Attribute members are allowed to have assignments in methods on the protocol class, just like
|
||||
non-protocol classes. Unlike other classes, however, *implicit* instance attributes -- those that
|
||||
are not declared in the class body -- are not allowed:
|
||||
non-protocol classes. Unlike other classes, however, instance attributes that are not declared in
|
||||
the class body are disallowed. This is mandated by [the spec][spec_protocol_members]:
|
||||
|
||||
> Additional attributes *only* defined in the body of a method by assignment via `self` are not
|
||||
> allowed. The rationale for this is that the protocol class implementation is often not shared by
|
||||
> subtypes, so the interface should not depend on the default implementation.
|
||||
|
||||
```py
|
||||
class Foo(Protocol):
|
||||
@@ -559,11 +680,33 @@ class Foo(Protocol):
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.x = 42 # fine
|
||||
self.a = 56 # error
|
||||
self.a = 56 # TODO: should emit diagnostic
|
||||
self.b: int = 128 # TODO: should emit diagnostic
|
||||
|
||||
def non_init_method(self) -> None:
|
||||
self.y = 64 # fine
|
||||
self.b = 72 # error
|
||||
self.c = 72 # TODO: should emit diagnostic
|
||||
|
||||
# Note: the list of members does not include `a`, `b` or `c`,
|
||||
# as none of these attributes is declared in the class body.
|
||||
#
|
||||
# TODO: actually a frozenset
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["non_init_method"], Literal["x"], Literal["y"]]
|
||||
```
|
||||
|
||||
If a member is declared in a superclass of a protocol class, it is fine for it to be assigned to in
|
||||
the sub-protocol class without a redeclaration:
|
||||
|
||||
```py
|
||||
class Super(Protocol):
|
||||
x: int
|
||||
|
||||
class Sub(Super, Protocol):
|
||||
x = 42 # no error here, since it's declared in the superclass
|
||||
|
||||
# TODO: actually frozensets
|
||||
reveal_type(get_protocol_members(Super)) # revealed: tuple[Literal["x"]]
|
||||
reveal_type(get_protocol_members(Sub)) # revealed: tuple[Literal["x"]]
|
||||
```
|
||||
|
||||
If a protocol has 0 members, then all other types are assignable to it, and all fully static types
|
||||
@@ -1077,23 +1220,25 @@ static_assert(is_assignable_to(HasGetAttrAndSetAttr, XAsymmetricProperty)) # er
|
||||
|
||||
## Narrowing of protocols
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
By default, a protocol class cannot be used as the second argument to `isinstance()` or
|
||||
`issubclass()`, and a type checker must emit an error on such calls. However, we still narrow the
|
||||
type inside these branches (this matches the behaviour of other type checkers):
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
from typing_extensions import Protocol, reveal_type
|
||||
|
||||
class HasX(Protocol):
|
||||
x: int
|
||||
|
||||
def f(arg: object, arg2: type):
|
||||
if isinstance(arg, HasX): # error
|
||||
if isinstance(arg, HasX): # error: [invalid-argument-type]
|
||||
reveal_type(arg) # revealed: HasX
|
||||
else:
|
||||
reveal_type(arg) # revealed: ~HasX
|
||||
|
||||
if issubclass(arg2, HasX): # error
|
||||
if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
reveal_type(arg2) # revealed: type[HasX]
|
||||
else:
|
||||
reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
@@ -1128,10 +1273,10 @@ class OnlyMethodMembers(Protocol):
|
||||
def method(self) -> None: ...
|
||||
|
||||
def f(arg1: type, arg2: type):
|
||||
if issubclass(arg1, OnlyMethodMembers): # error
|
||||
reveal_type(arg1) # revealed: type[OnlyMethodMembers]
|
||||
if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
|
||||
reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
else:
|
||||
reveal_type(arg1) # revealed: type & ~type[OnlyMethodMembers]
|
||||
reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
|
||||
if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
@@ -1139,6 +1284,140 @@ def f(arg1: type, arg2: type):
|
||||
reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
```
|
||||
|
||||
## Truthiness of protocol instance
|
||||
|
||||
An instance of a protocol type generally has ambiguous truthiness:
|
||||
|
||||
```py
|
||||
from typing import Protocol
|
||||
|
||||
class Foo(Protocol):
|
||||
x: int
|
||||
|
||||
def f(foo: Foo):
|
||||
reveal_type(bool(foo)) # revealed: bool
|
||||
```
|
||||
|
||||
But this is not the case if the protocol has a `__bool__` method member that returns `Literal[True]`
|
||||
or `Literal[False]`:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
class Truthy(Protocol):
|
||||
def __bool__(self) -> Literal[True]: ...
|
||||
|
||||
class FalsyFoo(Foo, Protocol):
|
||||
def __bool__(self) -> Literal[False]: ...
|
||||
|
||||
class FalsyFooSubclass(FalsyFoo, Protocol):
|
||||
y: str
|
||||
|
||||
def g(a: Truthy, b: FalsyFoo, c: FalsyFooSubclass):
|
||||
reveal_type(bool(a)) # revealed: Literal[True]
|
||||
reveal_type(bool(b)) # revealed: Literal[False]
|
||||
reveal_type(bool(c)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
It is not sufficient for a protocol to have a callable `__bool__` instance member that returns
|
||||
`Literal[True]` for it to be considered always truthy. Dunder methods are looked up on the class
|
||||
rather than the instance. If a protocol `X` has an instance-attribute `__bool__` member, it is
|
||||
unknowable whether that attribute can be accessed on the type of an object that satisfies `X`'s
|
||||
interface:
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
|
||||
class InstanceAttrBool(Protocol):
|
||||
__bool__: Callable[[], Literal[True]]
|
||||
|
||||
def h(obj: InstanceAttrBool):
|
||||
reveal_type(bool(obj)) # revealed: bool
|
||||
```
|
||||
|
||||
## Fully static protocols; gradual protocols
|
||||
|
||||
A protocol is only fully static if all of its members are fully static:
|
||||
|
||||
```py
|
||||
from typing import Protocol, Any
|
||||
from knot_extensions import is_fully_static, static_assert
|
||||
|
||||
class FullyStatic(Protocol):
|
||||
x: int
|
||||
|
||||
class NotFullyStatic(Protocol):
|
||||
x: Any
|
||||
|
||||
static_assert(is_fully_static(FullyStatic))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(not is_fully_static(NotFullyStatic)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
Non-fully-static protocols do not participate in subtyping, only assignability:
|
||||
|
||||
```py
|
||||
from knot_extensions import is_subtype_of, is_assignable_to
|
||||
|
||||
class NominalWithX:
|
||||
x: int = 42
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(is_assignable_to(NominalWithX, FullyStatic)) # error: [static-assert-error]
|
||||
static_assert(is_assignable_to(NominalWithX, NotFullyStatic)) # error: [static-assert-error]
|
||||
static_assert(is_subtype_of(NominalWithX, FullyStatic)) # error: [static-assert-error]
|
||||
|
||||
static_assert(not is_subtype_of(NominalWithX, NotFullyStatic))
|
||||
```
|
||||
|
||||
Empty protocols are fully static; this follows from the fact that an empty protocol is equivalent to
|
||||
the nominal type `object` (as described above):
|
||||
|
||||
```py
|
||||
class Empty(Protocol): ...
|
||||
|
||||
static_assert(is_fully_static(Empty))
|
||||
```
|
||||
|
||||
A method member is only considered fully static if all its parameter annotations and its return
|
||||
annotation are fully static:
|
||||
|
||||
```py
|
||||
class FullyStaticMethodMember(Protocol):
|
||||
def method(self, x: int) -> str: ...
|
||||
|
||||
class DynamicParameter(Protocol):
|
||||
def method(self, x: Any) -> str: ...
|
||||
|
||||
class DynamicReturn(Protocol):
|
||||
def method(self, x: int) -> Any: ...
|
||||
|
||||
static_assert(is_fully_static(FullyStaticMethodMember))
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_fully_static(DynamicParameter)) # error: [static-assert-error]
|
||||
static_assert(not is_fully_static(DynamicReturn)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
The [typing spec][spec_protocol_members] states:
|
||||
|
||||
> If any parameters of a protocol method are not annotated, then their types are assumed to be `Any`
|
||||
|
||||
Thus, a partially unannotated method member can also not be considered to be fully static:
|
||||
|
||||
```py
|
||||
class NoParameterAnnotation(Protocol):
|
||||
def method(self, x) -> str: ...
|
||||
|
||||
class NoReturnAnnotation(Protocol):
|
||||
def method(self, x: int): ...
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(not is_fully_static(NoParameterAnnotation)) # error: [static-assert-error]
|
||||
static_assert(not is_fully_static(NoReturnAnnotation)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
## `typing.SupportsIndex` and `typing.Sized`
|
||||
|
||||
`typing.SupportsIndex` is already somewhat supported through some special-casing in red-knot.
|
||||
@@ -1167,10 +1446,10 @@ def _(some_list: list, some_tuple: tuple[int, str], some_sized: Sized):
|
||||
|
||||
Add tests for:
|
||||
|
||||
- Assignments without declarations in protocol class bodies. And various weird ways of creating
|
||||
attributes in a class body or instance method. [Example mypy tests][mypy_weird_protocols].
|
||||
- More tests for protocols inside `type[]`. [Spec reference][protocols_inside_type_spec].
|
||||
- Protocols with instance-method members
|
||||
- Protocols with instance-method members, including:
|
||||
- Protocols with methods that have parameters or the return type unannotated
|
||||
- Protocols with methods that have parameters or the return type annotated with `Any`
|
||||
- Protocols with `@classmethod` and `@staticmethod`
|
||||
- Assignability of non-instance types to protocols with instance-method members (e.g. a
|
||||
class-literal type can be a subtype of `Sized` if its metaclass has a `__len__` method)
|
||||
@@ -1184,16 +1463,13 @@ Add tests for:
|
||||
- Protocols with instance attributes annotated with `Callable` (can a nominal type with a method
|
||||
satisfy that protocol, and if so in what cases?)
|
||||
- Protocols decorated with `@final`
|
||||
- Protocols with attribute members annotated with `Any`
|
||||
- Protocols with methods that have parameters or the return type unannotated
|
||||
- Protocols with methods that have parameters or the return type annotated with `Any`
|
||||
- Equivalence and subtyping between `Callable` types and protocols that define `__call__`
|
||||
|
||||
[mypy_protocol_docs]: https://mypy.readthedocs.io/en/stable/protocols.html#protocols-and-structural-subtyping
|
||||
[mypy_protocol_tests]: https://github.com/python/mypy/blob/master/test-data/unit/check-protocols.test
|
||||
[mypy_weird_protocols]: https://github.com/python/mypy/blob/a3ce6d5307e99a1b6c181eaa7c5cf134c53b7d8b/test-data/unit/check-protocols.test#L2131-L2132
|
||||
[protocol conformance tests]: https://github.com/python/typing/tree/main/conformance/tests
|
||||
[protocols_inside_type_spec]: https://typing.python.org/en/latest/spec/protocol.html#type-and-class-objects-vs-protocols
|
||||
[recursive_protocols_spec]: https://typing.python.org/en/latest/spec/protocol.html#recursive-protocols
|
||||
[self_types_protocols_spec]: https://typing.python.org/en/latest/spec/protocol.html#self-types-in-protocols
|
||||
[spec_protocol_members]: https://typing.python.org/en/latest/spec/protocol.html#protocol-members
|
||||
[typing_spec_protocols]: https://typing.python.org/en/latest/spec/protocol.html
|
||||
|
||||
@@ -404,7 +404,7 @@ x = int
|
||||
class C:
|
||||
var: ClassVar[x]
|
||||
|
||||
reveal_type(C.var) # revealed: Unknown | str
|
||||
reveal_type(C.var) # revealed: str
|
||||
|
||||
x = str
|
||||
```
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
# `global` references
|
||||
|
||||
## Implicit global in function
|
||||
|
||||
A name reference to a never-defined symbol in a function is implicitly a global lookup.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## Explicit global in function
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## Unassignable type in function
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
|
||||
def f():
|
||||
y: int = 1
|
||||
# error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
y = ""
|
||||
|
||||
global x
|
||||
# TODO: error: [invalid-assignment] "Object of type `Literal[""]` is not assignable to `int`"
|
||||
x = ""
|
||||
```
|
||||
|
||||
## Nested intervening scope
|
||||
|
||||
A `global` statement causes lookup to skip any bindings in intervening scopes:
|
||||
|
||||
```py
|
||||
x: int = 1
|
||||
|
||||
def outer():
|
||||
x: str = ""
|
||||
|
||||
def inner():
|
||||
global x
|
||||
# TODO: revealed: int
|
||||
reveal_type(x) # revealed: str
|
||||
```
|
||||
|
||||
## Narrowing
|
||||
|
||||
An assignment following a `global` statement should narrow the type in the local scope after the
|
||||
assignment.
|
||||
|
||||
```py
|
||||
x: int | None
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 1
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## `nonlocal` and `global`
|
||||
|
||||
A binding cannot be both `nonlocal` and `global`. This should emit a semantic syntax error. CPython
|
||||
marks the `nonlocal` line, while `mypy`, `pyright`, and `ruff` (`PLE0115`) mark the `global` line.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
def g() -> None:
|
||||
nonlocal x
|
||||
global x # TODO: error: [invalid-syntax] "name 'x' is nonlocal and global"
|
||||
x = None
|
||||
```
|
||||
|
||||
## Global declaration after `global` statement
|
||||
|
||||
```py
|
||||
def f():
|
||||
global x
|
||||
# TODO this should also not be an error
|
||||
y = x # error: [unresolved-reference] "Name `x` used when not defined"
|
||||
x = 1 # No error.
|
||||
|
||||
x = 2
|
||||
```
|
||||
|
||||
## Semantic syntax errors
|
||||
|
||||
Using a name prior to its `global` declaration in the same scope is a syntax error.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
print(x) # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
print(x)
|
||||
|
||||
def f():
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
global x
|
||||
x = 1 # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
del x
|
||||
|
||||
def f():
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
global x, y
|
||||
del x # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x, y
|
||||
del x
|
||||
|
||||
def f():
|
||||
print(f"{x=}") # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
|
||||
# still an error in module scope
|
||||
x = None # TODO: error: [invalid-syntax] name `x` is used prior to global declaration
|
||||
global x
|
||||
```
|
||||
@@ -43,14 +43,3 @@ def f():
|
||||
def h():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
## Implicit global in function
|
||||
|
||||
A name reference to a never-defined symbol in a function is implicitly a global lookup.
|
||||
|
||||
```py
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
reveal_type(x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
C = 1 # error: "Implicit shadowing of class `C`; annotate to make it explicit if this is intentional"
|
||||
C = 1 # error: "Implicit shadowing of class `C`"
|
||||
```
|
||||
|
||||
## Explicit
|
||||
|
||||
@@ -15,7 +15,7 @@ def f(x: str):
|
||||
```py
|
||||
def f(): ...
|
||||
|
||||
f = 1 # error: "Implicit shadowing of function `f`; annotate to make it explicit if this is intentional"
|
||||
f = 1 # error: "Implicit shadowing of function `f`"
|
||||
```
|
||||
|
||||
## Explicit shadowing
|
||||
|
||||
@@ -28,12 +28,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
--> /src/mdtest_snippet.py:11:1
|
||||
|
|
||||
10 | # TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
|
||||
11 | instance.attr = 1 # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -29,12 +29,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
--> /src/mdtest_snippet.py:12:1
|
||||
|
|
||||
11 | # TODO: ideally, we would mention why this is an invalid assignment (wrong argument type for `value` parameter)
|
||||
12 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Invalid assignment to data descriptor attribute `attr` on type `C` with custom `__set__` method
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -26,13 +26,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
--> /src/mdtest_snippet.py:6:1
|
||||
|
|
||||
4 | instance = C()
|
||||
5 | instance.attr = 1 # fine
|
||||
6 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
| ^^^^^^^^^^^^^
|
||||
7 |
|
||||
8 | C.attr = 1 # fine
|
||||
|
|
||||
@@ -40,12 +40,12 @@ error: lint:invalid-assignment
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
8 | C.attr = 1 # fine
|
||||
9 | C.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -26,13 +26,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
warning: lint:possibly-unbound-attribute
|
||||
warning: lint:possibly-unbound-attribute: Attribute `attr` on type `Literal[C]` is possibly unbound
|
||||
--> /src/mdtest_snippet.py:6:5
|
||||
|
|
||||
4 | attr: int = 0
|
||||
5 |
|
||||
6 | C.attr = 1 # error: [possibly-unbound-attribute]
|
||||
| ^^^^^^ Attribute `attr` on type `Literal[C]` is possibly unbound
|
||||
| ^^^^^^
|
||||
7 |
|
||||
8 | instance = C()
|
||||
|
|
||||
@@ -40,12 +40,12 @@ warning: lint:possibly-unbound-attribute
|
||||
```
|
||||
|
||||
```
|
||||
warning: lint:possibly-unbound-attribute
|
||||
warning: lint:possibly-unbound-attribute: Attribute `attr` on type `C` is possibly unbound
|
||||
--> /src/mdtest_snippet.py:9:5
|
||||
|
|
||||
8 | instance = C()
|
||||
9 | instance.attr = 1 # error: [possibly-unbound-attribute]
|
||||
| ^^^^^^^^^^^^^ Attribute `attr` on type `C` is possibly unbound
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -26,13 +26,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
--> /src/mdtest_snippet.py:7:1
|
||||
|
|
||||
5 | instance = C()
|
||||
6 | instance.attr = 1 # fine
|
||||
7 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
| ^^^^^^^^^^^^^
|
||||
8 |
|
||||
9 | C.attr = 1 # error: [invalid-attribute-access]
|
||||
|
|
||||
@@ -40,13 +40,13 @@ error: lint:invalid-assignment
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-attribute-access
|
||||
error: lint:invalid-attribute-access: Cannot assign to instance attribute `attr` from the class object `Literal[C]`
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
7 | instance.attr = "wrong" # error: [invalid-assignment]
|
||||
8 |
|
||||
9 | C.attr = 1 # error: [invalid-attribute-access]
|
||||
| ^^^^^^ Cannot assign to instance attribute `attr` from the class object `Literal[C]`
|
||||
| ^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -37,12 +37,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Object of type `Literal[1]` is not assignable to attribute `attr` on type `Literal[C1, C1]`
|
||||
--> /src/mdtest_snippet.py:11:5
|
||||
|
|
||||
10 | # TODO: The error message here could be improved to explain why the assignment fails.
|
||||
11 | C1.attr = 1 # error: [invalid-assignment]
|
||||
| ^^^^^^^ Object of type `Literal[1]` is not assignable to attribute `attr` on type `Literal[C1, C1]`
|
||||
| ^^^^^^^
|
||||
12 |
|
||||
13 | class C2:
|
||||
|
|
||||
|
||||
@@ -23,13 +23,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-attribute
|
||||
error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type `Literal[C]`.
|
||||
--> /src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | class C: ...
|
||||
2 |
|
||||
3 | C.non_existent = 1 # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^ Unresolved attribute `non_existent` on type `Literal[C]`.
|
||||
| ^^^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | instance = C()
|
||||
|
|
||||
@@ -37,12 +37,12 @@ error: lint:unresolved-attribute
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unresolved-attribute
|
||||
error: lint:unresolved-attribute: Unresolved attribute `non_existent` on type `C`.
|
||||
--> /src/mdtest_snippet.py:6:1
|
||||
|
|
||||
5 | instance = C()
|
||||
6 | instance.non_existent = 1 # error: [unresolved-attribute]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ Unresolved attribute `non_existent` on type `C`.
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -27,12 +27,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/attrib
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
--> /src/mdtest_snippet.py:7:1
|
||||
|
|
||||
6 | C.attr = 1 # fine
|
||||
7 | C.attr = "wrong" # error: [invalid-assignment]
|
||||
| ^^^^^^ Object of type `Literal["wrong"]` is not assignable to attribute `attr` of type `int`
|
||||
| ^^^^^^
|
||||
8 |
|
||||
9 | instance = C()
|
||||
|
|
||||
@@ -40,12 +40,12 @@ error: lint:invalid-assignment
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-attribute-access
|
||||
error: lint:invalid-attribute-access: Cannot assign to ClassVar `attr` from an instance of type `C`
|
||||
--> /src/mdtest_snippet.py:10:1
|
||||
|
|
||||
9 | instance = C()
|
||||
10 | instance.attr = 1 # error: [invalid-attribute-access]
|
||||
| ^^^^^^^^^^^^^ Cannot assign to ClassVar `attr` from an instance of type `C`
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -18,11 +18,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/import/basic.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `zqzqzqzqzqzqzq`
|
||||
--> /src/mdtest_snippet.py:1:8
|
||||
|
|
||||
1 | import zqzqzqzqzqzqzq # error: [unresolved-import] "Cannot resolve import `zqzqzqzqzqzqzq`"
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `zqzqzqzqzqzqzq`
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -27,12 +27,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/import/basic.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `a.foo`
|
||||
--> /src/mdtest_snippet.py:2:8
|
||||
|
|
||||
1 | # Topmost component resolvable, submodule not resolvable:
|
||||
2 | import a.foo # error: [unresolved-import] "Cannot resolve import `a.foo`"
|
||||
| ^^^^^ Cannot resolve import `a.foo`
|
||||
| ^^^^^
|
||||
3 |
|
||||
4 | # Topmost component unresolvable:
|
||||
|
|
||||
@@ -40,12 +40,12 @@ error: lint:unresolved-import
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `b.foo`
|
||||
--> /src/mdtest_snippet.py:5:8
|
||||
|
|
||||
4 | # Topmost component unresolvable:
|
||||
5 | import b.foo # error: [unresolved-import] "Cannot resolve import `b.foo`"
|
||||
| ^^^^^ Cannot resolve import `b.foo`
|
||||
| ^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -28,12 +28,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable` is not iterable because it has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
--> /src/mdtest_snippet.py:10:10
|
||||
|
|
||||
9 | # error: [not-iterable]
|
||||
10 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` is not iterable because it has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
| ^^^^^^^^^^
|
||||
11 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Literal[123]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
--> /src/mdtest_snippet.py:2:10
|
||||
|
|
||||
1 | nonsense = 123
|
||||
2 | for x in nonsense: # error: [not-iterable]
|
||||
| ^^^^^^^^ Object of type `Literal[123]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
| ^^^^^^^^
|
||||
3 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -24,13 +24,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `None`, which is not callable
|
||||
--> /src/mdtest_snippet.py:6:10
|
||||
|
|
||||
4 | __iter__: None = None
|
||||
5 |
|
||||
6 | for x in NotIterable(): # error: [not-iterable]
|
||||
| ^^^^^^^^^^^^^ Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `None`, which is not callable
|
||||
| ^^^^^^^^^^^^^
|
||||
7 | pass
|
||||
|
|
||||
|
||||
|
||||
@@ -25,12 +25,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Bad` is not iterable because it has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
|
||||
--> /src/mdtest_snippet.py:7:10
|
||||
|
|
||||
6 | # error: [not-iterable]
|
||||
7 | for x in Bad():
|
||||
| ^^^^^ Object of type `Bad` is not iterable because it has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
|
||||
| ^^^^^
|
||||
8 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
|
||||
|
||||
@@ -46,12 +46,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `CustomCallable`) may not be callable
|
||||
--> /src/mdtest_snippet.py:22:14
|
||||
|
|
||||
21 | # error: [not-iterable]
|
||||
22 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `CustomCallable`) may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
23 | # TODO... `int` might be ideal here?
|
||||
24 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
@@ -73,12 +73,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable2.__getitem__(key: int) -> int) | None`) may not be callable
|
||||
--> /src/mdtest_snippet.py:27:14
|
||||
|
|
||||
26 | # error: [not-iterable]
|
||||
27 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable2.__getitem__(key: int) -> int) | None`) may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
28 | # TODO... `int` might be ideal here?
|
||||
29 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
@@ -43,12 +43,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
|
||||
--> /src/mdtest_snippet.py:20:14
|
||||
|
|
||||
19 | # error: [not-iterable]
|
||||
20 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it has no `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
21 | # TODO: `str` might be better
|
||||
22 | reveal_type(x) # revealed: str | Unknown
|
||||
|
|
||||
@@ -70,12 +70,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
--> /src/mdtest_snippet.py:25:14
|
||||
|
|
||||
24 | # error: [not-iterable]
|
||||
25 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
| ^^^^^^^^^^^
|
||||
26 | reveal_type(y) # revealed: str | int
|
||||
|
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because its `__iter__` method (with type `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`) may have an invalid signature (expected `def __iter__(self): ...`)
|
||||
--> /src/mdtest_snippet.py:17:14
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method (with type `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`) may have an invalid signature (expected `def __iter__(self): ...`)
|
||||
| ^^^^^^^^^^^
|
||||
18 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
@@ -73,12 +73,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
|
||||
--> /src/mdtest_snippet.py:28:14
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
29 | # TODO: `int` would probably be better here:
|
||||
30 | reveal_type(x) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
@@ -51,12 +51,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
--> /src/mdtest_snippet.py:28:14
|
||||
|
|
||||
27 | # error: [not-iterable]
|
||||
28 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
| ^^^^^^^^^^^
|
||||
29 | reveal_type(x) # revealed: int | str
|
||||
|
|
||||
|
||||
@@ -77,12 +77,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
|
||||
--> /src/mdtest_snippet.py:32:14
|
||||
|
|
||||
31 | # error: [not-iterable]
|
||||
32 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
33 | # TODO: `int` would probably be better here:
|
||||
34 | reveal_type(y) # revealed: int | Unknown
|
||||
|
|
||||
|
||||
@@ -36,12 +36,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
--> /src/mdtest_snippet.py:18:14
|
||||
|
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
| ^^^^^^^^^^
|
||||
19 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
|
||||
|
||||
@@ -54,12 +54,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
|
||||
--> /src/mdtest_snippet.py:31:14
|
||||
|
|
||||
30 | # error: [not-iterable]
|
||||
31 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` may not be iterable because it may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
|
||||
| ^^^^^^^^^^^
|
||||
32 | # TODO: `bytes | str` might be better
|
||||
33 | reveal_type(x) # revealed: bytes | str | Unknown
|
||||
|
|
||||
@@ -81,12 +81,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
--> /src/mdtest_snippet.py:36:14
|
||||
|
|
||||
35 | # error: [not-iterable]
|
||||
36 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` may not be iterable because it may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol (expected a signature at least as permissive as `def __getitem__(self, key: int): ...`)
|
||||
| ^^^^^^^^^^^
|
||||
37 | reveal_type(y) # revealed: bytes | str | int
|
||||
|
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable` may not be iterable because it may not have an `__iter__` method or a `__getitem__` method
|
||||
--> /src/mdtest_snippet.py:17:14
|
||||
|
|
||||
16 | # error: [not-iterable]
|
||||
17 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` may not be iterable because it may not have an `__iter__` method or a `__getitem__` method
|
||||
| ^^^^^^^^^^
|
||||
18 | reveal_type(x) # revealed: int | bytes
|
||||
|
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Test | Test2` may not be iterable because its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
|
||||
--> /src/mdtest_snippet.py:18:14
|
||||
|
|
||||
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
|
||||
17 | # error: [not-iterable]
|
||||
18 | for x in Test() if flag else Test2():
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Object of type `Test | Test2` may not be iterable because its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
19 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
|
||||
@@ -31,13 +31,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Test | Literal[42]` may not be iterable because it may not have an `__iter__` method and it doesn't have a `__getitem__` method
|
||||
--> /src/mdtest_snippet.py:13:14
|
||||
|
|
||||
11 | def _(flag: bool):
|
||||
12 | # error: [not-iterable]
|
||||
13 | for x in Test() if flag else 42:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Object of type `Test | Literal[42]` may not be iterable because it may not have an `__iter__` method and it doesn't have a `__getitem__` method
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
14 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
|
||||
@@ -33,25 +33,25 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `int | None`, which is not callable
|
||||
--> /src/mdtest_snippet.py:11:14
|
||||
|
|
||||
10 | # error: [not-iterable]
|
||||
11 | for x in NotIterable():
|
||||
| ^^^^^^^^^^^^^ Object of type `NotIterable` is not iterable because its `__iter__` attribute has type `int | None`, which is not callable
|
||||
| ^^^^^^^^^^^^^
|
||||
12 | pass
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
warning: lint:possibly-unresolved-reference
|
||||
warning: lint:possibly-unresolved-reference: Name `x` used when possibly not defined
|
||||
--> /src/mdtest_snippet.py:16:17
|
||||
|
|
||||
14 | # revealed: Unknown
|
||||
15 | # error: [possibly-unresolved-reference]
|
||||
16 | reveal_type(x)
|
||||
| ^ Name `x` used when possibly not defined
|
||||
| ^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -26,12 +26,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Bad` is not iterable because its `__iter__` method returns an object of type `int`, which has no `__next__` method
|
||||
--> /src/mdtest_snippet.py:8:10
|
||||
|
|
||||
7 | # error: [not-iterable]
|
||||
8 | for x in Bad():
|
||||
| ^^^^^ Object of type `Bad` is not iterable because its `__iter__` method returns an object of type `int`, which has no `__next__` method
|
||||
| ^^^^^
|
||||
9 | reveal_type(x) # revealed: Unknown
|
||||
|
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable` is not iterable because its `__iter__` method has an invalid signature (expected `def __iter__(self): ...`)
|
||||
--> /src/mdtest_snippet.py:12:10
|
||||
|
|
||||
11 | # error: [not-iterable]
|
||||
12 | for x in Iterable():
|
||||
| ^^^^^^^^^^ Object of type `Iterable` is not iterable because its `__iter__` method has an invalid signature (expected `def __iter__(self): ...`)
|
||||
| ^^^^^^^^^^
|
||||
13 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/loops/for.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable1` is not iterable because its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
--> /src/mdtest_snippet.py:19:10
|
||||
|
|
||||
18 | # error: [not-iterable]
|
||||
19 | for x in Iterable1():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable1` is not iterable because its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method (expected `def __next__(self): ...`)
|
||||
| ^^^^^^^^^^^
|
||||
20 | reveal_type(x) # revealed: int
|
||||
|
|
||||
|
||||
@@ -67,12 +67,12 @@ info: revealed-type: Revealed type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Iterable2` is not iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
|
||||
--> /src/mdtest_snippet.py:23:10
|
||||
|
|
||||
22 | # error: [not-iterable]
|
||||
23 | for y in Iterable2():
|
||||
| ^^^^^^^^^^^ Object of type `Iterable2` is not iterable because its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
|
||||
| ^^^^^^^^^^^
|
||||
24 | reveal_type(y) # revealed: Unknown
|
||||
|
|
||||
|
||||
|
||||
@@ -24,12 +24,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/binary/instances.m
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:7:8
|
||||
|
|
||||
6 | # error: [unsupported-bool-conversion]
|
||||
7 | 10 and a and True
|
||||
| ^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -28,26 +28,28 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
8 | # error: [unsupported-bool-conversion]
|
||||
9 | 10 in WithContains()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
10 | # error: [unsupported-bool-conversion]
|
||||
11 | 10 not in WithContains()
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:11:1
|
||||
|
|
||||
9 | 10 in WithContains()
|
||||
10 | # error: [unsupported-bool-conversion]
|
||||
11 | 10 not in WithContains()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -18,11 +18,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/no_mat
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:no-matching-overload
|
||||
error: lint:no-matching-overload: No overload of class `type` matches arguments
|
||||
--> /src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | type("Foo", ()) # error: [no-matching-overload]
|
||||
| ^^^^^^^^^^^^^^^ No overload of class `type` matches arguments
|
||||
| ^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -22,12 +22,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/unary/not.md
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:5:1
|
||||
|
|
||||
4 | # error: [unsupported-bool-conversion]
|
||||
5 | not NotBoolable()
|
||||
| ^^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: protocols.md - Protocols - Calls to protocol classes
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/protocols.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | # error: [call-non-callable]
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
5 |
|
||||
6 | class MyProtocol(Protocol):
|
||||
7 | x: int
|
||||
8 |
|
||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
12 |
|
||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||
14 | def f(x: type[MyProtocol]):
|
||||
15 | reveal_type(x()) # revealed: MyProtocol
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:call-non-callable: Object of type `typing.Protocol` is not callable
|
||||
--> /src/mdtest_snippet.py:4:13
|
||||
|
|
||||
3 | # error: [call-non-callable]
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
| ^^^^^^^^^^
|
||||
5 |
|
||||
6 | class MyProtocol(Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:4:1
|
||||
|
|
||||
3 | # error: [call-non-callable]
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ `Unknown`
|
||||
5 |
|
||||
6 | class MyProtocol(Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:call-non-callable: Cannot instantiate class `MyProtocol`
|
||||
--> /src/mdtest_snippet.py:10:13
|
||||
|
|
||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||
| ^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
|
|
||||
info: Protocol classes cannot be instantiated
|
||||
--> /src/mdtest_snippet.py:6:7
|
||||
|
|
||||
4 | reveal_type(Protocol()) # revealed: Unknown
|
||||
5 |
|
||||
6 | class MyProtocol(Protocol):
|
||||
| ^^^^^^^^^^^^^^^^^^^^ `MyProtocol` declared as a protocol here
|
||||
7 | x: int
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:10:1
|
||||
|
|
||||
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
|
||||
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `MyProtocol`
|
||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:13:1
|
||||
|
|
||||
11 | class SubclassOfMyProtocol(MyProtocol): ...
|
||||
12 |
|
||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
|
||||
14 | def f(x: type[MyProtocol]):
|
||||
15 | reveal_type(x()) # revealed: MyProtocol
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:15:5
|
||||
|
|
||||
13 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
|
||||
14 | def f(x: type[MyProtocol]):
|
||||
15 | reveal_type(x()) # revealed: MyProtocol
|
||||
| ^^^^^^^^^^^^^^^^ `MyProtocol`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: protocols.md - Protocols - Invalid calls to `get_protocol_members()`
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/protocols.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol, get_protocol_members
|
||||
2 |
|
||||
3 | class NotAProtocol: ...
|
||||
4 |
|
||||
5 | get_protocol_members(NotAProtocol) # error: [invalid-argument-type]
|
||||
6 |
|
||||
7 | class AlsoNotAProtocol(NotAProtocol, object): ...
|
||||
8 |
|
||||
9 | get_protocol_members(AlsoNotAProtocol) # error: [invalid-argument-type]
|
||||
10 | class GenericProtocol[T](Protocol): ...
|
||||
11 |
|
||||
12 | get_protocol_members(GenericProtocol[int]) # TODO: should emit a diagnostic here (https://github.com/astral-sh/ruff/issues/17549)
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type: Invalid argument to `get_protocol_members`
|
||||
--> /src/mdtest_snippet.py:5:1
|
||||
|
|
||||
3 | class NotAProtocol: ...
|
||||
4 |
|
||||
5 | get_protocol_members(NotAProtocol) # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||
6 |
|
||||
7 | class AlsoNotAProtocol(NotAProtocol, object): ...
|
||||
|
|
||||
info: Only protocol classes can be passed to `get_protocol_members`
|
||||
info: `NotAProtocol` is declared here, but it is not a protocol class:
|
||||
--> /src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import Protocol, get_protocol_members
|
||||
2 |
|
||||
3 | class NotAProtocol: ...
|
||||
| ^^^^^^^^^^^^
|
||||
4 |
|
||||
5 | get_protocol_members(NotAProtocol) # error: [invalid-argument-type]
|
||||
|
|
||||
info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol`
|
||||
info: See https://typing.python.org/en/latest/spec/protocol.html#
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type: Invalid argument to `get_protocol_members`
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
7 | class AlsoNotAProtocol(NotAProtocol, object): ...
|
||||
8 |
|
||||
9 | get_protocol_members(AlsoNotAProtocol) # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||
10 | class GenericProtocol[T](Protocol): ...
|
||||
|
|
||||
info: Only protocol classes can be passed to `get_protocol_members`
|
||||
info: `AlsoNotAProtocol` is declared here, but it is not a protocol class:
|
||||
--> /src/mdtest_snippet.py:7:7
|
||||
|
|
||||
5 | get_protocol_members(NotAProtocol) # error: [invalid-argument-type]
|
||||
6 |
|
||||
7 | class AlsoNotAProtocol(NotAProtocol, object): ...
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
8 |
|
||||
9 | get_protocol_members(AlsoNotAProtocol) # error: [invalid-argument-type]
|
||||
|
|
||||
info: A class is only a protocol class if it directly inherits from `typing.Protocol` or `typing_extensions.Protocol`
|
||||
info: See https://typing.python.org/en/latest/spec/protocol.html#
|
||||
|
||||
```
|
||||
@@ -0,0 +1,241 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: protocols.md - Protocols - Narrowing of protocols
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/protocols.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
4 | x: int
|
||||
5 |
|
||||
6 | def f(arg: object, arg2: type):
|
||||
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
9 | else:
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
11 |
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
14 | else:
|
||||
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
16 | from typing import runtime_checkable
|
||||
17 |
|
||||
18 | @runtime_checkable
|
||||
19 | class RuntimeCheckableHasX(Protocol):
|
||||
20 | x: int
|
||||
21 |
|
||||
22 | def f(arg: object):
|
||||
23 | if isinstance(arg, RuntimeCheckableHasX): # no error!
|
||||
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
|
||||
25 | else:
|
||||
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
||||
27 | @runtime_checkable
|
||||
28 | class OnlyMethodMembers(Protocol):
|
||||
29 | def method(self) -> None: ...
|
||||
30 |
|
||||
31 | def f(arg1: type, arg2: type):
|
||||
32 | if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
|
||||
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
34 | else:
|
||||
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
36 |
|
||||
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type: Class `HasX` cannot be used as the second argument to `isinstance`
|
||||
--> /src/mdtest_snippet.py:7:8
|
||||
|
|
||||
6 | def f(arg: object, arg2: type):
|
||||
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
9 | else:
|
||||
|
|
||||
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
|
||||
--> /src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
| ^^^^^^^^^^^^^^ `HasX` declared here
|
||||
4 | x: int
|
||||
|
|
||||
info: A protocol class can only be used in `isinstance` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable`
|
||||
info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:8:9
|
||||
|
|
||||
6 | def f(arg: object, arg2: type):
|
||||
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
| ^^^^^^^^^^^^^^^^ `HasX`
|
||||
9 | else:
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:10:9
|
||||
|
|
||||
8 | reveal_type(arg) # revealed: HasX
|
||||
9 | else:
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
| ^^^^^^^^^^^^^^^^ `~HasX`
|
||||
11 |
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-argument-type: Class `HasX` cannot be used as the second argument to `issubclass`
|
||||
--> /src/mdtest_snippet.py:12:8
|
||||
|
|
||||
10 | reveal_type(arg) # revealed: ~HasX
|
||||
11 |
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ This call will raise `TypeError` at runtime
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
14 | else:
|
||||
|
|
||||
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
|
||||
--> /src/mdtest_snippet.py:3:7
|
||||
|
|
||||
1 | from typing_extensions import Protocol, reveal_type
|
||||
2 |
|
||||
3 | class HasX(Protocol):
|
||||
| ^^^^^^^^^^^^^^ `HasX` declared here
|
||||
4 | x: int
|
||||
|
|
||||
info: A protocol class can only be used in `issubclass` checks if it is decorated with `@typing.runtime_checkable` or `@typing_extensions.runtime_checkable`
|
||||
info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:13:9
|
||||
|
|
||||
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
| ^^^^^^^^^^^^^^^^^ `type[HasX]`
|
||||
14 | else:
|
||||
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:15:9
|
||||
|
|
||||
13 | reveal_type(arg2) # revealed: type[HasX]
|
||||
14 | else:
|
||||
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
||||
| ^^^^^^^^^^^^^^^^^ `type & ~type[HasX]`
|
||||
16 | from typing import runtime_checkable
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:24:9
|
||||
|
|
||||
22 | def f(arg: object):
|
||||
23 | if isinstance(arg, RuntimeCheckableHasX): # no error!
|
||||
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
|
||||
| ^^^^^^^^^^^^^^^^ `RuntimeCheckableHasX`
|
||||
25 | else:
|
||||
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:26:9
|
||||
|
|
||||
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
|
||||
25 | else:
|
||||
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
||||
| ^^^^^^^^^^^^^^^^ `~RuntimeCheckableHasX`
|
||||
27 | @runtime_checkable
|
||||
28 | class OnlyMethodMembers(Protocol):
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:33:9
|
||||
|
|
||||
31 | def f(arg1: type, arg2: type):
|
||||
32 | if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
|
||||
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
| ^^^^^^^^^^^^^^^^^ `type[RuntimeCheckableHasX]`
|
||||
34 | else:
|
||||
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:35:9
|
||||
|
|
||||
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
|
||||
34 | else:
|
||||
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
||||
| ^^^^^^^^^^^^^^^^^ `type & ~type[RuntimeCheckableHasX]`
|
||||
36 |
|
||||
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:38:9
|
||||
|
|
||||
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
| ^^^^^^^^^^^^^^^^^ `type[OnlyMethodMembers]`
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
info: revealed-type: Revealed type
|
||||
--> /src/mdtest_snippet.py:40:9
|
||||
|
|
||||
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
|
||||
39 | else:
|
||||
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
||||
| ^^^^^^^^^^^^^^^^^ `type & ~type[OnlyMethodMembers]`
|
||||
|
|
||||
|
||||
```
|
||||
@@ -56,12 +56,12 @@ error: lint:invalid-return-type: Return type does not match returned value
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.py:7:22
|
||||
|
|
||||
6 | # error: [invalid-return-type]
|
||||
7 | def f(cond: bool) -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
8 | if cond:
|
||||
9 | return 1
|
||||
|
|
||||
@@ -69,12 +69,12 @@ error: lint:invalid-return-type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.py:12:22
|
||||
|
|
||||
11 | # error: [invalid-return-type]
|
||||
12 | def f(cond: bool) -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
13 | if cond:
|
||||
14 | raise ValueError()
|
||||
|
|
||||
@@ -82,12 +82,12 @@ error: lint:invalid-return-type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.py:17:22
|
||||
|
|
||||
16 | # error: [invalid-return-type]
|
||||
17 | def f(cond: bool) -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
18 | if cond:
|
||||
19 | cond = False
|
||||
|
|
||||
|
||||
@@ -35,12 +35,12 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/function/return_ty
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.py:2:12
|
||||
|
|
||||
1 | # error: [invalid-return-type]
|
||||
2 | def f() -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
3 | 1
|
||||
|
|
||||
|
||||
|
||||
@@ -45,12 +45,12 @@ error: lint:invalid-return-type: Return type does not match returned value
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.pyi:6:14
|
||||
|
|
||||
5 | # error: [invalid-return-type]
|
||||
6 | def foo() -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
7 | print("...")
|
||||
8 | ...
|
||||
|
|
||||
@@ -58,12 +58,12 @@ error: lint:invalid-return-type
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:invalid-return-type
|
||||
error: lint:invalid-return-type: Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
--> /src/mdtest_snippet.pyi:11:14
|
||||
|
|
||||
10 | # error: [invalid-return-type]
|
||||
11 | def foo() -> int:
|
||||
| ^^^ Function can implicitly return `None`, which is not assignable to return type `int`
|
||||
| ^^^
|
||||
12 | f"""{foo} is a function that ..."""
|
||||
13 | ...
|
||||
|
|
||||
|
||||
@@ -33,28 +33,30 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/instanc
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:12:1
|
||||
|
|
||||
11 | # error: [unsupported-bool-conversion]
|
||||
12 | 10 < Comparable() < 20
|
||||
| ^^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
13 | # error: [unsupported-bool-conversion]
|
||||
14 | 10 < Comparable() < Comparable()
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:14:1
|
||||
|
|
||||
12 | 10 < Comparable() < 20
|
||||
13 | # error: [unsupported-bool-conversion]
|
||||
14 | 10 < Comparable() < Comparable()
|
||||
| ^^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
15 |
|
||||
16 | Comparable() < Comparable() # fine
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: semantic_syntax_errors.md - Semantic syntax error diagnostics - `async` comprehensions in synchronous comprehensions - Python 3.10
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | async def elements(n):
|
||||
2 | yield n
|
||||
3 |
|
||||
4 | async def f():
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)"
|
||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
7 | async def test():
|
||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||
9 | async def f():
|
||||
10 | [x for x in [1]] and [x async for x in elements(1)]
|
||||
11 |
|
||||
12 | async def f():
|
||||
13 | def g():
|
||||
14 | pass
|
||||
15 | [x async for x in elements(1)]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: invalid-syntax
|
||||
--> /src/mdtest_snippet.py:6:19
|
||||
|
|
||||
4 | async def f():
|
||||
5 | # error: 19 [invalid-syntax] "cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax...
|
||||
6 | return {n: [x async for x in elements(n)] for n in range(3)}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use an asynchronous comprehension outside of an asynchronous function on Python 3.10 (syntax was added in 3.11)
|
||||
7 | async def test():
|
||||
8 | return [[x async for x in elements(n)] async for n in range(3)]
|
||||
|
|
||||
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: shadowing.md - Shadowing - Implicit class shadowing
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/shadowing.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | class C: ...
|
||||
2 |
|
||||
3 | C = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment: Implicit shadowing of class `C`
|
||||
--> /src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | class C: ...
|
||||
2 |
|
||||
3 | C = 1 # error: [invalid-assignment]
|
||||
| ^
|
||||
|
|
||||
info: Annotate to make it explicit if this is intentional
|
||||
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: shadowing.md - Shadowing - Implicit function shadowing
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/shadowing.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | def f(): ...
|
||||
2 |
|
||||
3 | f = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment: Implicit shadowing of function `f`
|
||||
--> /src/mdtest_snippet.py:3:1
|
||||
|
|
||||
1 | def f(): ...
|
||||
2 |
|
||||
3 | f = 1 # error: [invalid-assignment]
|
||||
| ^
|
||||
|
|
||||
info: Annotate to make it explicit if this is intentional
|
||||
|
||||
```
|
||||
@@ -34,14 +34,15 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable | Literal[False]`
|
||||
--> /src/mdtest_snippet.py:15:1
|
||||
|
|
||||
14 | # error: [unsupported-bool-conversion]
|
||||
15 | a < b < b
|
||||
| ^^^^^ Boolean conversion is unsupported for type `NotBoolable | Literal[False]`; its `__bool__` method isn't callable
|
||||
| ^^^^^
|
||||
16 |
|
||||
17 | a < b # fine
|
||||
|
|
||||
info: `__bool__` on `NotBoolable | Literal[False]` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -26,12 +26,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`
|
||||
--> /src/mdtest_snippet.py:9:1
|
||||
|
|
||||
8 | # error: [unsupported-bool-conversion]
|
||||
9 | (A(),) == (A(),)
|
||||
| ^^^^^^^^^^^^^^^^ Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
info: `__bool__` on `NotBoolable` must be callable
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: unpacking.md - Unpacking - Exactly too few values to unpack
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpacking.md
|
||||
---
|
||||
|
||||
# Python source files
|
||||
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | a, b = (1,) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment: Not enough values to unpack
|
||||
--> /src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | a, b = (1,) # error: [invalid-assignment]
|
||||
| ^^^^ ---- Got 1
|
||||
| |
|
||||
| Expected 2
|
||||
|
|
||||
|
||||
```
|
||||
@@ -3,7 +3,7 @@ source: crates/red_knot_test/src/lib.rs
|
||||
expression: snapshot
|
||||
---
|
||||
---
|
||||
mdtest name: unpacking.md - Unpacking - Too many values to unpack
|
||||
mdtest name: unpacking.md - Unpacking - Exactly too many values to unpack
|
||||
mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpacking.md
|
||||
---
|
||||
|
||||
@@ -18,11 +18,13 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpack
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Too many values to unpack
|
||||
--> /src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | a, b = (1, 2, 3) # error: [invalid-assignment]
|
||||
| ^^^^ Too many values to unpack (expected 2, got 3)
|
||||
| ^^^^ --------- Got 3
|
||||
| |
|
||||
| Expected 2
|
||||
|
|
||||
|
||||
```
|
||||
@@ -18,11 +18,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpack
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:not-iterable
|
||||
error: lint:not-iterable: Object of type `Literal[1]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
--> /src/mdtest_snippet.py:1:8
|
||||
|
|
||||
1 | a, b = 1 # error: [not-iterable]
|
||||
| ^ Object of type `Literal[1]` is not iterable because it doesn't have an `__iter__` method or a `__getitem__` method
|
||||
| ^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -12,17 +12,19 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unpack
|
||||
## mdtest_snippet.py
|
||||
|
||||
```
|
||||
1 | a, b = (1,) # error: [invalid-assignment]
|
||||
1 | [a, *b, c, d] = (1, 2) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:invalid-assignment
|
||||
error: lint:invalid-assignment: Not enough values to unpack
|
||||
--> /src/mdtest_snippet.py:1:1
|
||||
|
|
||||
1 | a, b = (1,) # error: [invalid-assignment]
|
||||
| ^^^^ Not enough values to unpack (expected 2, got 1)
|
||||
1 | [a, *b, c, d] = (1, 2) # error: [invalid-assignment]
|
||||
| ^^^^^^^^^^^^^ ------ Got 2
|
||||
| |
|
||||
| Expected 3 or more
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user