Compare commits
32 Commits
dcreager/n
...
david/eq-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242d9b163d | ||
|
|
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 |
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@ebaa9fd27b51a278873b63676fd25490cec6823b"
|
||||
|
||||
- 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
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -162,6 +162,31 @@ def _(flag: bool):
|
||||
reveal_type(f("string")) # revealed: Literal["string", "'string'"]
|
||||
```
|
||||
|
||||
## Unions with literals and negations
|
||||
|
||||
```py
|
||||
from typing import Literal, Union
|
||||
from knot_extensions import Not, AlwaysFalsy, static_assert, is_subtype_of, is_assignable_to
|
||||
|
||||
static_assert(is_subtype_of(Literal["a", ""], Union[Literal["a", ""], Not[AlwaysFalsy]]))
|
||||
static_assert(is_subtype_of(Not[AlwaysFalsy], Union[Literal["", "a"], Not[AlwaysFalsy]]))
|
||||
static_assert(is_subtype_of(Literal["a", ""], Union[Not[AlwaysFalsy], Literal["a", ""]]))
|
||||
static_assert(is_subtype_of(Not[AlwaysFalsy], Union[Not[AlwaysFalsy], Literal["a", ""]]))
|
||||
|
||||
static_assert(is_subtype_of(Literal["a", ""], Union[Literal["a", ""], Not[Literal[""]]]))
|
||||
static_assert(is_subtype_of(Not[Literal[""]], Union[Literal["a", ""], Not[Literal[""]]]))
|
||||
static_assert(is_subtype_of(Literal["a", ""], Union[Not[Literal[""]], Literal["a", ""]]))
|
||||
static_assert(is_subtype_of(Not[Literal[""]], Union[Not[Literal[""]], Literal["a", ""]]))
|
||||
|
||||
def _(
|
||||
x: Union[Literal["a", ""], Not[AlwaysFalsy]],
|
||||
y: Union[Literal["a", ""], Not[Literal[""]]],
|
||||
):
|
||||
reveal_type(x) # revealed: Literal[""] | ~AlwaysFalsy
|
||||
# TODO should be `object`
|
||||
reveal_type(y) # revealed: Literal[""] | ~Literal[""]
|
||||
```
|
||||
|
||||
## Cannot use an argument as both a value and a type form
|
||||
|
||||
```py
|
||||
|
||||
@@ -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,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]
|
||||
```
|
||||
|
||||
@@ -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,12 @@ 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`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
| ^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -28,12 +28,12 @@ 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`; its `__bool__` method isn't callable
|
||||
--> /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()
|
||||
|
|
||||
@@ -41,13 +41,13 @@ error: lint:unsupported-bool-conversion
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -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,12 @@ 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`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -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,12 +33,12 @@ 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`; its `__bool__` method isn't callable
|
||||
--> /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()
|
||||
|
|
||||
@@ -46,13 +46,13 @@ error: lint:unsupported-bool-conversion
|
||||
```
|
||||
|
||||
```
|
||||
error: lint:unsupported-bool-conversion
|
||||
error: lint:unsupported-bool-conversion: Boolean conversion is unsupported for type `NotBoolable`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
|
|
||||
|
||||
@@ -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,12 +34,12 @@ 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]`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
|
|
||||
|
||||
@@ -26,12 +26,12 @@ 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`; its `__bool__` method isn't callable
|
||||
--> /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
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -20,11 +20,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
|
||||
--> /src/mdtest_snippet.py:1:8
|
||||
|
|
||||
1 | import does_not_exist # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
2 |
|
||||
3 | x = does_not_exist.foo
|
||||
|
|
||||
|
||||
@@ -25,11 +25,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Module `a` has no member `does_not_exist`
|
||||
--> /src/mdtest_snippet.py:1:28
|
||||
|
|
||||
1 | from a import does_exist1, does_not_exist, does_exist2 # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^ Module `a` has no member `does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
```
|
||||
|
||||
@@ -20,11 +20,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `.does_not_exist`
|
||||
--> /src/mdtest_snippet.py:1:7
|
||||
|
|
||||
1 | from .does_not_exist import add # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `.does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
|
||||
@@ -20,11 +20,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `.does_not_exist.foo.bar`
|
||||
--> /src/mdtest_snippet.py:1:7
|
||||
|
|
||||
1 | from .does_not_exist.foo.bar import add # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ Cannot resolve import `.does_not_exist.foo.bar`
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
|
||||
@@ -20,11 +20,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `does_not_exist`
|
||||
--> /src/mdtest_snippet.py:1:6
|
||||
|
|
||||
1 | from does_not_exist import add # error: [unresolved-import]
|
||||
| ^^^^^^^^^^^^^^ Cannot resolve import `does_not_exist`
|
||||
| ^^^^^^^^^^^^^^
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
|
||||
@@ -32,11 +32,11 @@ mdtest path: crates/red_knot_python_semantic/resources/mdtest/diagnostics/unreso
|
||||
# Diagnostics
|
||||
|
||||
```
|
||||
error: lint:unresolved-import
|
||||
error: lint:unresolved-import: Cannot resolve import `....foo`
|
||||
--> /src/package/subpackage/subsubpackage/__init__.py:1:10
|
||||
|
|
||||
1 | from ....foo import add # error: [unresolved-import]
|
||||
| ^^^ Cannot resolve import `....foo`
|
||||
| ^^^
|
||||
2 |
|
||||
3 | stat = add(10, 15)
|
||||
|
|
||||
|
||||
@@ -56,6 +56,19 @@ static_assert(not is_disjoint_from(FinalSubclass, A))
|
||||
# ... which makes it disjoint from B1, B2:
|
||||
static_assert(is_disjoint_from(B1, FinalSubclass))
|
||||
static_assert(is_disjoint_from(B2, FinalSubclass))
|
||||
|
||||
# Instance types can also be disjoint if they have disjoint metaclasses.
|
||||
# No possible subclass of `Meta1` and `Meta2` could exist, therefore
|
||||
# no possible subclass of `UsesMeta1` and `UsesMeta2` can exist:
|
||||
class Meta1(type): ...
|
||||
class UsesMeta1(metaclass=Meta1): ...
|
||||
|
||||
@final
|
||||
class Meta2(type): ...
|
||||
|
||||
class UsesMeta2(metaclass=Meta2): ...
|
||||
|
||||
static_assert(is_disjoint_from(UsesMeta1, UsesMeta2))
|
||||
```
|
||||
|
||||
## Tuple types
|
||||
@@ -342,8 +355,8 @@ static_assert(is_disjoint_from(Meta1, type[UsesMeta2]))
|
||||
|
||||
### `type[T]` versus `type[S]`
|
||||
|
||||
By the same token, `type[T]` is disjoint from `type[S]` if the metaclass of `T` is disjoint from the
|
||||
metaclass of `S`.
|
||||
By the same token, `type[T]` is disjoint from `type[S]` if `T` is `@final`, `S` is `@final`, or the
|
||||
metaclass of `T` is disjoint from the metaclass of `S`.
|
||||
|
||||
```py
|
||||
from typing import final
|
||||
@@ -353,6 +366,9 @@ from knot_extensions import static_assert, is_disjoint_from
|
||||
class Meta1(type): ...
|
||||
|
||||
class Meta2(type): ...
|
||||
|
||||
static_assert(is_disjoint_from(type[Meta1], type[Meta2]))
|
||||
|
||||
class UsesMeta1(metaclass=Meta1): ...
|
||||
class UsesMeta2(metaclass=Meta2): ...
|
||||
|
||||
|
||||
@@ -1125,6 +1125,47 @@ def f(fn: Callable[[int], int]) -> None: ...
|
||||
f(a)
|
||||
```
|
||||
|
||||
### Class literals
|
||||
|
||||
#### Classes with metaclasses
|
||||
|
||||
```py
|
||||
from typing import Callable, overload
|
||||
from typing_extensions import Self
|
||||
from knot_extensions import TypeOf, static_assert, is_subtype_of
|
||||
|
||||
class MetaWithReturn(type):
|
||||
def __call__(cls) -> "A":
|
||||
return super().__call__()
|
||||
|
||||
class A(metaclass=MetaWithReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[A], Callable[[], A]))
|
||||
static_assert(not is_subtype_of(TypeOf[A], Callable[[object], A]))
|
||||
|
||||
class MetaWithDifferentReturn(type):
|
||||
def __call__(cls) -> int:
|
||||
return super().__call__()
|
||||
|
||||
class B(metaclass=MetaWithDifferentReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[B], Callable[[], int]))
|
||||
static_assert(not is_subtype_of(TypeOf[B], Callable[[], B]))
|
||||
|
||||
class MetaWithOverloadReturn(type):
|
||||
@overload
|
||||
def __call__(cls, x: int) -> int: ...
|
||||
@overload
|
||||
def __call__(cls) -> str: ...
|
||||
def __call__(cls, x: int | None = None) -> str | int:
|
||||
return super().__call__()
|
||||
|
||||
class C(metaclass=MetaWithOverloadReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[C], Callable[[int], int]))
|
||||
static_assert(is_subtype_of(TypeOf[C], Callable[[], str]))
|
||||
```
|
||||
|
||||
### Bound methods
|
||||
|
||||
```py
|
||||
|
||||
@@ -65,7 +65,7 @@ reveal_type(c) # revealed: Literal[4]
|
||||
### Uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
(a, b, c) = (1, 2)
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -75,7 +75,7 @@ reveal_type(c) # revealed: Unknown
|
||||
### Uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
(a, b) = (1, 2, 3)
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -84,7 +84,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Nested uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
(a, (b, c), d) = (1, (2,), 3)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -95,7 +95,7 @@ reveal_type(d) # revealed: Literal[3]
|
||||
### Nested uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
(a, (b, c), d) = (1, (2, 3, 4), 5)
|
||||
reveal_type(a) # revealed: Literal[1]
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -106,7 +106,7 @@ reveal_type(d) # revealed: Literal[5]
|
||||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
[a, *b, c, d] = (1, 2)
|
||||
reveal_type(a) # revealed: Unknown
|
||||
# TODO: Should be list[Any] once support for assigning to starred expression is added
|
||||
@@ -159,7 +159,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
### Starred expression (6)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 5 or more, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 5 or more"
|
||||
(a, b, c, *d, e, f) = (1,)
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -225,7 +225,7 @@ reveal_type(b) # revealed: LiteralString
|
||||
### Uneven unpacking (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
a, b, c = "ab"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -235,7 +235,7 @@ reveal_type(c) # revealed: Unknown
|
||||
### Uneven unpacking (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
a, b = "abc"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -244,7 +244,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Starred expression (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
(a, *b, c, d) = "ab"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
# TODO: Should be list[LiteralString] once support for assigning to starred expression is added
|
||||
@@ -254,7 +254,7 @@ reveal_type(d) # revealed: Unknown
|
||||
```
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3 or more, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3 or more"
|
||||
(a, b, *c, d) = "a"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -306,7 +306,7 @@ reveal_type(c) # revealed: @Todo(starred unpacking)
|
||||
### Unicode
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
(a, b) = "é"
|
||||
|
||||
reveal_type(a) # revealed: Unknown
|
||||
@@ -316,7 +316,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Unicode escape (1)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
(a, b) = "\u9e6c"
|
||||
|
||||
reveal_type(a) # revealed: Unknown
|
||||
@@ -326,7 +326,7 @@ reveal_type(b) # revealed: Unknown
|
||||
### Unicode escape (2)
|
||||
|
||||
```py
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
(a, b) = "\U0010ffff"
|
||||
|
||||
reveal_type(a) # revealed: Unknown
|
||||
@@ -420,8 +420,8 @@ def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 3)"
|
||||
# error: [invalid-assignment] "Too many values to unpack (expected 2, got 5)"
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
# error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
a, b = arg
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -431,8 +431,8 @@ def _(arg: tuple[int, bytes, int] | tuple[int, int, str, int, bytes]):
|
||||
|
||||
```py
|
||||
def _(arg: tuple[int, bytes] | tuple[int, str]):
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
a, b, c = arg
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -575,7 +575,7 @@ for a, b in ((1, 2), ("a", "b")):
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
# error: "Object of type `Literal[2]` is not iterable"
|
||||
# error: "Object of type `Literal[4]` is not iterable"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c"):
|
||||
reveal_type(a) # revealed: Unknown | Literal[3, 5]
|
||||
reveal_type(b) # revealed: Unknown | Literal["a", "b"]
|
||||
@@ -702,7 +702,7 @@ class ContextManager:
|
||||
def __exit__(self, *args) -> None:
|
||||
pass
|
||||
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 3, got 2)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 3"
|
||||
with ContextManager() as (a, b, c):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -765,7 +765,7 @@ def _(arg: tuple[tuple[int, int, int], tuple[int, str, bytes], tuple[int, int, s
|
||||
# error: "Object of type `Literal[1]` is not iterable"
|
||||
# error: "Object of type `Literal[2]` is not iterable"
|
||||
# error: "Object of type `Literal[4]` is not iterable"
|
||||
# error: [invalid-assignment] "Not enough values to unpack (expected 2, got 1)"
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected 2"
|
||||
# revealed: tuple[Unknown | Literal[3, 5], Unknown | Literal["a", "b"]]
|
||||
[reveal_type((a, b)) for a, b in (1, 2, (3, "a"), 4, (5, "b"), "c")]
|
||||
```
|
||||
|
||||
34
crates/red_knot_python_semantic/resources/primer/bad.txt
Normal file
34
crates/red_knot_python_semantic/resources/primer/bad.txt
Normal file
@@ -0,0 +1,34 @@
|
||||
Tanjun # cycle panic (signature_)
|
||||
aiohttp # missing expression ID
|
||||
alerta # missing expression ID
|
||||
altair # cycle panics (try_metaclass_)
|
||||
antidote # hangs / slow
|
||||
artigraph # cycle panics (value_type_)
|
||||
colour # cycle panics (try_metaclass_)
|
||||
core # cycle panics (value_type_)
|
||||
cpython # missing expression ID, access to field whilst being initialized, too many cycle iterations
|
||||
discord.py # some kind of hang, only when multi-threaded?
|
||||
freqtrade # cycle panics (try_metaclass_)
|
||||
hydpy # cycle panics (try_metaclass_)
|
||||
ibis # cycle panics (try_metaclass_)
|
||||
manticore # stack overflow
|
||||
materialize # stack overflow
|
||||
meson # missing expression ID
|
||||
mypy # cycle panic (signature_)
|
||||
pandas # slow
|
||||
pandas-stubs # cycle panics (try_metaclass_)
|
||||
pandera # cycle panics (try_metaclass_)
|
||||
prefect # slow
|
||||
pytest # cycle panics (signature_), missing expression ID
|
||||
pywin32 # bad use-def map (binding with definitely-visible unbound)
|
||||
schemathesis # cycle panics (signature_)
|
||||
scikit-learn # success, but mypy-primer hangs processing the output
|
||||
scipy # missing expression ID
|
||||
spack # success, but mypy-primer hangs processing the output
|
||||
spark # cycle panics (try_metaclass_)
|
||||
sphinx # missing expression ID
|
||||
steam.py # missing expression ID
|
||||
streamlit # cycle panic (signature_)
|
||||
sympy # stack overflow
|
||||
trio # missing expression ID
|
||||
xarray # cycle panics (try_metaclass_)
|
||||
@@ -1,23 +1,109 @@
|
||||
arrow
|
||||
AutoSplit
|
||||
Expression
|
||||
PyGithub
|
||||
PyWinCtl
|
||||
SinbadCogs
|
||||
aiohttp-devtools
|
||||
aioredis
|
||||
aiortc
|
||||
alectryon
|
||||
anyio
|
||||
apprise
|
||||
arviz
|
||||
async-utils
|
||||
asynq
|
||||
attrs
|
||||
bandersnatch
|
||||
beartype
|
||||
bidict
|
||||
black
|
||||
bokeh
|
||||
boostedblob
|
||||
check-jsonschema
|
||||
cki-lib
|
||||
cloud-init
|
||||
com2ann
|
||||
comtypes
|
||||
cwltool
|
||||
dacite
|
||||
dd-trace-py
|
||||
dedupe
|
||||
django-stubs
|
||||
downforeveryone
|
||||
dragonchain
|
||||
dulwich
|
||||
flake8
|
||||
flake8-pyi
|
||||
git-revise
|
||||
graphql-core
|
||||
httpx-caching
|
||||
hydra-zen
|
||||
ignite
|
||||
imagehash
|
||||
isort
|
||||
itsdangerous
|
||||
janus
|
||||
jax
|
||||
jinja
|
||||
koda-validate
|
||||
kopf
|
||||
kornia
|
||||
mitmproxy
|
||||
mkdocs
|
||||
mkosi
|
||||
mongo-python-driver
|
||||
more-itertools
|
||||
mypy-protobuf
|
||||
mypy_primer
|
||||
nionutils
|
||||
nox
|
||||
openlibrary
|
||||
operator
|
||||
optuna
|
||||
paasta
|
||||
packaging
|
||||
paroxython
|
||||
parso
|
||||
pegen
|
||||
pip
|
||||
poetry
|
||||
porcupine
|
||||
ppb-vector
|
||||
psycopg
|
||||
pwndbg
|
||||
pybind11
|
||||
pycryptodome
|
||||
pydantic
|
||||
pyinstrument
|
||||
pyjwt
|
||||
pylint
|
||||
pylox
|
||||
pyodide
|
||||
pyp
|
||||
pyppeteer
|
||||
pytest-robotframework
|
||||
python-chess
|
||||
python-htmlgen
|
||||
python-sop
|
||||
rclip
|
||||
rich
|
||||
rotki
|
||||
schema_salad
|
||||
scrapy
|
||||
setuptools
|
||||
sockeye
|
||||
speedrun.com_global_scoreboard_webapp
|
||||
starlette
|
||||
static-frame
|
||||
stone
|
||||
tornado
|
||||
twine
|
||||
typeshed-stats
|
||||
urllib3
|
||||
vision
|
||||
websockets
|
||||
werkzeug
|
||||
xarray-dataclasses
|
||||
yarl
|
||||
zipp
|
||||
zulip
|
||||
|
||||
@@ -47,9 +47,10 @@ pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
pub(crate) use class::{
|
||||
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, InstanceType, KnownClass,
|
||||
KnownInstanceType, NonGenericClass,
|
||||
Class, ClassLiteralType, ClassType, GenericAlias, GenericClass, KnownClass, NonGenericClass,
|
||||
};
|
||||
pub(crate) use instance::InstanceType;
|
||||
pub(crate) use known_instance::KnownInstanceType;
|
||||
|
||||
mod builder;
|
||||
mod call;
|
||||
@@ -60,6 +61,8 @@ mod diagnostic;
|
||||
mod display;
|
||||
mod generics;
|
||||
mod infer;
|
||||
mod instance;
|
||||
mod known_instance;
|
||||
mod mro;
|
||||
mod narrow;
|
||||
mod signatures;
|
||||
@@ -339,12 +342,12 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Used as the return type of `dataclass(…)` calls. Keeps track of the arguments
|
||||
/// Used for the return type of `dataclass(…)` calls. Keeps track of the arguments
|
||||
/// that were passed in. For the precise meaning of the fields, see [1].
|
||||
///
|
||||
/// [1]: https://docs.python.org/3/library/dataclasses.html
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct DataclassMetadata: u16 {
|
||||
pub struct DataclassParams: u16 {
|
||||
const INIT = 0b0000_0000_0001;
|
||||
const REPR = 0b0000_0000_0010;
|
||||
const EQ = 0b0000_0000_0100;
|
||||
@@ -358,12 +361,57 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DataclassMetadata {
|
||||
impl Default for DataclassParams {
|
||||
fn default() -> Self {
|
||||
Self::INIT | Self::REPR | Self::EQ | Self::MATCH_ARGS
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DataclassTransformerParams> for DataclassParams {
|
||||
fn from(params: DataclassTransformerParams) -> Self {
|
||||
let mut result = Self::default();
|
||||
|
||||
result.set(
|
||||
Self::EQ,
|
||||
params.contains(DataclassTransformerParams::EQ_DEFAULT),
|
||||
);
|
||||
result.set(
|
||||
Self::ORDER,
|
||||
params.contains(DataclassTransformerParams::ORDER_DEFAULT),
|
||||
);
|
||||
result.set(
|
||||
Self::KW_ONLY,
|
||||
params.contains(DataclassTransformerParams::KW_ONLY_DEFAULT),
|
||||
);
|
||||
result.set(
|
||||
Self::FROZEN,
|
||||
params.contains(DataclassTransformerParams::FROZEN_DEFAULT),
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Used for the return type of `dataclass_transform(…)` calls. Keeps track of the
|
||||
/// arguments that were passed in. For the precise meaning of the fields, see [1].
|
||||
///
|
||||
/// [1]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub struct DataclassTransformerParams: u8 {
|
||||
const EQ_DEFAULT = 0b0000_0001;
|
||||
const ORDER_DEFAULT = 0b0000_0010;
|
||||
const KW_ONLY_DEFAULT = 0b0000_0100;
|
||||
const FROZEN_DEFAULT = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DataclassTransformerParams {
|
||||
fn default() -> Self {
|
||||
Self::EQ_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a type: a set of possible values at runtime.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum Type<'db> {
|
||||
@@ -404,7 +452,9 @@ pub enum Type<'db> {
|
||||
/// A special callable that is returned by a `dataclass(…)` call. It is usually
|
||||
/// used as a decorator. Note that this is only used as a return type for actual
|
||||
/// `dataclass` calls, not for the argumentless `@dataclass` decorator.
|
||||
DataclassDecorator(DataclassMetadata),
|
||||
DataclassDecorator(DataclassParams),
|
||||
/// A special callable that is returned by a `dataclass_transform(…)` call.
|
||||
DataclassTransformer(DataclassTransformerParams),
|
||||
/// The type of an arbitrary callable object with a certain specified signature.
|
||||
Callable(CallableType<'db>),
|
||||
/// A specific module object
|
||||
@@ -415,7 +465,8 @@ pub enum Type<'db> {
|
||||
GenericAlias(GenericAlias<'db>),
|
||||
/// The set of all class objects that are subclasses of the given class (C), spelled `type[C]`.
|
||||
SubclassOf(SubclassOfType<'db>),
|
||||
/// The set of Python objects with the given class in their __class__'s method resolution order
|
||||
/// The set of Python objects with the given class in their __class__'s method resolution order.
|
||||
/// Construct this variant using the `Type::instance` constructor function.
|
||||
Instance(InstanceType<'db>),
|
||||
/// A single Python object that requires special treatment in the type system
|
||||
KnownInstance(KnownInstanceType<'db>),
|
||||
@@ -480,17 +531,20 @@ impl<'db> Type<'db> {
|
||||
|
||||
fn is_none(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType))
|
||||
.is_some_and(|instance| instance.class().is_known(db, KnownClass::NoneType))
|
||||
}
|
||||
|
||||
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType))
|
||||
self.into_instance().is_some_and(|instance| {
|
||||
instance
|
||||
.class()
|
||||
.is_known(db, KnownClass::NotImplementedType)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_object(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_instance()
|
||||
.is_some_and(|instance| instance.class.is_object(db))
|
||||
.is_some_and(|instance| instance.class().is_object(db))
|
||||
}
|
||||
|
||||
pub const fn is_todo(&self) -> bool {
|
||||
@@ -524,7 +578,8 @@ impl<'db> Type<'db> {
|
||||
| Self::BoundMethod(_)
|
||||
| Self::WrapperDescriptor(_)
|
||||
| Self::MethodWrapper(_)
|
||||
| Self::DataclassDecorator(_) => false,
|
||||
| Self::DataclassDecorator(_)
|
||||
| Self::DataclassTransformer(_) => false,
|
||||
|
||||
Self::GenericAlias(generic) => generic
|
||||
.specialization(db)
|
||||
@@ -641,10 +696,6 @@ impl<'db> Type<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
pub const fn is_instance(&self) -> bool {
|
||||
matches!(self, Type::Instance(..))
|
||||
}
|
||||
|
||||
pub const fn is_property_instance(&self) -> bool {
|
||||
matches!(self, Type::PropertyInstance(..))
|
||||
}
|
||||
@@ -745,13 +796,6 @@ impl<'db> Type<'db> {
|
||||
.expect("Expected a Type::IntLiteral variant")
|
||||
}
|
||||
|
||||
pub const fn into_instance(self) -> Option<InstanceType<'db>> {
|
||||
match self {
|
||||
Type::Instance(instance_type) => Some(instance_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn into_known_instance(self) -> Option<KnownInstanceType<'db>> {
|
||||
match self {
|
||||
Type::KnownInstance(known_instance) => Some(known_instance),
|
||||
@@ -780,10 +824,6 @@ impl<'db> Type<'db> {
|
||||
matches!(self, Type::LiteralString)
|
||||
}
|
||||
|
||||
pub const fn instance(class: ClassType<'db>) -> Self {
|
||||
Self::Instance(InstanceType { class })
|
||||
}
|
||||
|
||||
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
|
||||
Self::StringLiteral(StringLiteralType::new(db, string))
|
||||
}
|
||||
@@ -837,7 +877,8 @@ impl<'db> Type<'db> {
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::BoundMethod(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Self::DataclassDecorator(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::KnownInstance(_)
|
||||
@@ -914,7 +955,7 @@ impl<'db> Type<'db> {
|
||||
(_, Type::Never) => false,
|
||||
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::Instance(InstanceType { class })) if class.is_object(db) => true,
|
||||
(_, Type::Instance(instance)) if instance.class().is_object(db) => true,
|
||||
|
||||
// A fully static typevar is always a subtype of itself, and is never a subtype of any
|
||||
// other typevar, since there is no guarantee that they will be specialized to the same
|
||||
@@ -1073,7 +1114,7 @@ impl<'db> Type<'db> {
|
||||
self_callable.is_subtype_of(db, other_callable)
|
||||
}
|
||||
|
||||
(Type::DataclassDecorator(_), _) => {
|
||||
(Type::DataclassDecorator(_) | Type::DataclassTransformer(_), _) => {
|
||||
// TODO: Implement subtyping using an equivalent `Callable` type.
|
||||
false
|
||||
}
|
||||
@@ -1134,6 +1175,29 @@ impl<'db> Type<'db> {
|
||||
self_subclass_ty.is_subtype_of(db, target_subclass_ty)
|
||||
}
|
||||
|
||||
(Type::ClassLiteral(_), Type::Callable(_)) => {
|
||||
let metaclass_call_function_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
"__call__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK
|
||||
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
|
||||
if let Symbol::Type(Type::BoundMethod(metaclass_call_function), _) =
|
||||
metaclass_call_function_symbol
|
||||
{
|
||||
// TODO: this intentionally diverges from step 1 in
|
||||
// https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
|
||||
// by always respecting the signature of the metaclass `__call__`, rather than
|
||||
// using a heuristic which makes unwarranted assumptions to sometimes ignore it.
|
||||
let metaclass_call_function = metaclass_call_function.into_callable_type(db);
|
||||
return metaclass_call_function.is_subtype_of(db, target);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
|
||||
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
|
||||
// is an instance of its metaclass `abc.ABCMeta`.
|
||||
@@ -1213,7 +1277,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
// All types are assignable to `object`.
|
||||
// TODO this special case might be removable once the below cases are comprehensive
|
||||
(_, Type::Instance(InstanceType { class })) if class.is_object(db) => true,
|
||||
(_, Type::Instance(instance)) if instance.class().is_object(db) => true,
|
||||
|
||||
// A typevar is always assignable to itself, and is never assignable to any other
|
||||
// typevar, since there is no guarantee that they will be specialized to the same
|
||||
@@ -1368,13 +1432,13 @@ impl<'db> Type<'db> {
|
||||
|
||||
// TODO: This is a workaround to avoid false positives (e.g. when checking function calls
|
||||
// with `SupportsIndex` parameters), which should be removed when we understand protocols.
|
||||
(lhs, Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::SupportsIndex) =>
|
||||
(lhs, Type::Instance(instance))
|
||||
if instance.class().is_known(db, KnownClass::SupportsIndex) =>
|
||||
{
|
||||
match lhs {
|
||||
Type::Instance(InstanceType { class })
|
||||
Type::Instance(instance)
|
||||
if matches!(
|
||||
class.known(db),
|
||||
instance.class().known(db),
|
||||
Some(KnownClass::Int | KnownClass::SupportsIndex)
|
||||
) =>
|
||||
{
|
||||
@@ -1386,9 +1450,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
// TODO: ditto for avoiding false positives when checking function calls with `Sized` parameters.
|
||||
(lhs, Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Sized) =>
|
||||
{
|
||||
(lhs, Type::Instance(instance)) if instance.class().is_known(db, KnownClass::Sized) => {
|
||||
matches!(
|
||||
lhs.to_meta_type(db).member(db, "__len__"),
|
||||
SymbolAndQualifiers {
|
||||
@@ -1605,6 +1667,7 @@ impl<'db> Type<'db> {
|
||||
| Type::MethodWrapper(..)
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::DataclassDecorator(..)
|
||||
| Type::DataclassTransformer(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
@@ -1621,6 +1684,7 @@ impl<'db> Type<'db> {
|
||||
| Type::MethodWrapper(..)
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::DataclassDecorator(..)
|
||||
| Type::DataclassTransformer(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::SliceLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
@@ -1697,9 +1761,9 @@ impl<'db> Type<'db> {
|
||||
.is_disjoint_from(db, other),
|
||||
},
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::KnownInstance(known_instance)) => {
|
||||
!known_instance.is_instance_of(db, class)
|
||||
(Type::KnownInstance(known_instance), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::KnownInstance(known_instance)) => {
|
||||
!known_instance.is_instance_of(db, instance.class())
|
||||
}
|
||||
|
||||
(known_instance_ty @ Type::KnownInstance(_), Type::Tuple(_))
|
||||
@@ -1707,20 +1771,20 @@ impl<'db> Type<'db> {
|
||||
known_instance_ty.is_disjoint_from(db, KnownClass::Tuple.to_instance(db))
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::BooleanLiteral(..)) => {
|
||||
(Type::BooleanLiteral(..), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::BooleanLiteral(..)) => {
|
||||
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
|
||||
// (it cannot be an instance of a `bool` subclass)
|
||||
!KnownClass::Bool.is_subclass_of(db, class)
|
||||
!KnownClass::Bool.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(..), _) | (_, Type::BooleanLiteral(..)) => true,
|
||||
|
||||
(Type::IntLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::IntLiteral(..)) => {
|
||||
(Type::IntLiteral(..), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::IntLiteral(..)) => {
|
||||
// A `Type::IntLiteral()` must be an instance of exactly `int`
|
||||
// (it cannot be an instance of an `int` subclass)
|
||||
!KnownClass::Int.is_subclass_of(db, class)
|
||||
!KnownClass::Int.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true,
|
||||
@@ -1728,34 +1792,28 @@ impl<'db> Type<'db> {
|
||||
(Type::StringLiteral(..), Type::LiteralString)
|
||||
| (Type::LiteralString, Type::StringLiteral(..)) => false,
|
||||
|
||||
(
|
||||
Type::StringLiteral(..) | Type::LiteralString,
|
||||
Type::Instance(InstanceType { class }),
|
||||
)
|
||||
| (
|
||||
Type::Instance(InstanceType { class }),
|
||||
Type::StringLiteral(..) | Type::LiteralString,
|
||||
) => {
|
||||
(Type::StringLiteral(..) | Type::LiteralString, Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
|
||||
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
|
||||
// (it cannot be an instance of a `str` subclass)
|
||||
!KnownClass::Str.is_subclass_of(db, class)
|
||||
!KnownClass::Str.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => false,
|
||||
(Type::LiteralString, _) | (_, Type::LiteralString) => true,
|
||||
|
||||
(Type::BytesLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::BytesLiteral(..)) => {
|
||||
(Type::BytesLiteral(..), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::BytesLiteral(..)) => {
|
||||
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
|
||||
// (it cannot be an instance of a `bytes` subclass)
|
||||
!KnownClass::Bytes.is_subclass_of(db, class)
|
||||
!KnownClass::Bytes.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
(Type::SliceLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::SliceLiteral(..)) => {
|
||||
(Type::SliceLiteral(..), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::SliceLiteral(..)) => {
|
||||
// A `Type::SliceLiteral` must be an instance of exactly `slice`
|
||||
// (it cannot be an instance of a `slice` subclass)
|
||||
!KnownClass::Slice.is_subclass_of(db, class)
|
||||
!KnownClass::Slice.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
// A class-literal type `X` is always disjoint from an instance type `Y`,
|
||||
@@ -1770,11 +1828,11 @@ impl<'db> Type<'db> {
|
||||
.metaclass_instance_type(db)
|
||||
.is_subtype_of(db, instance),
|
||||
|
||||
(Type::FunctionLiteral(..), Type::Instance(InstanceType { class }))
|
||||
| (Type::Instance(InstanceType { class }), Type::FunctionLiteral(..)) => {
|
||||
(Type::FunctionLiteral(..), Type::Instance(instance))
|
||||
| (Type::Instance(instance), Type::FunctionLiteral(..)) => {
|
||||
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
|
||||
// (it cannot be an instance of a `types.FunctionType` subclass)
|
||||
!KnownClass::FunctionType.is_subclass_of(db, class)
|
||||
!KnownClass::FunctionType.is_subclass_of(db, instance.class())
|
||||
}
|
||||
|
||||
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
|
||||
@@ -1815,8 +1873,14 @@ impl<'db> Type<'db> {
|
||||
true
|
||||
}
|
||||
|
||||
(Type::Callable(_) | Type::DataclassDecorator(_), _)
|
||||
| (_, Type::Callable(_) | Type::DataclassDecorator(_)) => {
|
||||
(
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
_,
|
||||
)
|
||||
| (
|
||||
_,
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
) => {
|
||||
// TODO: Implement disjointness for general callable type with other types
|
||||
false
|
||||
}
|
||||
@@ -1827,13 +1891,7 @@ impl<'db> Type<'db> {
|
||||
other.is_disjoint_from(db, KnownClass::ModuleType.to_instance(db))
|
||||
}
|
||||
|
||||
(
|
||||
Type::Instance(InstanceType { class: left_class }),
|
||||
Type::Instance(InstanceType { class: right_class }),
|
||||
) => {
|
||||
(left_class.is_final(db) && !left_class.is_subclass_of(db, right_class))
|
||||
|| (right_class.is_final(db) && !right_class.is_subclass_of(db, left_class))
|
||||
}
|
||||
(Type::Instance(left), Type::Instance(right)) => left.is_disjoint_from(db, right),
|
||||
|
||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||
let self_elements = tuple.elements(db);
|
||||
@@ -1879,6 +1937,7 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
@@ -2010,10 +2069,8 @@ impl<'db> Type<'db> {
|
||||
// (this variant represents `f.__get__`, where `f` is any function)
|
||||
false
|
||||
}
|
||||
Type::DataclassDecorator(_) => false,
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
class.known(db).is_some_and(KnownClass::is_singleton)
|
||||
}
|
||||
Type::DataclassDecorator(_) | Type::DataclassTransformer(_) => false,
|
||||
Type::Instance(instance) => instance.is_singleton(db),
|
||||
Type::PropertyInstance(_) => false,
|
||||
Type::Tuple(..) => {
|
||||
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
||||
@@ -2085,9 +2142,7 @@ impl<'db> Type<'db> {
|
||||
.iter()
|
||||
.all(|elem| elem.is_single_valued(db)),
|
||||
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
class.known(db).is_some_and(KnownClass::is_single_valued)
|
||||
}
|
||||
Type::Instance(instance) => instance.is_single_valued(db),
|
||||
|
||||
Type::BoundSuper(_) => {
|
||||
// At runtime two super instances never compare equal, even if their arguments are identical.
|
||||
@@ -2103,7 +2158,8 @@ impl<'db> Type<'db> {
|
||||
| Type::AlwaysFalsy
|
||||
| Type::Callable(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::DataclassDecorator(_) => false,
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2227,7 +2283,7 @@ impl<'db> Type<'db> {
|
||||
// We eagerly normalize type[object], i.e. Type::SubclassOf(object) to `type`, i.e. Type::Instance(type).
|
||||
// So looking up a name in the MRO of `Type::Instance(type)` is equivalent to looking up the name in the
|
||||
// MRO of the class `object`.
|
||||
Type::Instance(InstanceType { class }) if class.is_known(db, KnownClass::Type) => {
|
||||
Type::Instance(instance) if instance.class().is_known(db, KnownClass::Type) => {
|
||||
KnownClass::Object
|
||||
.to_class_literal(db)
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
@@ -2239,6 +2295,7 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::AlwaysTruthy
|
||||
@@ -2316,7 +2373,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::Dynamic(_) | Type::Never => Symbol::bound(self).into(),
|
||||
|
||||
Type::Instance(InstanceType { class }) => class.instance_member(db, name),
|
||||
Type::Instance(instance) => instance.class().instance_member(db, name),
|
||||
|
||||
Type::FunctionLiteral(_) => KnownClass::FunctionType
|
||||
.to_instance(db)
|
||||
@@ -2334,7 +2391,9 @@ impl<'db> Type<'db> {
|
||||
Type::DataclassDecorator(_) => KnownClass::FunctionType
|
||||
.to_instance(db)
|
||||
.instance_member(db, name),
|
||||
Type::Callable(_) => KnownClass::Object.to_instance(db).instance_member(db, name),
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => {
|
||||
KnownClass::Object.to_instance(db).instance_member(db, name)
|
||||
}
|
||||
|
||||
Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||
None => KnownClass::Object.to_instance(db).instance_member(db, name),
|
||||
@@ -2751,13 +2810,13 @@ impl<'db> Type<'db> {
|
||||
Type::DataclassDecorator(_) => KnownClass::FunctionType
|
||||
.to_instance(db)
|
||||
.member_lookup_with_policy(db, name, policy),
|
||||
Type::Callable(_) => KnownClass::Object
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object
|
||||
.to_instance(db)
|
||||
.member_lookup_with_policy(db, name, policy),
|
||||
|
||||
Type::Instance(InstanceType { class })
|
||||
Type::Instance(instance)
|
||||
if matches!(name.as_str(), "major" | "minor")
|
||||
&& class.is_known(db, KnownClass::VersionInfo) =>
|
||||
&& instance.class().is_known(db, KnownClass::VersionInfo) =>
|
||||
{
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let segment = if name == "major" {
|
||||
@@ -2827,10 +2886,11 @@ impl<'db> Type<'db> {
|
||||
// attributes on the original type. But in typeshed its return type is `Any`.
|
||||
// It will need a special handling, so it remember the origin type to properly
|
||||
// resolve the attribute.
|
||||
if self.into_instance().is_some_and(|instance| {
|
||||
instance.class.is_known(db, KnownClass::ModuleType)
|
||||
|| instance.class.is_known(db, KnownClass::GenericAlias)
|
||||
}) {
|
||||
if matches!(
|
||||
self.into_instance()
|
||||
.and_then(|instance| instance.class().known(db)),
|
||||
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
|
||||
) {
|
||||
return Symbol::Unbound.into();
|
||||
}
|
||||
|
||||
@@ -3057,6 +3117,7 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::AlwaysTruthy => Truthiness::AlwaysTrue,
|
||||
@@ -3087,7 +3148,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
},
|
||||
|
||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||
Type::Instance(instance) => match instance.class().known(db) {
|
||||
Some(known_class) => known_class.bool(),
|
||||
None => try_dunder_bool()?,
|
||||
},
|
||||
@@ -3364,6 +3425,18 @@ impl<'db> Type<'db> {
|
||||
))
|
||||
}
|
||||
|
||||
// TODO: We should probably also check the original return type of the function
|
||||
// that was decorated with `@dataclass_transform`, to see if it is consistent with
|
||||
// with what we configure here.
|
||||
Type::DataclassTransformer(_) => Signatures::single(CallableSignature::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::new([Parameter::positional_only(Some(Name::new_static("func")))
|
||||
.with_annotated_type(Type::object(db))]),
|
||||
None,
|
||||
),
|
||||
)),
|
||||
|
||||
Type::FunctionLiteral(function_type) => match function_type.known(db) {
|
||||
Some(
|
||||
KnownFunction::IsEquivalentTo
|
||||
@@ -3477,8 +3550,7 @@ impl<'db> Type<'db> {
|
||||
Parameters::new([Parameter::positional_only(Some(
|
||||
Name::new_static("cls"),
|
||||
))
|
||||
// TODO: type[_T]
|
||||
.with_annotated_type(Type::any())]),
|
||||
.with_annotated_type(KnownClass::Type.to_instance(db))]),
|
||||
None,
|
||||
),
|
||||
// TODO: make this overload Python-version-dependent
|
||||
@@ -4266,6 +4338,7 @@ impl<'db> Type<'db> {
|
||||
| Type::BoundMethod(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::Instance(_)
|
||||
| Type::KnownInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
@@ -4336,6 +4409,7 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::Never
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::BoundSuper(_)
|
||||
@@ -4462,7 +4536,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::Dynamic(_) => Ok(*self),
|
||||
|
||||
Type::Instance(InstanceType { class }) => match class.known(db) {
|
||||
Type::Instance(instance) => match instance.class().known(db) {
|
||||
Some(KnownClass::TypeVar) => Ok(todo_type!(
|
||||
"Support for `typing.TypeVar` instances in type expressions"
|
||||
)),
|
||||
@@ -4538,8 +4612,8 @@ impl<'db> Type<'db> {
|
||||
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Type::Never => Type::Never,
|
||||
Type::Instance(InstanceType { class }) => SubclassOfType::from(db, *class),
|
||||
Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db),
|
||||
Type::Instance(instance) => instance.to_meta_type(db),
|
||||
Type::KnownInstance(known_instance) => known_instance.to_meta_type(db),
|
||||
Type::PropertyInstance(_) => KnownClass::Property.to_class_literal(db),
|
||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db),
|
||||
@@ -4551,7 +4625,7 @@ impl<'db> Type<'db> {
|
||||
Type::MethodWrapper(_) => KnownClass::MethodWrapperType.to_class_literal(db),
|
||||
Type::WrapperDescriptor(_) => KnownClass::WrapperDescriptorType.to_class_literal(db),
|
||||
Type::DataclassDecorator(_) => KnownClass::FunctionType.to_class_literal(db),
|
||||
Type::Callable(_) => KnownClass::Type.to_instance(db),
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||
|
||||
@@ -4691,6 +4765,7 @@ impl<'db> Type<'db> {
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::MethodWrapper(MethodWrapperKind::StrStartswith(_))
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
// A non-generic class never needs to be specialized. A generic class is specialized
|
||||
// explicitly (via a subscript expression) or implicitly (via a call), and not because
|
||||
@@ -4772,7 +4847,9 @@ impl<'db> Type<'db> {
|
||||
Some(TypeDefinition::Class(class_literal.definition(db)))
|
||||
}
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
|
||||
Self::Instance(instance) => Some(TypeDefinition::Class(instance.class.definition(db))),
|
||||
Self::Instance(instance) => {
|
||||
Some(TypeDefinition::Class(instance.class().definition(db)))
|
||||
}
|
||||
Self::KnownInstance(instance) => match instance {
|
||||
KnownInstanceType::TypeVar(var) => {
|
||||
Some(TypeDefinition::TypeVar(var.definition(db)))
|
||||
@@ -4797,6 +4874,7 @@ impl<'db> Type<'db> {
|
||||
| Self::MethodWrapper(_)
|
||||
| Self::WrapperDescriptor(_)
|
||||
| Self::DataclassDecorator(_)
|
||||
| Self::DataclassTransformer(_)
|
||||
| Self::PropertyInstance(_)
|
||||
| Self::BoundSuper(_)
|
||||
| Self::Tuple(_) => self.to_meta_type(db).definition(db),
|
||||
@@ -4951,11 +5029,10 @@ impl<'db> InvalidTypeExpressionError<'db> {
|
||||
} = self;
|
||||
if is_reachable {
|
||||
for error in invalid_expressions {
|
||||
context.report_lint_old(
|
||||
&INVALID_TYPE_FORM,
|
||||
node,
|
||||
format_args!("{}", error.reason(context.db())),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, node) else {
|
||||
continue;
|
||||
};
|
||||
builder.into_diagnostic(error.reason(context.db()));
|
||||
}
|
||||
}
|
||||
fallback_type
|
||||
@@ -5140,6 +5217,11 @@ impl<'db> ContextManagerError<'db> {
|
||||
context_expression_type: Type<'db>,
|
||||
context_expression_node: ast::AnyNodeRef,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&INVALID_CONTEXT_MANAGER, context_expression_node)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let format_call_dunder_error = |call_dunder_error: &CallDunderError<'db>, name: &str| {
|
||||
match call_dunder_error {
|
||||
CallDunderError::MethodNotAvailable => format!("it does not implement `{name}`"),
|
||||
@@ -5191,9 +5273,7 @@ impl<'db> ContextManagerError<'db> {
|
||||
} => format_call_dunder_errors(enter_error, "__enter__", exit_error, "__exit__"),
|
||||
};
|
||||
|
||||
context.report_lint_old(
|
||||
&INVALID_CONTEXT_MANAGER,
|
||||
context_expression_node,
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Object of type `{context_expression}` cannot be used with `with` because {formatted_errors}",
|
||||
context_expression = context_expression_type.display(db)
|
||||
@@ -5291,10 +5371,13 @@ impl<'db> IterationError<'db> {
|
||||
iterable_type: Type<'db>,
|
||||
iterable_node: ast::AnyNodeRef,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&NOT_ITERABLE, iterable_node) else {
|
||||
return;
|
||||
};
|
||||
let db = context.db();
|
||||
|
||||
let report_not_iterable = |arguments: std::fmt::Arguments| {
|
||||
context.report_lint_old(&NOT_ITERABLE, iterable_node, arguments);
|
||||
builder.into_diagnostic(arguments);
|
||||
};
|
||||
|
||||
// TODO: for all of these error variants, the "explanation" for the diagnostic
|
||||
@@ -5568,13 +5651,14 @@ impl<'db> BoolError<'db> {
|
||||
}
|
||||
|
||||
fn report_diagnostic_impl(&self, context: &InferContext, condition: TextRange) {
|
||||
let Some(builder) = context.report_lint(&UNSUPPORTED_BOOL_CONVERSION, condition) else {
|
||||
return;
|
||||
};
|
||||
match self {
|
||||
Self::IncorrectArguments {
|
||||
not_boolable_type, ..
|
||||
} => {
|
||||
context.report_lint_old(
|
||||
&UNSUPPORTED_BOOL_CONVERSION,
|
||||
condition,
|
||||
builder.into_diagnostic(
|
||||
format_args!(
|
||||
"Boolean conversion is unsupported for type `{}`; it incorrectly implements `__bool__`",
|
||||
not_boolable_type.display(context.db())
|
||||
@@ -5585,25 +5669,20 @@ impl<'db> BoolError<'db> {
|
||||
not_boolable_type,
|
||||
return_type,
|
||||
} => {
|
||||
context.report_lint_old(
|
||||
&UNSUPPORTED_BOOL_CONVERSION,
|
||||
condition,
|
||||
format_args!(
|
||||
"Boolean conversion is unsupported for type `{not_boolable}`; the return type of its bool method (`{return_type}`) isn't assignable to `bool",
|
||||
not_boolable = not_boolable_type.display(context.db()),
|
||||
return_type = return_type.display(context.db())
|
||||
),
|
||||
);
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Boolean conversion is unsupported for type `{not_boolable}`; \
|
||||
the return type of its bool method (`{return_type}`) \
|
||||
isn't assignable to `bool",
|
||||
not_boolable = not_boolable_type.display(context.db()),
|
||||
return_type = return_type.display(context.db())
|
||||
));
|
||||
}
|
||||
Self::NotCallable { not_boolable_type } => {
|
||||
context.report_lint_old(
|
||||
&UNSUPPORTED_BOOL_CONVERSION,
|
||||
condition,
|
||||
format_args!(
|
||||
"Boolean conversion is unsupported for type `{}`; its `__bool__` method isn't callable",
|
||||
not_boolable_type.display(context.db())
|
||||
),
|
||||
);
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Boolean conversion is unsupported for type `{}`; \
|
||||
its `__bool__` method isn't callable",
|
||||
not_boolable_type.display(context.db())
|
||||
));
|
||||
}
|
||||
Self::Union { union, .. } => {
|
||||
let first_error = union
|
||||
@@ -5612,26 +5691,20 @@ impl<'db> BoolError<'db> {
|
||||
.find_map(|element| element.try_bool(context.db()).err())
|
||||
.unwrap();
|
||||
|
||||
context.report_lint_old(
|
||||
&UNSUPPORTED_BOOL_CONVERSION,
|
||||
condition,
|
||||
format_args!(
|
||||
"Boolean conversion is unsupported for union `{}` because `{}` doesn't implement `__bool__` correctly",
|
||||
Type::Union(*union).display(context.db()),
|
||||
first_error.not_boolable_type().display(context.db()),
|
||||
),
|
||||
);
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Boolean conversion is unsupported for union `{}` \
|
||||
because `{}` doesn't implement `__bool__` correctly",
|
||||
Type::Union(*union).display(context.db()),
|
||||
first_error.not_boolable_type().display(context.db()),
|
||||
));
|
||||
}
|
||||
|
||||
Self::Other { not_boolable_type } => {
|
||||
context.report_lint_old(
|
||||
&UNSUPPORTED_BOOL_CONVERSION,
|
||||
condition,
|
||||
format_args!(
|
||||
"Boolean conversion is unsupported for type `{}`; it incorrectly implements `__bool__`",
|
||||
not_boolable_type.display(context.db())
|
||||
),
|
||||
);
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Boolean conversion is unsupported for type `{}`; \
|
||||
it incorrectly implements `__bool__`",
|
||||
not_boolable_type.display(context.db())
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5662,27 +5735,28 @@ impl<'db> ConstructorCallError<'db> {
|
||||
) {
|
||||
let report_init_error = |call_dunder_error: &CallDunderError<'db>| match call_dunder_error {
|
||||
CallDunderError::MethodNotAvailable => {
|
||||
// If we are using vendored typeshed, it should be impossible to have missing
|
||||
// or unbound `__init__` method on a class, as all classes have `object` in MRO.
|
||||
// Thus the following may only trigger if a custom typeshed is used.
|
||||
context.report_lint_old(
|
||||
&CALL_POSSIBLY_UNBOUND_METHOD,
|
||||
context_expression_node,
|
||||
format_args!(
|
||||
"`__init__` method is missing on type `{}`. Make sure your `object` in typeshed has its definition.",
|
||||
if let Some(builder) =
|
||||
context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node)
|
||||
{
|
||||
// If we are using vendored typeshed, it should be impossible to have missing
|
||||
// or unbound `__init__` method on a class, as all classes have `object` in MRO.
|
||||
// Thus the following may only trigger if a custom typeshed is used.
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`__init__` method is missing on type `{}`. \
|
||||
Make sure your `object` in typeshed has its definition.",
|
||||
context_expression_type.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(bindings) => {
|
||||
context.report_lint_old(
|
||||
&CALL_POSSIBLY_UNBOUND_METHOD,
|
||||
context_expression_node,
|
||||
format_args!(
|
||||
if let Some(builder) =
|
||||
context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__init__` on type `{}` is possibly unbound.",
|
||||
context_expression_type.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
@@ -5698,14 +5772,14 @@ impl<'db> ConstructorCallError<'db> {
|
||||
unreachable!("`__new__` method may not be called if missing");
|
||||
}
|
||||
CallDunderError::PossiblyUnbound(bindings) => {
|
||||
context.report_lint_old(
|
||||
&CALL_POSSIBLY_UNBOUND_METHOD,
|
||||
context_expression_node,
|
||||
format_args!(
|
||||
if let Some(builder) =
|
||||
context.report_lint(&CALL_POSSIBLY_UNBOUND_METHOD, context_expression_node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Method `__new__` on type `{}` is possibly unbound.",
|
||||
context_expression_type.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
bindings.report_diagnostics(context, context_expression_node);
|
||||
}
|
||||
@@ -5860,6 +5934,10 @@ pub struct FunctionType<'db> {
|
||||
/// A set of special decorators that were applied to this function
|
||||
decorators: FunctionDecorators,
|
||||
|
||||
/// The arguments to `dataclass_transformer`, if this function was annotated
|
||||
/// with `@dataclass_transformer(...)`.
|
||||
dataclass_transformer_params: Option<DataclassTransformerParams>,
|
||||
|
||||
/// The generic context of a generic function.
|
||||
generic_context: Option<GenericContext<'db>>,
|
||||
|
||||
@@ -5996,6 +6074,7 @@ impl<'db> FunctionType<'db> {
|
||||
self.known(db),
|
||||
self.body_scope(db),
|
||||
self.decorators(db),
|
||||
self.dataclass_transformer_params(db),
|
||||
Some(generic_context),
|
||||
self.specialization(db),
|
||||
)
|
||||
@@ -6012,6 +6091,7 @@ impl<'db> FunctionType<'db> {
|
||||
self.known(db),
|
||||
self.body_scope(db),
|
||||
self.decorators(db),
|
||||
self.dataclass_transformer_params(db),
|
||||
self.generic_context(db),
|
||||
Some(specialization),
|
||||
)
|
||||
@@ -6056,6 +6136,8 @@ pub enum KnownFunction {
|
||||
GetProtocolMembers,
|
||||
/// `typing(_extensions).runtime_checkable`
|
||||
RuntimeCheckable,
|
||||
/// `typing(_extensions).dataclass_transform`
|
||||
DataclassTransform,
|
||||
|
||||
/// `abc.abstractmethod`
|
||||
#[strum(serialize = "abstractmethod")]
|
||||
@@ -6120,6 +6202,7 @@ impl KnownFunction {
|
||||
| Self::IsProtocol
|
||||
| Self::GetProtocolMembers
|
||||
| Self::RuntimeCheckable
|
||||
| Self::DataclassTransform
|
||||
| Self::NoTypeCheck => {
|
||||
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
|
||||
}
|
||||
@@ -7043,34 +7126,31 @@ impl BoundSuperError<'_> {
|
||||
pub(super) fn report_diagnostic(&self, context: &InferContext, node: AnyNodeRef) {
|
||||
match self {
|
||||
BoundSuperError::InvalidPivotClassType { pivot_class } => {
|
||||
context.report_lint_old(
|
||||
&INVALID_SUPER_ARGUMENT,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`{pivot_class}` is not a valid class",
|
||||
pivot_class = pivot_class.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
BoundSuperError::FailingConditionCheck { pivot_class, owner } => {
|
||||
context.report_lint_old(
|
||||
&INVALID_SUPER_ARGUMENT,
|
||||
node,
|
||||
format_args!(
|
||||
"`{owner}` is not an instance or subclass of `{pivot_class}` in `super({pivot_class}, {owner})` call",
|
||||
if let Some(builder) = context.report_lint(&INVALID_SUPER_ARGUMENT, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"`{owner}` is not an instance or subclass of \
|
||||
`{pivot_class}` in `super({pivot_class}, {owner})` call",
|
||||
pivot_class = pivot_class.display(context.db()),
|
||||
owner = owner.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
BoundSuperError::UnavailableImplicitArguments => {
|
||||
context.report_lint_old(
|
||||
&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) =
|
||||
context.report_lint(&UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS, node)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot determine implicit arguments for 'super()' in this context",
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7088,7 +7168,7 @@ impl<'db> SuperOwnerKind<'db> {
|
||||
match self {
|
||||
SuperOwnerKind::Dynamic(dynamic) => Either::Left(ClassBase::Dynamic(dynamic).mro(db)),
|
||||
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
|
||||
SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)),
|
||||
SuperOwnerKind::Instance(instance) => Either::Right(instance.class().iter_mro(db)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7104,7 +7184,7 @@ impl<'db> SuperOwnerKind<'db> {
|
||||
match self {
|
||||
SuperOwnerKind::Dynamic(_) => None,
|
||||
SuperOwnerKind::Class(class) => Some(class),
|
||||
SuperOwnerKind::Instance(instance) => Some(instance.class),
|
||||
SuperOwnerKind::Instance(instance) => Some(instance.class()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7275,35 +7355,38 @@ impl<'db> BoundSuperType<'db> {
|
||||
policy: MemberLookupPolicy,
|
||||
) -> SymbolAndQualifiers<'db> {
|
||||
let owner = self.owner(db);
|
||||
match owner {
|
||||
SuperOwnerKind::Dynamic(_) => owner
|
||||
.into_type()
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`"),
|
||||
SuperOwnerKind::Class(class) | SuperOwnerKind::Instance(InstanceType { class }) => {
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
// TODO properly support super() with generic types
|
||||
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
|
||||
// * also requires understanding how we should handle cases like this:
|
||||
// ```python
|
||||
// b_int: B[int]
|
||||
// b_unknown: B
|
||||
//
|
||||
// super(B, b_int)
|
||||
// super(B[int], b_unknown)
|
||||
// ```
|
||||
match class_literal {
|
||||
ClassLiteralType::Generic(_) => {
|
||||
Symbol::bound(todo_type!("super in generic class")).into()
|
||||
}
|
||||
ClassLiteralType::NonGeneric(_) => class_literal.class_member_from_mro(
|
||||
db,
|
||||
name,
|
||||
policy,
|
||||
self.skip_until_after_pivot(db, owner.iter_mro(db)),
|
||||
),
|
||||
}
|
||||
let class = match owner {
|
||||
SuperOwnerKind::Dynamic(_) => {
|
||||
return owner
|
||||
.into_type()
|
||||
.find_name_in_mro_with_policy(db, name, policy)
|
||||
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`")
|
||||
}
|
||||
SuperOwnerKind::Class(class) => *class,
|
||||
SuperOwnerKind::Instance(instance) => instance.class(),
|
||||
};
|
||||
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
// TODO properly support super() with generic types
|
||||
// * requires a fix for https://github.com/astral-sh/ruff/issues/17432
|
||||
// * also requires understanding how we should handle cases like this:
|
||||
// ```python
|
||||
// b_int: B[int]
|
||||
// b_unknown: B
|
||||
//
|
||||
// super(B, b_int)
|
||||
// super(B[int], b_unknown)
|
||||
// ```
|
||||
match class_literal {
|
||||
ClassLiteralType::Generic(_) => {
|
||||
Symbol::bound(todo_type!("super in generic class")).into()
|
||||
}
|
||||
ClassLiteralType::NonGeneric(_) => class_literal.class_member_from_mro(
|
||||
db,
|
||||
name,
|
||||
policy,
|
||||
self.skip_until_after_pivot(db, owner.iter_mro(db)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7493,6 +7576,7 @@ pub(crate) mod tests {
|
||||
| KnownFunction::IsProtocol
|
||||
| KnownFunction::GetProtocolMembers
|
||||
| KnownFunction::RuntimeCheckable
|
||||
| KnownFunction::DataclassTransform
|
||||
| KnownFunction::NoTypeCheck => KnownModule::TypingExtensions,
|
||||
|
||||
KnownFunction::IsSingleton
|
||||
|
||||
@@ -44,6 +44,40 @@ use crate::types::{
|
||||
use crate::{Db, FxOrderSet};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum LiteralKind {
|
||||
Int,
|
||||
String,
|
||||
Bytes,
|
||||
}
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
/// Return `true` if this type can be a supertype of some literals of `kind` and not others.
|
||||
fn splits_literals(self, db: &'db dyn Db, kind: LiteralKind) -> bool {
|
||||
match (self, kind) {
|
||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => true,
|
||||
(Type::StringLiteral(_), LiteralKind::String) => true,
|
||||
(Type::BytesLiteral(_), LiteralKind::Bytes) => true,
|
||||
(Type::IntLiteral(_), LiteralKind::Int) => true,
|
||||
(Type::Intersection(intersection), _) => {
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|ty| ty.splits_literals(db, kind))
|
||||
|| intersection
|
||||
.negative(db)
|
||||
.iter()
|
||||
.any(|ty| ty.splits_literals(db, kind))
|
||||
}
|
||||
(Type::Union(union), _) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|ty| ty.splits_literals(db, kind)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum UnionElement<'db> {
|
||||
IntLiterals(FxOrderSet<i64>),
|
||||
StringLiterals(FxOrderSet<StringLiteralType<'db>>),
|
||||
@@ -61,12 +95,9 @@ impl<'db> UnionElement<'db> {
|
||||
/// If this `UnionElement` is some other type, return `ReduceResult::Type` so `UnionBuilder`
|
||||
/// can perform more complex checks on it.
|
||||
fn try_reduce(&mut self, db: &'db dyn Db, other_type: Type<'db>) -> ReduceResult<'db> {
|
||||
// `AlwaysTruthy` and `AlwaysFalsy` are the only types which can be a supertype of only
|
||||
// _some_ literals of the same kind, so we need to walk the full set in this case.
|
||||
let needs_filter = matches!(other_type, Type::AlwaysTruthy | Type::AlwaysFalsy);
|
||||
match self {
|
||||
UnionElement::IntLiterals(literals) => {
|
||||
ReduceResult::KeepIf(if needs_filter {
|
||||
ReduceResult::KeepIf(if other_type.splits_literals(db, LiteralKind::Int) {
|
||||
literals.retain(|literal| {
|
||||
!Type::IntLiteral(*literal).is_subtype_of(db, other_type)
|
||||
});
|
||||
@@ -77,7 +108,7 @@ impl<'db> UnionElement<'db> {
|
||||
})
|
||||
}
|
||||
UnionElement::StringLiterals(literals) => {
|
||||
ReduceResult::KeepIf(if needs_filter {
|
||||
ReduceResult::KeepIf(if other_type.splits_literals(db, LiteralKind::String) {
|
||||
literals.retain(|literal| {
|
||||
!Type::StringLiteral(*literal).is_subtype_of(db, other_type)
|
||||
});
|
||||
@@ -88,7 +119,7 @@ impl<'db> UnionElement<'db> {
|
||||
})
|
||||
}
|
||||
UnionElement::BytesLiterals(literals) => {
|
||||
ReduceResult::KeepIf(if needs_filter {
|
||||
ReduceResult::KeepIf(if other_type.splits_literals(db, LiteralKind::Bytes) {
|
||||
literals.retain(|literal| {
|
||||
!Type::BytesLiteral(*literal).is_subtype_of(db, other_type)
|
||||
});
|
||||
@@ -493,7 +524,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
_ => {
|
||||
let known_instance = new_positive
|
||||
.into_instance()
|
||||
.and_then(|instance| instance.class.known(db));
|
||||
.and_then(|instance| instance.class().known(db));
|
||||
|
||||
if known_instance == Some(KnownClass::Object) {
|
||||
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
|
||||
@@ -513,7 +544,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
new_positive = Type::BooleanLiteral(false);
|
||||
}
|
||||
Type::Instance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Bool) =>
|
||||
if instance.class().is_known(db, KnownClass::Bool) =>
|
||||
{
|
||||
match new_positive {
|
||||
// `bool & AlwaysTruthy` -> `Literal[True]`
|
||||
@@ -607,7 +638,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
self.positive
|
||||
.iter()
|
||||
.filter_map(|ty| ty.into_instance())
|
||||
.filter_map(|instance| instance.class.known(db))
|
||||
.filter_map(|instance| instance.class().known(db))
|
||||
.any(KnownClass::is_bool)
|
||||
};
|
||||
|
||||
@@ -623,7 +654,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
Type::Never => {
|
||||
// Adding ~Never to an intersection is a no-op.
|
||||
}
|
||||
Type::Instance(instance) if instance.class.is_object(db) => {
|
||||
Type::Instance(instance) if instance.class().is_object(db) => {
|
||||
// Adding ~object to an intersection results in Never.
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::Never);
|
||||
|
||||
@@ -19,8 +19,9 @@ use crate::types::diagnostic::{
|
||||
use crate::types::generics::{Specialization, SpecializationBuilder};
|
||||
use crate::types::signatures::{Parameter, ParameterForm};
|
||||
use crate::types::{
|
||||
BoundMethodType, DataclassMetadata, FunctionDecorators, KnownClass, KnownFunction,
|
||||
KnownInstanceType, MethodWrapperKind, PropertyInstanceType, UnionType, WrapperDescriptorKind,
|
||||
BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType,
|
||||
KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType,
|
||||
UnionType, WrapperDescriptorKind,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Severity, Span, SubDiagnostic};
|
||||
use ruff_python_ast as ast;
|
||||
@@ -178,24 +179,23 @@ impl<'db> Bindings<'db> {
|
||||
// If all union elements are not callable, report that the union as a whole is not
|
||||
// callable.
|
||||
if self.into_iter().all(|b| !b.is_callable()) {
|
||||
context.report_lint_old(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
self.callable_type().display(context.db())
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (index, conflicting_form) in self.conflicting_forms.iter().enumerate() {
|
||||
if *conflicting_form {
|
||||
context.report_lint_old(
|
||||
&CONFLICTING_ARGUMENT_FORMS,
|
||||
BindingError::get_node(node, Some(index)),
|
||||
format_args!("Argument is used as both a value and a type form in call"),
|
||||
);
|
||||
let node = BindingError::get_node(node, Some(index));
|
||||
if let Some(builder) = context.report_lint(&CONFLICTING_ARGUMENT_FORMS, node) {
|
||||
builder.into_diagnostic(
|
||||
"Argument is used as both a value and a type form in call",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,8 +210,17 @@ impl<'db> Bindings<'db> {
|
||||
/// Evaluates the return type of certain known callables, where we have special-case logic to
|
||||
/// determine the return type in a way that isn't directly expressible in the type system.
|
||||
fn evaluate_known_cases(&mut self, db: &'db dyn Db) {
|
||||
let to_bool = |ty: &Option<Type<'_>>, default: bool| -> bool {
|
||||
if let Some(Type::BooleanLiteral(value)) = ty {
|
||||
*value
|
||||
} else {
|
||||
// TODO: emit a diagnostic if we receive `bool`
|
||||
default
|
||||
}
|
||||
};
|
||||
|
||||
// Each special case listed here should have a corresponding clause in `Type::signatures`.
|
||||
for binding in &mut self.elements {
|
||||
for (binding, callable_signature) in self.elements.iter_mut().zip(self.signatures.iter()) {
|
||||
let binding_type = binding.callable_type;
|
||||
let Some((overload_index, overload)) = binding.matching_overload_mut() else {
|
||||
continue;
|
||||
@@ -413,6 +422,21 @@ impl<'db> Bindings<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
Type::DataclassTransformer(params) => {
|
||||
if let [Some(Type::FunctionLiteral(function))] = overload.parameter_types() {
|
||||
overload.set_return_type(Type::FunctionLiteral(FunctionType::new(
|
||||
db,
|
||||
function.name(db),
|
||||
function.known(db),
|
||||
function.body_scope(db),
|
||||
function.decorators(db),
|
||||
Some(params),
|
||||
function.generic_context(db),
|
||||
function.specialization(db),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Type::BoundMethod(bound_method)
|
||||
if bound_method.self_instance(db).is_property_instance() =>
|
||||
{
|
||||
@@ -598,53 +622,90 @@ impl<'db> Bindings<'db> {
|
||||
if let [init, repr, eq, order, unsafe_hash, frozen, match_args, kw_only, slots, weakref_slot] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
let to_bool = |ty: &Option<Type<'_>>, default: bool| -> bool {
|
||||
if let Some(Type::BooleanLiteral(value)) = ty {
|
||||
*value
|
||||
} else {
|
||||
// TODO: emit a diagnostic if we receive `bool`
|
||||
default
|
||||
}
|
||||
};
|
||||
|
||||
let mut metadata = DataclassMetadata::empty();
|
||||
let mut params = DataclassParams::empty();
|
||||
|
||||
if to_bool(init, true) {
|
||||
metadata |= DataclassMetadata::INIT;
|
||||
params |= DataclassParams::INIT;
|
||||
}
|
||||
if to_bool(repr, true) {
|
||||
metadata |= DataclassMetadata::REPR;
|
||||
params |= DataclassParams::REPR;
|
||||
}
|
||||
if to_bool(eq, true) {
|
||||
metadata |= DataclassMetadata::EQ;
|
||||
params |= DataclassParams::EQ;
|
||||
}
|
||||
if to_bool(order, false) {
|
||||
metadata |= DataclassMetadata::ORDER;
|
||||
params |= DataclassParams::ORDER;
|
||||
}
|
||||
if to_bool(unsafe_hash, false) {
|
||||
metadata |= DataclassMetadata::UNSAFE_HASH;
|
||||
params |= DataclassParams::UNSAFE_HASH;
|
||||
}
|
||||
if to_bool(frozen, false) {
|
||||
metadata |= DataclassMetadata::FROZEN;
|
||||
params |= DataclassParams::FROZEN;
|
||||
}
|
||||
if to_bool(match_args, true) {
|
||||
metadata |= DataclassMetadata::MATCH_ARGS;
|
||||
params |= DataclassParams::MATCH_ARGS;
|
||||
}
|
||||
if to_bool(kw_only, false) {
|
||||
metadata |= DataclassMetadata::KW_ONLY;
|
||||
params |= DataclassParams::KW_ONLY;
|
||||
}
|
||||
if to_bool(slots, false) {
|
||||
metadata |= DataclassMetadata::SLOTS;
|
||||
params |= DataclassParams::SLOTS;
|
||||
}
|
||||
if to_bool(weakref_slot, false) {
|
||||
metadata |= DataclassMetadata::WEAKREF_SLOT;
|
||||
params |= DataclassParams::WEAKREF_SLOT;
|
||||
}
|
||||
|
||||
overload.set_return_type(Type::DataclassDecorator(metadata));
|
||||
overload.set_return_type(Type::DataclassDecorator(params));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
Some(KnownFunction::DataclassTransform) => {
|
||||
if let [eq_default, order_default, kw_only_default, frozen_default, _field_specifiers, _kwargs] =
|
||||
overload.parameter_types()
|
||||
{
|
||||
let mut params = DataclassTransformerParams::empty();
|
||||
|
||||
if to_bool(eq_default, true) {
|
||||
params |= DataclassTransformerParams::EQ_DEFAULT;
|
||||
}
|
||||
if to_bool(order_default, false) {
|
||||
params |= DataclassTransformerParams::ORDER_DEFAULT;
|
||||
}
|
||||
if to_bool(kw_only_default, false) {
|
||||
params |= DataclassTransformerParams::KW_ONLY_DEFAULT;
|
||||
}
|
||||
if to_bool(frozen_default, false) {
|
||||
params |= DataclassTransformerParams::FROZEN_DEFAULT;
|
||||
}
|
||||
|
||||
overload.set_return_type(Type::DataclassTransformer(params));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let Some(params) = function_type.dataclass_transformer_params(db) {
|
||||
// This is a call to a custom function that was decorated with `@dataclass_transformer`.
|
||||
// If this function was called with a keyword argument like `order=False`, we extract
|
||||
// the argument type and overwrite the corresponding flag in `dataclass_params` after
|
||||
// constructing them from the `dataclass_transformer`-parameter defaults.
|
||||
|
||||
let mut dataclass_params = DataclassParams::from(params);
|
||||
|
||||
if let Some(Some(Type::BooleanLiteral(order))) = callable_signature
|
||||
.iter()
|
||||
.nth(overload_index)
|
||||
.and_then(|signature| {
|
||||
let (idx, _) =
|
||||
signature.parameters().keyword_by_name("order")?;
|
||||
overload.parameter_types().get(idx)
|
||||
})
|
||||
{
|
||||
dataclass_params.set(DataclassParams::ORDER, *order);
|
||||
}
|
||||
|
||||
overload.set_return_type(Type::DataclassDecorator(dataclass_params));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Type::ClassLiteral(class) => match class.known(db) {
|
||||
@@ -845,43 +906,37 @@ impl<'db> CallableBinding<'db> {
|
||||
|
||||
fn report_diagnostics(&self, context: &InferContext<'db>, node: ast::AnyNodeRef) {
|
||||
if !self.is_callable() {
|
||||
context.report_lint_old(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
self.callable_type.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if self.dunder_call_is_possibly_unbound {
|
||||
context.report_lint_old(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||
self.callable_type.display(context.db()),
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let callable_description = CallableDescription::new(context.db(), self.callable_type);
|
||||
if self.overloads.len() > 1 {
|
||||
context.report_lint_old(
|
||||
&NO_MATCHING_OVERLOAD,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&NO_MATCHING_OVERLOAD, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"No overload{} matches arguments",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1382,10 +1437,9 @@ impl<'db> BindingError<'db> {
|
||||
expected_positional_count,
|
||||
provided_positional_count,
|
||||
} => {
|
||||
context.report_lint_old(
|
||||
&TOO_MANY_POSITIONAL_ARGUMENTS,
|
||||
Self::get_node(node, *first_excess_argument_index),
|
||||
format_args!(
|
||||
let node = Self::get_node(node, *first_excess_argument_index);
|
||||
if let Some(builder) = context.report_lint(&TOO_MANY_POSITIONAL_ARGUMENTS, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Too many positional arguments{}: expected \
|
||||
{expected_positional_count}, got {provided_positional_count}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
@@ -1393,75 +1447,70 @@ impl<'db> BindingError<'db> {
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Self::MissingArguments { parameters } => {
|
||||
let s = if parameters.0.len() == 1 { "" } else { "s" };
|
||||
context.report_lint_old(
|
||||
&MISSING_ARGUMENT,
|
||||
node,
|
||||
format_args!(
|
||||
if let Some(builder) = context.report_lint(&MISSING_ARGUMENT, node) {
|
||||
let s = if parameters.0.len() == 1 { "" } else { "s" };
|
||||
builder.into_diagnostic(format_args!(
|
||||
"No argument{s} provided for required parameter{s} {parameters}{}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Self::UnknownArgument {
|
||||
argument_name,
|
||||
argument_index,
|
||||
} => {
|
||||
context.report_lint_old(
|
||||
&UNKNOWN_ARGUMENT,
|
||||
Self::get_node(node, *argument_index),
|
||||
format_args!(
|
||||
let node = Self::get_node(node, *argument_index);
|
||||
if let Some(builder) = context.report_lint(&UNKNOWN_ARGUMENT, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Argument `{argument_name}` does not match any known parameter{}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Self::ParameterAlreadyAssigned {
|
||||
argument_index,
|
||||
parameter,
|
||||
} => {
|
||||
context.report_lint_old(
|
||||
&PARAMETER_ALREADY_ASSIGNED,
|
||||
Self::get_node(node, *argument_index),
|
||||
format_args!(
|
||||
let node = Self::get_node(node, *argument_index);
|
||||
if let Some(builder) = context.report_lint(&PARAMETER_ALREADY_ASSIGNED, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Multiple values provided for parameter {parameter}{}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Self::InternalCallError(reason) => {
|
||||
context.report_lint_old(
|
||||
&CALL_NON_CALLABLE,
|
||||
Self::get_node(node, None),
|
||||
format_args!(
|
||||
let node = Self::get_node(node, None);
|
||||
if let Some(builder) = context.report_lint(&CALL_NON_CALLABLE, node) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Call{} failed: {reason}",
|
||||
if let Some(CallableDescription { kind, name }) = callable_description {
|
||||
format!(" of {kind} `{name}`")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ use std::sync::{LazyLock, Mutex};
|
||||
use super::{
|
||||
class_base::ClassBase, infer_expression_type, infer_unpack_types, IntersectionBuilder,
|
||||
KnownFunction, MemberLookupPolicy, Mro, MroError, MroIterator, SubclassOfType, Truthiness,
|
||||
Type, TypeAliasType, TypeQualifiers, TypeVarInstance,
|
||||
Type, TypeQualifiers,
|
||||
};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::DeclarationWithConstraint;
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{Parameter, Parameters};
|
||||
use crate::types::{CallableType, DataclassMetadata, Signature};
|
||||
use crate::types::{
|
||||
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
|
||||
};
|
||||
use crate::{
|
||||
module_resolver::file_to_module,
|
||||
semantic_index::{
|
||||
@@ -106,7 +108,8 @@ pub struct Class<'db> {
|
||||
|
||||
pub(crate) known: Option<KnownClass>,
|
||||
|
||||
pub(crate) dataclass_metadata: Option<DataclassMetadata>,
|
||||
pub(crate) dataclass_params: Option<DataclassParams>,
|
||||
pub(crate) dataclass_transformer_params: Option<DataclassTransformerParams>,
|
||||
}
|
||||
|
||||
impl<'db> Class<'db> {
|
||||
@@ -469,8 +472,8 @@ impl<'db> ClassLiteralType<'db> {
|
||||
self.class(db).known
|
||||
}
|
||||
|
||||
pub(crate) fn dataclass_metadata(self, db: &'db dyn Db) -> Option<DataclassMetadata> {
|
||||
self.class(db).dataclass_metadata
|
||||
pub(crate) fn dataclass_params(self, db: &'db dyn Db) -> Option<DataclassParams> {
|
||||
self.class(db).dataclass_params
|
||||
}
|
||||
|
||||
/// Return `true` if this class represents `known_class`
|
||||
@@ -699,6 +702,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
/// Return the metaclass of this class, or `type[Unknown]` if the metaclass cannot be inferred.
|
||||
pub(super) fn metaclass(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.try_metaclass(db)
|
||||
.map(|(ty, _)| ty)
|
||||
.unwrap_or_else(|_| SubclassOfType::subclass_of_unknown())
|
||||
}
|
||||
|
||||
@@ -712,7 +716,10 @@ impl<'db> ClassLiteralType<'db> {
|
||||
|
||||
/// Return the metaclass of this class, or an error if the metaclass cannot be inferred.
|
||||
#[salsa::tracked]
|
||||
pub(super) fn try_metaclass(self, db: &'db dyn Db) -> Result<Type<'db>, MetaclassError<'db>> {
|
||||
pub(super) fn try_metaclass(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
) -> Result<(Type<'db>, Option<DataclassTransformerParams>), MetaclassError<'db>> {
|
||||
let class = self.class(db);
|
||||
tracing::trace!("ClassLiteralType::try_metaclass: {}", class.name);
|
||||
|
||||
@@ -723,7 +730,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
// We emit diagnostics for cyclic class definitions elsewhere.
|
||||
// Avoid attempting to infer the metaclass if the class is cyclically defined:
|
||||
// it would be easy to enter an infinite loop.
|
||||
return Ok(SubclassOfType::subclass_of_unknown());
|
||||
return Ok((SubclassOfType::subclass_of_unknown(), None));
|
||||
}
|
||||
|
||||
let explicit_metaclass = self.explicit_metaclass(db);
|
||||
@@ -768,7 +775,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
}),
|
||||
};
|
||||
|
||||
return return_ty_result.map(|ty| ty.to_meta_type(db));
|
||||
return return_ty_result.map(|ty| (ty.to_meta_type(db), None));
|
||||
};
|
||||
|
||||
// Reconcile all base classes' metaclasses with the candidate metaclass.
|
||||
@@ -805,7 +812,10 @@ impl<'db> ClassLiteralType<'db> {
|
||||
});
|
||||
}
|
||||
|
||||
Ok(candidate.metaclass.into())
|
||||
Ok((
|
||||
candidate.metaclass.into(),
|
||||
candidate.metaclass.class(db).dataclass_transformer_params,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns the class member of this class named `name`.
|
||||
@@ -969,12 +979,8 @@ impl<'db> ClassLiteralType<'db> {
|
||||
});
|
||||
|
||||
if symbol.symbol.is_unbound() {
|
||||
if let Some(metadata) = self.dataclass_metadata(db) {
|
||||
if let Some(dataclass_member) =
|
||||
self.own_dataclass_member(db, specialization, metadata, name)
|
||||
{
|
||||
return Symbol::bound(dataclass_member).into();
|
||||
}
|
||||
if let Some(dataclass_member) = self.own_dataclass_member(db, specialization, name) {
|
||||
return Symbol::bound(dataclass_member).into();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,70 +992,97 @@ impl<'db> ClassLiteralType<'db> {
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
specialization: Option<Specialization<'db>>,
|
||||
metadata: DataclassMetadata,
|
||||
name: &str,
|
||||
) -> Option<Type<'db>> {
|
||||
if name == "__init__" && metadata.contains(DataclassMetadata::INIT) {
|
||||
let mut parameters = vec![];
|
||||
let params = self.dataclass_params(db);
|
||||
let has_dataclass_param = |param| params.is_some_and(|params| params.contains(param));
|
||||
|
||||
for (name, (mut attr_ty, mut default_ty)) in self.dataclass_fields(db, specialization) {
|
||||
// The descriptor handling below is guarded by this fully-static check, because dynamic
|
||||
// types like `Any` are valid (data) descriptors: since they have all possible attributes,
|
||||
// they also have a (callable) `__set__` method. The problem is that we can't determine
|
||||
// the type of the value parameter this way. Instead, we want to use the dynamic type
|
||||
// itself in this case, so we skip the special descriptor handling.
|
||||
if attr_ty.is_fully_static(db) {
|
||||
let dunder_set = attr_ty.class_member(db, "__set__".into());
|
||||
if let Some(dunder_set) = dunder_set.symbol.ignore_possibly_unbound() {
|
||||
// This type of this attribute is a data descriptor. Instead of overwriting the
|
||||
// descriptor attribute, data-classes will (implicitly) call the `__set__` method
|
||||
// of the descriptor. This means that the synthesized `__init__` parameter for
|
||||
// this attribute is determined by possible `value` parameter types with which
|
||||
// the `__set__` method can be called. We build a union of all possible options
|
||||
// to account for possible overloads.
|
||||
let mut value_types = UnionBuilder::new(db);
|
||||
for signature in &dunder_set.signatures(db) {
|
||||
for overload in signature {
|
||||
if let Some(value_param) = overload.parameters().get_positional(2) {
|
||||
value_types = value_types.add(
|
||||
value_param.annotated_type().unwrap_or_else(Type::unknown),
|
||||
);
|
||||
} else if overload.parameters().is_gradual() {
|
||||
value_types = value_types.add(Type::unknown());
|
||||
match name {
|
||||
"__init__" => {
|
||||
let has_synthesized_dunder_init = has_dataclass_param(DataclassParams::INIT)
|
||||
|| self
|
||||
.try_metaclass(db)
|
||||
.is_ok_and(|(_, transformer_params)| transformer_params.is_some());
|
||||
|
||||
if !has_synthesized_dunder_init {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parameters = vec![];
|
||||
|
||||
for (name, (mut attr_ty, mut default_ty)) in
|
||||
self.dataclass_fields(db, specialization)
|
||||
{
|
||||
// The descriptor handling below is guarded by this fully-static check, because dynamic
|
||||
// types like `Any` are valid (data) descriptors: since they have all possible attributes,
|
||||
// they also have a (callable) `__set__` method. The problem is that we can't determine
|
||||
// the type of the value parameter this way. Instead, we want to use the dynamic type
|
||||
// itself in this case, so we skip the special descriptor handling.
|
||||
if attr_ty.is_fully_static(db) {
|
||||
let dunder_set = attr_ty.class_member(db, "__set__".into());
|
||||
if let Some(dunder_set) = dunder_set.symbol.ignore_possibly_unbound() {
|
||||
// This type of this attribute is a data descriptor. Instead of overwriting the
|
||||
// descriptor attribute, data-classes will (implicitly) call the `__set__` method
|
||||
// of the descriptor. This means that the synthesized `__init__` parameter for
|
||||
// this attribute is determined by possible `value` parameter types with which
|
||||
// the `__set__` method can be called. We build a union of all possible options
|
||||
// to account for possible overloads.
|
||||
let mut value_types = UnionBuilder::new(db);
|
||||
for signature in &dunder_set.signatures(db) {
|
||||
for overload in signature {
|
||||
if let Some(value_param) =
|
||||
overload.parameters().get_positional(2)
|
||||
{
|
||||
value_types = value_types.add(
|
||||
value_param
|
||||
.annotated_type()
|
||||
.unwrap_or_else(Type::unknown),
|
||||
);
|
||||
} else if overload.parameters().is_gradual() {
|
||||
value_types = value_types.add(Type::unknown());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
attr_ty = value_types.build();
|
||||
attr_ty = value_types.build();
|
||||
|
||||
// The default value of the attribute is *not* determined by the right hand side
|
||||
// of the class-body assignment. Instead, the runtime invokes `__get__` on the
|
||||
// descriptor, as if it had been called on the class itself, i.e. it passes `None`
|
||||
// for the `instance` argument.
|
||||
// The default value of the attribute is *not* determined by the right hand side
|
||||
// of the class-body assignment. Instead, the runtime invokes `__get__` on the
|
||||
// descriptor, as if it had been called on the class itself, i.e. it passes `None`
|
||||
// for the `instance` argument.
|
||||
|
||||
if let Some(ref mut default_ty) = default_ty {
|
||||
*default_ty = default_ty
|
||||
.try_call_dunder_get(db, Type::none(db), Type::ClassLiteral(self))
|
||||
.map(|(return_ty, _)| return_ty)
|
||||
.unwrap_or_else(Type::unknown);
|
||||
if let Some(ref mut default_ty) = default_ty {
|
||||
*default_ty = default_ty
|
||||
.try_call_dunder_get(
|
||||
db,
|
||||
Type::none(db),
|
||||
Type::ClassLiteral(self),
|
||||
)
|
||||
.map(|(return_ty, _)| return_ty)
|
||||
.unwrap_or_else(Type::unknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut parameter =
|
||||
Parameter::positional_or_keyword(name).with_annotated_type(attr_ty);
|
||||
|
||||
if let Some(default_ty) = default_ty {
|
||||
parameter = parameter.with_default_type(default_ty);
|
||||
}
|
||||
|
||||
parameters.push(parameter);
|
||||
}
|
||||
|
||||
let mut parameter =
|
||||
Parameter::positional_or_keyword(name).with_annotated_type(attr_ty);
|
||||
let init_signature =
|
||||
Signature::new(Parameters::new(parameters), Some(Type::none(db)));
|
||||
|
||||
if let Some(default_ty) = default_ty {
|
||||
parameter = parameter.with_default_type(default_ty);
|
||||
}
|
||||
|
||||
parameters.push(parameter);
|
||||
Some(Type::Callable(CallableType::single(db, init_signature)))
|
||||
}
|
||||
"__lt__" | "__le__" | "__gt__" | "__ge__" => {
|
||||
if !has_dataclass_param(DataclassParams::ORDER) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let init_signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
|
||||
|
||||
return Some(Type::Callable(CallableType::single(db, init_signature)));
|
||||
} else if matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__") {
|
||||
if metadata.contains(DataclassMetadata::ORDER) {
|
||||
let signature = Signature::new(
|
||||
Parameters::new([Parameter::positional_or_keyword(Name::new_static("other"))
|
||||
// TODO: could be `Self`.
|
||||
@@ -1059,11 +1092,17 @@ impl<'db> ClassLiteralType<'db> {
|
||||
Some(KnownClass::Bool.to_instance(db)),
|
||||
);
|
||||
|
||||
return Some(Type::Callable(CallableType::single(db, signature)));
|
||||
Some(Type::Callable(CallableType::single(db, signature)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
fn is_dataclass(self, db: &'db dyn Db) -> bool {
|
||||
self.dataclass_params(db).is_some()
|
||||
|| self
|
||||
.try_metaclass(db)
|
||||
.is_ok_and(|(_, transformer_params)| transformer_params.is_some())
|
||||
}
|
||||
|
||||
/// Returns a list of all annotated attributes defined in this class, or any of its superclasses.
|
||||
@@ -1079,7 +1118,7 @@ impl<'db> ClassLiteralType<'db> {
|
||||
.filter_map(|superclass| {
|
||||
if let Some(class) = superclass.into_class() {
|
||||
let class_literal = class.class_literal(db).0;
|
||||
if class_literal.dataclass_metadata(db).is_some() {
|
||||
if class_literal.is_dataclass(db) {
|
||||
Some(class_literal)
|
||||
} else {
|
||||
None
|
||||
@@ -1653,40 +1692,6 @@ impl InheritanceCycle {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the set of runtime objects which are instances of a certain class.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||
pub struct InstanceType<'db> {
|
||||
pub class: ClassType<'db>,
|
||||
}
|
||||
|
||||
impl<'db> InstanceType<'db> {
|
||||
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
|
||||
// N.B. The subclass relation is fully static
|
||||
self.class.is_subclass_of(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
|
||||
self.class.is_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: InstanceType<'db>) -> bool {
|
||||
self.class.is_assignable_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_gradual_equivalent_to(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: InstanceType<'db>,
|
||||
) -> bool {
|
||||
self.class.is_gradual_equivalent_to(db, other.class)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<InstanceType<'db>> for Type<'db> {
|
||||
fn from(value: InstanceType<'db>) -> Self {
|
||||
Self::Instance(value)
|
||||
}
|
||||
}
|
||||
/// Non-exhaustive enumeration of known classes (e.g. `builtins.int`, `typing.Any`, ...) to allow
|
||||
/// for easier syntax when interacting with very common classes.
|
||||
///
|
||||
@@ -2410,357 +2415,6 @@ impl<'db> KnownClassLookupError<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration of specific runtime that are special enough to be considered their own type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum KnownInstanceType<'db> {
|
||||
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
|
||||
Annotated,
|
||||
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
|
||||
Literal,
|
||||
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
|
||||
LiteralString,
|
||||
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
|
||||
Optional,
|
||||
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
|
||||
Union,
|
||||
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
|
||||
NoReturn,
|
||||
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
|
||||
Never,
|
||||
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
|
||||
/// This is not used since typeshed switched to representing `Any` as a class; now we use
|
||||
/// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at
|
||||
/// least for now. TODO maybe remove?
|
||||
Any,
|
||||
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
|
||||
Tuple,
|
||||
/// The symbol `typing.List` (which can also be found as `typing_extensions.List`)
|
||||
List,
|
||||
/// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`)
|
||||
Dict,
|
||||
/// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`)
|
||||
Set,
|
||||
/// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`)
|
||||
FrozenSet,
|
||||
/// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`)
|
||||
ChainMap,
|
||||
/// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`)
|
||||
Counter,
|
||||
/// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`)
|
||||
DefaultDict,
|
||||
/// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`)
|
||||
Deque,
|
||||
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
|
||||
OrderedDict,
|
||||
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
|
||||
Protocol,
|
||||
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
|
||||
Generic,
|
||||
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
|
||||
Type,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
||||
TypeAliasType(TypeAliasType<'db>),
|
||||
/// The symbol `knot_extensions.Unknown`
|
||||
Unknown,
|
||||
/// The symbol `knot_extensions.AlwaysTruthy`
|
||||
AlwaysTruthy,
|
||||
/// The symbol `knot_extensions.AlwaysFalsy`
|
||||
AlwaysFalsy,
|
||||
/// The symbol `knot_extensions.Not`
|
||||
Not,
|
||||
/// The symbol `knot_extensions.Intersection`
|
||||
Intersection,
|
||||
/// The symbol `knot_extensions.TypeOf`
|
||||
TypeOf,
|
||||
/// The symbol `knot_extensions.CallableTypeOf`
|
||||
CallableTypeOf,
|
||||
|
||||
// Various special forms, special aliases and type qualifiers that we don't yet understand
|
||||
// (all currently inferred as TODO in most contexts):
|
||||
TypingSelf,
|
||||
Final,
|
||||
ClassVar,
|
||||
Callable,
|
||||
Concatenate,
|
||||
Unpack,
|
||||
Required,
|
||||
NotRequired,
|
||||
TypeAlias,
|
||||
TypeGuard,
|
||||
TypeIs,
|
||||
ReadOnly,
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
|
||||
impl<'db> KnownInstanceType<'db> {
|
||||
/// Evaluate the known instance in boolean context
|
||||
pub(crate) const fn bool(self) -> Truthiness {
|
||||
match self {
|
||||
Self::Annotated
|
||||
| Self::Literal
|
||||
| Self::LiteralString
|
||||
| Self::Optional
|
||||
| Self::TypeVar(_)
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
| Self::Never
|
||||
| Self::Any
|
||||
| Self::Tuple
|
||||
| Self::Type
|
||||
| Self::TypingSelf
|
||||
| Self::Final
|
||||
| Self::ClassVar
|
||||
| Self::Callable
|
||||
| Self::Concatenate
|
||||
| Self::Unpack
|
||||
| Self::Required
|
||||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypeIs
|
||||
| Self::List
|
||||
| Self::Dict
|
||||
| Self::DefaultDict
|
||||
| Self::Set
|
||||
| Self::FrozenSet
|
||||
| Self::Counter
|
||||
| Self::Deque
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict
|
||||
| Self::Protocol
|
||||
| Self::Generic
|
||||
| Self::ReadOnly
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::Unknown
|
||||
| Self::AlwaysTruthy
|
||||
| Self::AlwaysFalsy
|
||||
| Self::Not
|
||||
| Self::Intersection
|
||||
| Self::TypeOf
|
||||
| Self::CallableTypeOf => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the repr of the symbol at runtime
|
||||
pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str {
|
||||
match self {
|
||||
Self::Annotated => "typing.Annotated",
|
||||
Self::Literal => "typing.Literal",
|
||||
Self::LiteralString => "typing.LiteralString",
|
||||
Self::Optional => "typing.Optional",
|
||||
Self::Union => "typing.Union",
|
||||
Self::NoReturn => "typing.NoReturn",
|
||||
Self::Never => "typing.Never",
|
||||
Self::Any => "typing.Any",
|
||||
Self::Tuple => "typing.Tuple",
|
||||
Self::Type => "typing.Type",
|
||||
Self::TypingSelf => "typing.Self",
|
||||
Self::Final => "typing.Final",
|
||||
Self::ClassVar => "typing.ClassVar",
|
||||
Self::Callable => "typing.Callable",
|
||||
Self::Concatenate => "typing.Concatenate",
|
||||
Self::Unpack => "typing.Unpack",
|
||||
Self::Required => "typing.Required",
|
||||
Self::NotRequired => "typing.NotRequired",
|
||||
Self::TypeAlias => "typing.TypeAlias",
|
||||
Self::TypeGuard => "typing.TypeGuard",
|
||||
Self::TypeIs => "typing.TypeIs",
|
||||
Self::List => "typing.List",
|
||||
Self::Dict => "typing.Dict",
|
||||
Self::DefaultDict => "typing.DefaultDict",
|
||||
Self::Set => "typing.Set",
|
||||
Self::FrozenSet => "typing.FrozenSet",
|
||||
Self::Counter => "typing.Counter",
|
||||
Self::Deque => "typing.Deque",
|
||||
Self::ChainMap => "typing.ChainMap",
|
||||
Self::OrderedDict => "typing.OrderedDict",
|
||||
Self::Protocol => "typing.Protocol",
|
||||
Self::Generic => "typing.Generic",
|
||||
Self::ReadOnly => "typing.ReadOnly",
|
||||
Self::TypeVar(typevar) => typevar.name(db),
|
||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||
Self::Unknown => "knot_extensions.Unknown",
|
||||
Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy",
|
||||
Self::AlwaysFalsy => "knot_extensions.AlwaysFalsy",
|
||||
Self::Not => "knot_extensions.Not",
|
||||
Self::Intersection => "knot_extensions.Intersection",
|
||||
Self::TypeOf => "knot_extensions.TypeOf",
|
||||
Self::CallableTypeOf => "knot_extensions.CallableTypeOf",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`KnownClass`] which this symbol is an instance of
|
||||
pub(crate) const fn class(self) -> KnownClass {
|
||||
match self {
|
||||
Self::Annotated => KnownClass::SpecialForm,
|
||||
Self::Literal => KnownClass::SpecialForm,
|
||||
Self::LiteralString => KnownClass::SpecialForm,
|
||||
Self::Optional => KnownClass::SpecialForm,
|
||||
Self::Union => KnownClass::SpecialForm,
|
||||
Self::NoReturn => KnownClass::SpecialForm,
|
||||
Self::Never => KnownClass::SpecialForm,
|
||||
Self::Any => KnownClass::Object,
|
||||
Self::Tuple => KnownClass::SpecialForm,
|
||||
Self::Type => KnownClass::SpecialForm,
|
||||
Self::TypingSelf => KnownClass::SpecialForm,
|
||||
Self::Final => KnownClass::SpecialForm,
|
||||
Self::ClassVar => KnownClass::SpecialForm,
|
||||
Self::Callable => KnownClass::SpecialForm,
|
||||
Self::Concatenate => KnownClass::SpecialForm,
|
||||
Self::Unpack => KnownClass::SpecialForm,
|
||||
Self::Required => KnownClass::SpecialForm,
|
||||
Self::NotRequired => KnownClass::SpecialForm,
|
||||
Self::TypeAlias => KnownClass::SpecialForm,
|
||||
Self::TypeGuard => KnownClass::SpecialForm,
|
||||
Self::TypeIs => KnownClass::SpecialForm,
|
||||
Self::ReadOnly => KnownClass::SpecialForm,
|
||||
Self::List => KnownClass::StdlibAlias,
|
||||
Self::Dict => KnownClass::StdlibAlias,
|
||||
Self::DefaultDict => KnownClass::StdlibAlias,
|
||||
Self::Set => KnownClass::StdlibAlias,
|
||||
Self::FrozenSet => KnownClass::StdlibAlias,
|
||||
Self::Counter => KnownClass::StdlibAlias,
|
||||
Self::Deque => KnownClass::StdlibAlias,
|
||||
Self::ChainMap => KnownClass::StdlibAlias,
|
||||
Self::OrderedDict => KnownClass::StdlibAlias,
|
||||
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
|
||||
Self::Generic => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
Self::TypeOf => KnownClass::SpecialForm,
|
||||
Self::Not => KnownClass::SpecialForm,
|
||||
Self::Intersection => KnownClass::SpecialForm,
|
||||
Self::CallableTypeOf => KnownClass::SpecialForm,
|
||||
Self::Unknown => KnownClass::Object,
|
||||
Self::AlwaysTruthy => KnownClass::Object,
|
||||
Self::AlwaysFalsy => KnownClass::Object,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the instance type which this type is a subtype of.
|
||||
///
|
||||
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
|
||||
/// so `KnownInstanceType::Literal.instance_fallback(db)`
|
||||
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
|
||||
pub(super) fn instance_fallback(self, db: &dyn Db) -> Type {
|
||||
self.class().to_instance(db)
|
||||
}
|
||||
|
||||
/// Return `true` if this symbol is an instance of `class`.
|
||||
pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool {
|
||||
self.class().is_subclass_of(db, class)
|
||||
}
|
||||
|
||||
pub(super) fn try_from_file_and_name(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
symbol_name: &str,
|
||||
) -> Option<Self> {
|
||||
let candidate = match symbol_name {
|
||||
"Any" => Self::Any,
|
||||
"ClassVar" => Self::ClassVar,
|
||||
"Deque" => Self::Deque,
|
||||
"List" => Self::List,
|
||||
"Dict" => Self::Dict,
|
||||
"DefaultDict" => Self::DefaultDict,
|
||||
"Set" => Self::Set,
|
||||
"FrozenSet" => Self::FrozenSet,
|
||||
"Counter" => Self::Counter,
|
||||
"ChainMap" => Self::ChainMap,
|
||||
"OrderedDict" => Self::OrderedDict,
|
||||
"Generic" => Self::Generic,
|
||||
"Protocol" => Self::Protocol,
|
||||
"Optional" => Self::Optional,
|
||||
"Union" => Self::Union,
|
||||
"NoReturn" => Self::NoReturn,
|
||||
"Tuple" => Self::Tuple,
|
||||
"Type" => Self::Type,
|
||||
"Callable" => Self::Callable,
|
||||
"Annotated" => Self::Annotated,
|
||||
"Literal" => Self::Literal,
|
||||
"Never" => Self::Never,
|
||||
"Self" => Self::TypingSelf,
|
||||
"Final" => Self::Final,
|
||||
"Unpack" => Self::Unpack,
|
||||
"Required" => Self::Required,
|
||||
"TypeAlias" => Self::TypeAlias,
|
||||
"TypeGuard" => Self::TypeGuard,
|
||||
"TypeIs" => Self::TypeIs,
|
||||
"ReadOnly" => Self::ReadOnly,
|
||||
"Concatenate" => Self::Concatenate,
|
||||
"NotRequired" => Self::NotRequired,
|
||||
"LiteralString" => Self::LiteralString,
|
||||
"Unknown" => Self::Unknown,
|
||||
"AlwaysTruthy" => Self::AlwaysTruthy,
|
||||
"AlwaysFalsy" => Self::AlwaysFalsy,
|
||||
"Not" => Self::Not,
|
||||
"Intersection" => Self::Intersection,
|
||||
"TypeOf" => Self::TypeOf,
|
||||
"CallableTypeOf" => Self::CallableTypeOf,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
candidate
|
||||
.check_module(file_to_module(db, file)?.known()?)
|
||||
.then_some(candidate)
|
||||
}
|
||||
|
||||
/// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate.
|
||||
///
|
||||
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
|
||||
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
|
||||
pub(super) fn check_module(self, module: KnownModule) -> bool {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::ClassVar
|
||||
| Self::Deque
|
||||
| Self::List
|
||||
| Self::Dict
|
||||
| Self::DefaultDict
|
||||
| Self::Set
|
||||
| Self::FrozenSet
|
||||
| Self::Counter
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict
|
||||
| Self::Optional
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
| Self::Tuple
|
||||
| Self::Type
|
||||
| Self::Generic
|
||||
| Self::Callable => module.is_typing(),
|
||||
Self::Annotated
|
||||
| Self::Protocol
|
||||
| Self::Literal
|
||||
| Self::LiteralString
|
||||
| Self::Never
|
||||
| Self::TypingSelf
|
||||
| Self::Final
|
||||
| Self::Concatenate
|
||||
| Self::Unpack
|
||||
| Self::Required
|
||||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypeIs
|
||||
| Self::ReadOnly
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::TypeVar(_) => {
|
||||
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
|
||||
}
|
||||
Self::Unknown
|
||||
| Self::AlwaysTruthy
|
||||
| Self::AlwaysFalsy
|
||||
| Self::Not
|
||||
| Self::Intersection
|
||||
| Self::TypeOf
|
||||
| Self::CallableTypeOf => module.is_knot_extensions(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
|
||||
pub(super) struct MetaclassError<'db> {
|
||||
kind: MetaclassErrorKind<'db>,
|
||||
|
||||
@@ -90,6 +90,7 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::MethodWrapper(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
|
||||
@@ -65,6 +65,14 @@ impl<'db> InferContext<'db> {
|
||||
Span::from(self.file()).with_range(ranged.range())
|
||||
}
|
||||
|
||||
/// Create a secondary annotation attached to the range of the given value in
|
||||
/// the file currently being type checked.
|
||||
///
|
||||
/// The annotation returned has no message attached to it.
|
||||
pub(crate) fn secondary<T: Ranged>(&self, ranged: T) -> Annotation {
|
||||
Annotation::secondary(self.span(ranged))
|
||||
}
|
||||
|
||||
pub(crate) fn db(&self) -> &'db dyn Db {
|
||||
self.db
|
||||
}
|
||||
@@ -73,22 +81,6 @@ impl<'db> InferContext<'db> {
|
||||
self.diagnostics.get_mut().extend(other);
|
||||
}
|
||||
|
||||
/// Reports a lint located at `ranged`.
|
||||
pub(super) fn report_lint_old<T>(
|
||||
&self,
|
||||
lint: &'static LintMetadata,
|
||||
ranged: T,
|
||||
message: fmt::Arguments,
|
||||
) where
|
||||
T: Ranged,
|
||||
{
|
||||
let Some(builder) = self.report_lint(lint, ranged) else {
|
||||
return;
|
||||
};
|
||||
let mut diag = builder.into_diagnostic("");
|
||||
diag.set_primary_message(message);
|
||||
}
|
||||
|
||||
/// Optionally return a builder for a lint diagnostic guard.
|
||||
///
|
||||
/// If the current context believes a diagnostic should be reported for
|
||||
@@ -396,7 +388,6 @@ impl<'db, 'ctx> LintDiagnosticGuardBuilder<'db, 'ctx> {
|
||||
///
|
||||
/// The diagnostic can be further mutated on the guard via its `DerefMut`
|
||||
/// impl to `Diagnostic`.
|
||||
#[must_use]
|
||||
pub(super) fn into_diagnostic(
|
||||
self,
|
||||
message: impl std::fmt::Display,
|
||||
@@ -533,7 +524,6 @@ impl<'db, 'ctx> DiagnosticGuardBuilder<'db, 'ctx> {
|
||||
///
|
||||
/// The diagnostic can be further mutated on the guard via its `DerefMut`
|
||||
/// impl to `Diagnostic`.
|
||||
#[must_use]
|
||||
pub(super) fn into_diagnostic(
|
||||
self,
|
||||
message: impl std::fmt::Display,
|
||||
|
||||
@@ -1075,14 +1075,13 @@ pub(super) fn report_index_out_of_bounds(
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&INDEX_OUT_OF_BOUNDS,
|
||||
node,
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INDEX_OUT_OF_BOUNDS, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
@@ -1092,22 +1091,20 @@ pub(super) fn report_non_subscriptable(
|
||||
non_subscriptable_ty: Type,
|
||||
method: &str,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&NON_SUBSCRIPTABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&NON_SUBSCRIPTABLE, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(super) fn report_slice_step_size_zero(context: &InferContext, node: AnyNodeRef) {
|
||||
context.report_lint_old(
|
||||
&ZERO_STEPSIZE_IN_SLICE,
|
||||
node,
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&ZERO_STEPSIZE_IN_SLICE, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic("Slice step size can not be zero");
|
||||
}
|
||||
|
||||
fn report_invalid_assignment_with_message(
|
||||
@@ -1116,19 +1113,26 @@ fn report_invalid_assignment_with_message(
|
||||
target_ty: Type,
|
||||
message: std::fmt::Arguments,
|
||||
) {
|
||||
let Some(builder) = context.report_lint(&INVALID_ASSIGNMENT, node) else {
|
||||
return;
|
||||
};
|
||||
match target_ty {
|
||||
Type::ClassLiteral(class) => {
|
||||
context.report_lint_old(&INVALID_ASSIGNMENT, node, format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(context.db())));
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Implicit shadowing of class `{}`",
|
||||
class.name(context.db()),
|
||||
));
|
||||
diag.info("Annotate to make it explicit if this is intentional");
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
context.report_lint_old(&INVALID_ASSIGNMENT, node, format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(context.db())));
|
||||
let mut diag = builder.into_diagnostic(format_args!(
|
||||
"Implicit shadowing of function `{}`",
|
||||
function.name(context.db()),
|
||||
));
|
||||
diag.info("Annotate to make it explicit if this is intentional");
|
||||
}
|
||||
_ => {
|
||||
context.report_lint_old(&INVALID_ASSIGNMENT, node, message);
|
||||
builder.into_diagnostic(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1202,21 +1206,21 @@ pub(super) fn report_implicit_return_type(
|
||||
range: impl Ranged,
|
||||
expected_ty: Type,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&INVALID_RETURN_TYPE,
|
||||
range,
|
||||
format_args!(
|
||||
"Function can implicitly return `None`, which is not assignable to return type `{}`",
|
||||
expected_ty.display(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_RETURN_TYPE, range) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Function can implicitly return `None`, which is not assignable to return type `{}`",
|
||||
expected_ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_type_checking_constant(context: &InferContext, node: AnyNodeRef) {
|
||||
context.report_lint_old(
|
||||
&INVALID_TYPE_CHECKING_CONSTANT,
|
||||
node,
|
||||
format_args!("The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to it.",),
|
||||
let Some(builder) = context.report_lint(&INVALID_TYPE_CHECKING_CONSTANT, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(
|
||||
"The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to it",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1224,13 +1228,12 @@ pub(super) fn report_possibly_unresolved_reference(
|
||||
context: &InferContext,
|
||||
expr_name_node: &ast::ExprName,
|
||||
) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
let Some(builder) = context.report_lint(&POSSIBLY_UNRESOLVED_REFERENCE, expr_name_node) else {
|
||||
return;
|
||||
};
|
||||
|
||||
context.report_lint_old(
|
||||
&POSSIBLY_UNRESOLVED_REFERENCE,
|
||||
expr_name_node,
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
builder.into_diagnostic(format_args!("Name `{id}` used when possibly not defined"));
|
||||
}
|
||||
|
||||
pub(super) fn report_possibly_unbound_attribute(
|
||||
@@ -1239,93 +1242,86 @@ pub(super) fn report_possibly_unbound_attribute(
|
||||
attribute: &str,
|
||||
object_ty: Type,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&POSSIBLY_UNBOUND_ATTRIBUTE,
|
||||
target,
|
||||
format_args!(
|
||||
"Attribute `{attribute}` on type `{}` is possibly unbound",
|
||||
object_ty.display(context.db()),
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&POSSIBLY_UNBOUND_ATTRIBUTE, target) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Attribute `{attribute}` on type `{}` is possibly unbound",
|
||||
object_ty.display(context.db()),
|
||||
));
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_reference(context: &InferContext, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
let Some(builder) = context.report_lint(&UNRESOLVED_REFERENCE, expr_name_node) else {
|
||||
return;
|
||||
};
|
||||
|
||||
context.report_lint_old(
|
||||
&UNRESOLVED_REFERENCE,
|
||||
expr_name_node,
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
builder.into_diagnostic(format_args!("Name `{id}` used when not defined"));
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_exception_caught(context: &InferContext, node: &ast::Expr, ty: Type) {
|
||||
context.report_lint_old(
|
||||
&INVALID_EXCEPTION_CAUGHT,
|
||||
node,
|
||||
format_args!(
|
||||
"Cannot catch object of type `{}` in an exception handler \
|
||||
let Some(builder) = context.report_lint(&INVALID_EXCEPTION_CAUGHT, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot catch object of type `{}` in an exception handler \
|
||||
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
|
||||
ty.display(context.db())
|
||||
),
|
||||
);
|
||||
ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_exception_raised(context: &InferContext, node: &ast::Expr, ty: Type) {
|
||||
context.report_lint_old(
|
||||
&INVALID_RAISE,
|
||||
node,
|
||||
format_args!(
|
||||
"Cannot raise object of type `{}` (must be a `BaseException` subclass or instance)",
|
||||
ty.display(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_RAISE, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot raise object of type `{}` (must be a `BaseException` subclass or instance)",
|
||||
ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_exception_cause(context: &InferContext, node: &ast::Expr, ty: Type) {
|
||||
context.report_lint_old(
|
||||
&INVALID_RAISE,
|
||||
node,
|
||||
format_args!(
|
||||
"Cannot use object of type `{}` as exception cause \
|
||||
(must be a `BaseException` subclass or instance or `None`)",
|
||||
ty.display(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_RAISE, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot use object of type `{}` as exception cause \
|
||||
(must be a `BaseException` subclass or instance or `None`)",
|
||||
ty.display(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn report_base_with_incompatible_slots(context: &InferContext, node: &ast::Expr) {
|
||||
context.report_lint_old(
|
||||
&INCOMPATIBLE_SLOTS,
|
||||
node,
|
||||
format_args!("Class base has incompatible `__slots__`"),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INCOMPATIBLE_SLOTS, node) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic("Class base has incompatible `__slots__`");
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_arguments_to_annotated(
|
||||
context: &InferContext,
|
||||
subscript: &ast::ExprSubscript,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&INVALID_TYPE_FORM,
|
||||
subscript,
|
||||
format_args!(
|
||||
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
|
||||
KnownInstanceType::Annotated.repr(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, subscript) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Special form `{}` expected at least 2 arguments \
|
||||
(one type and at least one metadata element)",
|
||||
KnownInstanceType::Annotated.repr(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn report_invalid_arguments_to_callable(
|
||||
context: &InferContext,
|
||||
subscript: &ast::ExprSubscript,
|
||||
) {
|
||||
context.report_lint_old(
|
||||
&INVALID_TYPE_FORM,
|
||||
subscript,
|
||||
format_args!(
|
||||
"Special form `{}` expected exactly two arguments (parameter types and return type)",
|
||||
KnownInstanceType::Callable.repr(context.db())
|
||||
),
|
||||
);
|
||||
let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, subscript) else {
|
||||
return;
|
||||
};
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Special form `{}` expected exactly two arguments (parameter types and return type)",
|
||||
KnownInstanceType::Callable.repr(context.db())
|
||||
));
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use crate::types::class::{ClassType, GenericAlias, GenericClass};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::{
|
||||
FunctionSignature, InstanceType, IntersectionType, KnownClass, MethodWrapperKind,
|
||||
StringLiteralType, SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
UnionType, WrapperDescriptorKind,
|
||||
FunctionSignature, IntersectionType, KnownClass, MethodWrapperKind, StringLiteralType,
|
||||
SubclassOfInner, Type, TypeVarBoundOrConstraints, TypeVarInstance, UnionType,
|
||||
WrapperDescriptorKind,
|
||||
};
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -73,7 +73,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||
match self.ty {
|
||||
Type::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::Instance(InstanceType { class }) => match (class, class.known(self.db)) {
|
||||
Type::Instance(instance) => match (instance.class(), instance.class().known(self.db)) {
|
||||
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
|
||||
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
|
||||
(ClassType::NonGeneric(class), _) => f.write_str(&class.class(self.db).name),
|
||||
@@ -195,7 +195,10 @@ impl Display for DisplayRepresentation<'_> {
|
||||
write!(f, "<wrapper-descriptor `{method}` of `{object}` objects>")
|
||||
}
|
||||
Type::DataclassDecorator(_) => {
|
||||
f.write_str("<decorator produced by dataclasses.dataclass>")
|
||||
f.write_str("<decorator produced by dataclass-like function>")
|
||||
}
|
||||
Type::DataclassTransformer(_) => {
|
||||
f.write_str("<decorator produced by typing.dataclass_transform>")
|
||||
}
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
94
crates/red_knot_python_semantic/src/types/instance.rs
Normal file
94
crates/red_knot_python_semantic/src/types/instance.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! Instance types: both nominal and structural.
|
||||
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||
use crate::Db;
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) const fn instance(class: ClassType<'db>) -> Self {
|
||||
Self::Instance(InstanceType { class })
|
||||
}
|
||||
|
||||
pub(crate) const fn into_instance(self) -> Option<InstanceType<'db>> {
|
||||
match self {
|
||||
Type::Instance(instance_type) => Some(instance_type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the set of runtime objects which are instances of a certain nominal class.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update)]
|
||||
pub struct InstanceType<'db> {
|
||||
// Keep this field private, so that the only way of constructing `InstanceType` instances
|
||||
// is through the `Type::instance` constructor function.
|
||||
class: ClassType<'db>,
|
||||
}
|
||||
|
||||
impl<'db> InstanceType<'db> {
|
||||
pub(super) fn class(self) -> ClassType<'db> {
|
||||
self.class
|
||||
}
|
||||
|
||||
pub(super) fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
// N.B. The subclass relation is fully static
|
||||
self.class.is_subclass_of(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_assignable_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_assignable_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_disjoint_from(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
if self.class.is_final(db) && !self.class.is_subclass_of(db, other.class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if other.class.is_final(db) && !other.class.is_subclass_of(db, self.class) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check to see whether the metaclasses of `self` and `other` are disjoint.
|
||||
// Avoid this check if the metaclass of either `self` or `other` is `type`,
|
||||
// however, since we end up with infinite recursion in that case due to the fact
|
||||
// that `type` is its own metaclass (and we know that `type` cannot be disjoint
|
||||
// from any metaclass, anyway).
|
||||
let type_type = KnownClass::Type.to_instance(db);
|
||||
let self_metaclass = self.class.metaclass_instance_type(db);
|
||||
if self_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
let other_metaclass = other.class.metaclass_instance_type(db);
|
||||
if other_metaclass == type_type {
|
||||
return false;
|
||||
}
|
||||
self_metaclass.is_disjoint_from(db, other_metaclass)
|
||||
}
|
||||
|
||||
pub(super) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_gradual_equivalent_to(db, other.class)
|
||||
}
|
||||
|
||||
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
|
||||
self.class.known(db).is_some_and(KnownClass::is_singleton)
|
||||
}
|
||||
|
||||
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
self.class
|
||||
.known(db)
|
||||
.is_some_and(KnownClass::is_single_valued)
|
||||
}
|
||||
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
SubclassOfType::from(db, self.class)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<InstanceType<'db>> for Type<'db> {
|
||||
fn from(value: InstanceType<'db>) -> Self {
|
||||
Self::Instance(value)
|
||||
}
|
||||
}
|
||||
372
crates/red_knot_python_semantic/src/types/known_instance.rs
Normal file
372
crates/red_knot_python_semantic/src/types/known_instance.rs
Normal file
@@ -0,0 +1,372 @@
|
||||
//! The `KnownInstance` type.
|
||||
//!
|
||||
//! Despite its name, this is quite a different type from [`super::InstanceType`].
|
||||
//! For the vast majority of instance-types in Python, we cannot say how many possible
|
||||
//! inhabitants there are or could be of that type at runtime. Each variant of the
|
||||
//! [`KnownInstanceType`] enum, however, represents a specific runtime symbol
|
||||
//! that requires heavy special-casing in the type system. Thus any one `KnownInstance`
|
||||
//! variant can only be inhabited by one or two specific objects at runtime with
|
||||
//! locations that are known in advance.
|
||||
|
||||
use super::{class::KnownClass, ClassType, Truthiness, Type, TypeAliasType, TypeVarInstance};
|
||||
use crate::db::Db;
|
||||
use crate::module_resolver::{file_to_module, KnownModule};
|
||||
use ruff_db::files::File;
|
||||
|
||||
/// Enumeration of specific runtime symbols that are special enough
|
||||
/// that they can each be considered to inhabit a unique type.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub enum KnownInstanceType<'db> {
|
||||
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
|
||||
Annotated,
|
||||
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
|
||||
Literal,
|
||||
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
|
||||
LiteralString,
|
||||
/// The symbol `typing.Optional` (which can also be found as `typing_extensions.Optional`)
|
||||
Optional,
|
||||
/// The symbol `typing.Union` (which can also be found as `typing_extensions.Union`)
|
||||
Union,
|
||||
/// The symbol `typing.NoReturn` (which can also be found as `typing_extensions.NoReturn`)
|
||||
NoReturn,
|
||||
/// The symbol `typing.Never` available since 3.11 (which can also be found as `typing_extensions.Never`)
|
||||
Never,
|
||||
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
|
||||
/// This is not used since typeshed switched to representing `Any` as a class; now we use
|
||||
/// `KnownClass::Any` instead. But we still support the old `Any = object()` representation, at
|
||||
/// least for now. TODO maybe remove?
|
||||
Any,
|
||||
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
|
||||
Tuple,
|
||||
/// The symbol `typing.List` (which can also be found as `typing_extensions.List`)
|
||||
List,
|
||||
/// The symbol `typing.Dict` (which can also be found as `typing_extensions.Dict`)
|
||||
Dict,
|
||||
/// The symbol `typing.Set` (which can also be found as `typing_extensions.Set`)
|
||||
Set,
|
||||
/// The symbol `typing.FrozenSet` (which can also be found as `typing_extensions.FrozenSet`)
|
||||
FrozenSet,
|
||||
/// The symbol `typing.ChainMap` (which can also be found as `typing_extensions.ChainMap`)
|
||||
ChainMap,
|
||||
/// The symbol `typing.Counter` (which can also be found as `typing_extensions.Counter`)
|
||||
Counter,
|
||||
/// The symbol `typing.DefaultDict` (which can also be found as `typing_extensions.DefaultDict`)
|
||||
DefaultDict,
|
||||
/// The symbol `typing.Deque` (which can also be found as `typing_extensions.Deque`)
|
||||
Deque,
|
||||
/// The symbol `typing.OrderedDict` (which can also be found as `typing_extensions.OrderedDict`)
|
||||
OrderedDict,
|
||||
/// The symbol `typing.Protocol` (which can also be found as `typing_extensions.Protocol`)
|
||||
Protocol,
|
||||
/// The symbol `typing.Generic` (which can also be found as `typing_extensions.Generic`)
|
||||
Generic,
|
||||
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
|
||||
Type,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
||||
TypeAliasType(TypeAliasType<'db>),
|
||||
/// The symbol `knot_extensions.Unknown`
|
||||
Unknown,
|
||||
/// The symbol `knot_extensions.AlwaysTruthy`
|
||||
AlwaysTruthy,
|
||||
/// The symbol `knot_extensions.AlwaysFalsy`
|
||||
AlwaysFalsy,
|
||||
/// The symbol `knot_extensions.Not`
|
||||
Not,
|
||||
/// The symbol `knot_extensions.Intersection`
|
||||
Intersection,
|
||||
/// The symbol `knot_extensions.TypeOf`
|
||||
TypeOf,
|
||||
/// The symbol `knot_extensions.CallableTypeOf`
|
||||
CallableTypeOf,
|
||||
/// The symbol `typing.Callable`
|
||||
/// (which can also be found as `typing_extensions.Callable` or as `collections.abc.Callable`)
|
||||
Callable,
|
||||
|
||||
// Various special forms, special aliases and type qualifiers that we don't yet understand
|
||||
// (all currently inferred as TODO in most contexts):
|
||||
TypingSelf,
|
||||
Final,
|
||||
ClassVar,
|
||||
Concatenate,
|
||||
Unpack,
|
||||
Required,
|
||||
NotRequired,
|
||||
TypeAlias,
|
||||
TypeGuard,
|
||||
TypeIs,
|
||||
ReadOnly,
|
||||
// TODO: fill this enum out with more special forms, etc.
|
||||
}
|
||||
|
||||
impl<'db> KnownInstanceType<'db> {
|
||||
/// Evaluate the known instance in boolean context
|
||||
pub(crate) const fn bool(self) -> Truthiness {
|
||||
match self {
|
||||
Self::Annotated
|
||||
| Self::Literal
|
||||
| Self::LiteralString
|
||||
| Self::Optional
|
||||
| Self::TypeVar(_)
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
| Self::Never
|
||||
| Self::Any
|
||||
| Self::Tuple
|
||||
| Self::Type
|
||||
| Self::TypingSelf
|
||||
| Self::Final
|
||||
| Self::ClassVar
|
||||
| Self::Callable
|
||||
| Self::Concatenate
|
||||
| Self::Unpack
|
||||
| Self::Required
|
||||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypeIs
|
||||
| Self::List
|
||||
| Self::Dict
|
||||
| Self::DefaultDict
|
||||
| Self::Set
|
||||
| Self::FrozenSet
|
||||
| Self::Counter
|
||||
| Self::Deque
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict
|
||||
| Self::Protocol
|
||||
| Self::Generic
|
||||
| Self::ReadOnly
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::Unknown
|
||||
| Self::AlwaysTruthy
|
||||
| Self::AlwaysFalsy
|
||||
| Self::Not
|
||||
| Self::Intersection
|
||||
| Self::TypeOf
|
||||
| Self::CallableTypeOf => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the repr of the symbol at runtime
|
||||
pub(crate) fn repr(self, db: &'db dyn Db) -> &'db str {
|
||||
match self {
|
||||
Self::Annotated => "typing.Annotated",
|
||||
Self::Literal => "typing.Literal",
|
||||
Self::LiteralString => "typing.LiteralString",
|
||||
Self::Optional => "typing.Optional",
|
||||
Self::Union => "typing.Union",
|
||||
Self::NoReturn => "typing.NoReturn",
|
||||
Self::Never => "typing.Never",
|
||||
Self::Any => "typing.Any",
|
||||
Self::Tuple => "typing.Tuple",
|
||||
Self::Type => "typing.Type",
|
||||
Self::TypingSelf => "typing.Self",
|
||||
Self::Final => "typing.Final",
|
||||
Self::ClassVar => "typing.ClassVar",
|
||||
Self::Callable => "typing.Callable",
|
||||
Self::Concatenate => "typing.Concatenate",
|
||||
Self::Unpack => "typing.Unpack",
|
||||
Self::Required => "typing.Required",
|
||||
Self::NotRequired => "typing.NotRequired",
|
||||
Self::TypeAlias => "typing.TypeAlias",
|
||||
Self::TypeGuard => "typing.TypeGuard",
|
||||
Self::TypeIs => "typing.TypeIs",
|
||||
Self::List => "typing.List",
|
||||
Self::Dict => "typing.Dict",
|
||||
Self::DefaultDict => "typing.DefaultDict",
|
||||
Self::Set => "typing.Set",
|
||||
Self::FrozenSet => "typing.FrozenSet",
|
||||
Self::Counter => "typing.Counter",
|
||||
Self::Deque => "typing.Deque",
|
||||
Self::ChainMap => "typing.ChainMap",
|
||||
Self::OrderedDict => "typing.OrderedDict",
|
||||
Self::Protocol => "typing.Protocol",
|
||||
Self::Generic => "typing.Generic",
|
||||
Self::ReadOnly => "typing.ReadOnly",
|
||||
Self::TypeVar(typevar) => typevar.name(db),
|
||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||
Self::Unknown => "knot_extensions.Unknown",
|
||||
Self::AlwaysTruthy => "knot_extensions.AlwaysTruthy",
|
||||
Self::AlwaysFalsy => "knot_extensions.AlwaysFalsy",
|
||||
Self::Not => "knot_extensions.Not",
|
||||
Self::Intersection => "knot_extensions.Intersection",
|
||||
Self::TypeOf => "knot_extensions.TypeOf",
|
||||
Self::CallableTypeOf => "knot_extensions.CallableTypeOf",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`KnownClass`] which this symbol is an instance of
|
||||
pub(crate) const fn class(self) -> KnownClass {
|
||||
match self {
|
||||
Self::Annotated => KnownClass::SpecialForm,
|
||||
Self::Literal => KnownClass::SpecialForm,
|
||||
Self::LiteralString => KnownClass::SpecialForm,
|
||||
Self::Optional => KnownClass::SpecialForm,
|
||||
Self::Union => KnownClass::SpecialForm,
|
||||
Self::NoReturn => KnownClass::SpecialForm,
|
||||
Self::Never => KnownClass::SpecialForm,
|
||||
Self::Any => KnownClass::Object,
|
||||
Self::Tuple => KnownClass::SpecialForm,
|
||||
Self::Type => KnownClass::SpecialForm,
|
||||
Self::TypingSelf => KnownClass::SpecialForm,
|
||||
Self::Final => KnownClass::SpecialForm,
|
||||
Self::ClassVar => KnownClass::SpecialForm,
|
||||
Self::Callable => KnownClass::SpecialForm,
|
||||
Self::Concatenate => KnownClass::SpecialForm,
|
||||
Self::Unpack => KnownClass::SpecialForm,
|
||||
Self::Required => KnownClass::SpecialForm,
|
||||
Self::NotRequired => KnownClass::SpecialForm,
|
||||
Self::TypeAlias => KnownClass::SpecialForm,
|
||||
Self::TypeGuard => KnownClass::SpecialForm,
|
||||
Self::TypeIs => KnownClass::SpecialForm,
|
||||
Self::ReadOnly => KnownClass::SpecialForm,
|
||||
Self::List => KnownClass::StdlibAlias,
|
||||
Self::Dict => KnownClass::StdlibAlias,
|
||||
Self::DefaultDict => KnownClass::StdlibAlias,
|
||||
Self::Set => KnownClass::StdlibAlias,
|
||||
Self::FrozenSet => KnownClass::StdlibAlias,
|
||||
Self::Counter => KnownClass::StdlibAlias,
|
||||
Self::Deque => KnownClass::StdlibAlias,
|
||||
Self::ChainMap => KnownClass::StdlibAlias,
|
||||
Self::OrderedDict => KnownClass::StdlibAlias,
|
||||
Self::Protocol => KnownClass::SpecialForm, // actually `_ProtocolMeta` at runtime but this is what typeshed says
|
||||
Self::Generic => KnownClass::SpecialForm, // actually `type` at runtime but this is what typeshed says
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
Self::TypeOf => KnownClass::SpecialForm,
|
||||
Self::Not => KnownClass::SpecialForm,
|
||||
Self::Intersection => KnownClass::SpecialForm,
|
||||
Self::CallableTypeOf => KnownClass::SpecialForm,
|
||||
Self::Unknown => KnownClass::Object,
|
||||
Self::AlwaysTruthy => KnownClass::Object,
|
||||
Self::AlwaysFalsy => KnownClass::Object,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the instance type which this type is a subtype of.
|
||||
///
|
||||
/// For example, the symbol `typing.Literal` is an instance of `typing._SpecialForm`,
|
||||
/// so `KnownInstanceType::Literal.instance_fallback(db)`
|
||||
/// returns `Type::Instance(InstanceType { class: <typing._SpecialForm> })`.
|
||||
pub(super) fn instance_fallback(self, db: &dyn Db) -> Type {
|
||||
self.class().to_instance(db)
|
||||
}
|
||||
|
||||
/// Return `true` if this symbol is an instance of `class`.
|
||||
pub(super) fn is_instance_of(self, db: &'db dyn Db, class: ClassType<'db>) -> bool {
|
||||
self.class().is_subclass_of(db, class)
|
||||
}
|
||||
|
||||
pub(super) fn try_from_file_and_name(
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
symbol_name: &str,
|
||||
) -> Option<Self> {
|
||||
let candidate = match symbol_name {
|
||||
"Any" => Self::Any,
|
||||
"ClassVar" => Self::ClassVar,
|
||||
"Deque" => Self::Deque,
|
||||
"List" => Self::List,
|
||||
"Dict" => Self::Dict,
|
||||
"DefaultDict" => Self::DefaultDict,
|
||||
"Set" => Self::Set,
|
||||
"FrozenSet" => Self::FrozenSet,
|
||||
"Counter" => Self::Counter,
|
||||
"ChainMap" => Self::ChainMap,
|
||||
"OrderedDict" => Self::OrderedDict,
|
||||
"Generic" => Self::Generic,
|
||||
"Protocol" => Self::Protocol,
|
||||
"Optional" => Self::Optional,
|
||||
"Union" => Self::Union,
|
||||
"NoReturn" => Self::NoReturn,
|
||||
"Tuple" => Self::Tuple,
|
||||
"Type" => Self::Type,
|
||||
"Callable" => Self::Callable,
|
||||
"Annotated" => Self::Annotated,
|
||||
"Literal" => Self::Literal,
|
||||
"Never" => Self::Never,
|
||||
"Self" => Self::TypingSelf,
|
||||
"Final" => Self::Final,
|
||||
"Unpack" => Self::Unpack,
|
||||
"Required" => Self::Required,
|
||||
"TypeAlias" => Self::TypeAlias,
|
||||
"TypeGuard" => Self::TypeGuard,
|
||||
"TypeIs" => Self::TypeIs,
|
||||
"ReadOnly" => Self::ReadOnly,
|
||||
"Concatenate" => Self::Concatenate,
|
||||
"NotRequired" => Self::NotRequired,
|
||||
"LiteralString" => Self::LiteralString,
|
||||
"Unknown" => Self::Unknown,
|
||||
"AlwaysTruthy" => Self::AlwaysTruthy,
|
||||
"AlwaysFalsy" => Self::AlwaysFalsy,
|
||||
"Not" => Self::Not,
|
||||
"Intersection" => Self::Intersection,
|
||||
"TypeOf" => Self::TypeOf,
|
||||
"CallableTypeOf" => Self::CallableTypeOf,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
candidate
|
||||
.check_module(file_to_module(db, file)?.known()?)
|
||||
.then_some(candidate)
|
||||
}
|
||||
|
||||
/// Return `true` if `module` is a module from which this `KnownInstance` variant can validly originate.
|
||||
///
|
||||
/// Most variants can only exist in one module, which is the same as `self.class().canonical_module()`.
|
||||
/// Some variants could validly be defined in either `typing` or `typing_extensions`, however.
|
||||
pub(super) fn check_module(self, module: KnownModule) -> bool {
|
||||
match self {
|
||||
Self::Any
|
||||
| Self::ClassVar
|
||||
| Self::Deque
|
||||
| Self::List
|
||||
| Self::Dict
|
||||
| Self::DefaultDict
|
||||
| Self::Set
|
||||
| Self::FrozenSet
|
||||
| Self::Counter
|
||||
| Self::ChainMap
|
||||
| Self::OrderedDict
|
||||
| Self::Optional
|
||||
| Self::Union
|
||||
| Self::NoReturn
|
||||
| Self::Tuple
|
||||
| Self::Type
|
||||
| Self::Generic
|
||||
| Self::Callable => module.is_typing(),
|
||||
Self::Annotated
|
||||
| Self::Protocol
|
||||
| Self::Literal
|
||||
| Self::LiteralString
|
||||
| Self::Never
|
||||
| Self::TypingSelf
|
||||
| Self::Final
|
||||
| Self::Concatenate
|
||||
| Self::Unpack
|
||||
| Self::Required
|
||||
| Self::NotRequired
|
||||
| Self::TypeAlias
|
||||
| Self::TypeGuard
|
||||
| Self::TypeIs
|
||||
| Self::ReadOnly
|
||||
| Self::TypeAliasType(_)
|
||||
| Self::TypeVar(_) => {
|
||||
matches!(module, KnownModule::Typing | KnownModule::TypingExtensions)
|
||||
}
|
||||
Self::Unknown
|
||||
| Self::AlwaysTruthy
|
||||
| Self::AlwaysFalsy
|
||||
| Self::Not
|
||||
| Self::Intersection
|
||||
| Self::TypeOf
|
||||
| Self::CallableTypeOf => module.is_knot_extensions(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.class().to_class_literal(db)
|
||||
}
|
||||
}
|
||||
@@ -446,6 +446,11 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
}
|
||||
}
|
||||
ast::CmpOp::Eq if lhs_ty.is_literal_string() => Some(rhs_ty),
|
||||
ast::CmpOp::Eq if !rhs_ty.is_none(self.db) => Some(
|
||||
IntersectionBuilder::new(self.db)
|
||||
.add_negative(Type::none(self.db))
|
||||
.build(),
|
||||
),
|
||||
ast::CmpOp::In => self.evaluate_expr_in(lhs_ty, rhs_ty),
|
||||
ast::CmpOp::NotIn => self
|
||||
.evaluate_expr_in(lhs_ty, rhs_ty)
|
||||
|
||||
@@ -142,38 +142,38 @@ pub(crate) fn parse_string_annotation(
|
||||
if let Some(string_literal) = string_expr.as_single_part_string() {
|
||||
let prefix = string_literal.flags.prefix();
|
||||
if prefix.is_raw() {
|
||||
context.report_lint_old(
|
||||
&RAW_STRING_TYPE_ANNOTATION,
|
||||
string_literal,
|
||||
format_args!("Type expressions cannot use raw string literal"),
|
||||
);
|
||||
if let Some(builder) = context.report_lint(&RAW_STRING_TYPE_ANNOTATION, string_literal)
|
||||
{
|
||||
builder.into_diagnostic("Type expressions cannot use raw string literal");
|
||||
}
|
||||
// Compare the raw contents (without quotes) of the expression with the parsed contents
|
||||
// contained in the string literal.
|
||||
} else if &source[string_literal.content_range()] == string_literal.as_str() {
|
||||
match ruff_python_parser::parse_string_annotation(source.as_str(), string_literal) {
|
||||
Ok(parsed) => return Some(parsed),
|
||||
Err(parse_error) => context.report_lint_old(
|
||||
&INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
|
||||
string_literal,
|
||||
format_args!("Syntax error in forward annotation: {}", parse_error.error),
|
||||
),
|
||||
Err(parse_error) => {
|
||||
if let Some(builder) =
|
||||
context.report_lint(&INVALID_SYNTAX_IN_FORWARD_ANNOTATION, string_literal)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Syntax error in forward annotation: {}",
|
||||
parse_error.error
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if let Some(builder) =
|
||||
context.report_lint(&ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION, string_expr)
|
||||
{
|
||||
// The raw contents of the string doesn't match the parsed content. This could be the
|
||||
// case for annotations that contain escape sequences.
|
||||
context.report_lint_old(
|
||||
&ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION,
|
||||
string_expr,
|
||||
format_args!("Type expressions cannot contain escape characters"),
|
||||
);
|
||||
builder.into_diagnostic("Type expressions cannot contain escape characters");
|
||||
}
|
||||
} else {
|
||||
} else if let Some(builder) =
|
||||
context.report_lint(&IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, string_expr)
|
||||
{
|
||||
// String is implicitly concatenated.
|
||||
context.report_lint_old(
|
||||
&IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION,
|
||||
string_expr,
|
||||
format_args!("Type expressions cannot span multiple string literals"),
|
||||
);
|
||||
builder.into_diagnostic("Type expressions cannot span multiple string literals");
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::cmp::Ordering;
|
||||
use crate::db::Db;
|
||||
|
||||
use super::{
|
||||
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, InstanceType,
|
||||
KnownInstanceType, SuperOwnerKind, TodoType, Type,
|
||||
class_base::ClassBase, subclass_of::SubclassOfInner, DynamicType, KnownInstanceType,
|
||||
SuperOwnerKind, TodoType, Type,
|
||||
};
|
||||
|
||||
/// Return an [`Ordering`] that describes the canonical order in which two types should appear
|
||||
@@ -79,6 +79,12 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(Type::DataclassDecorator(_), _) => Ordering::Less,
|
||||
(_, Type::DataclassDecorator(_)) => Ordering::Greater,
|
||||
|
||||
(Type::DataclassTransformer(left), Type::DataclassTransformer(right)) => {
|
||||
left.bits().cmp(&right.bits())
|
||||
}
|
||||
(Type::DataclassTransformer(_), _) => Ordering::Less,
|
||||
(_, Type::DataclassTransformer(_)) => Ordering::Greater,
|
||||
|
||||
(Type::Callable(left), Type::Callable(right)) => {
|
||||
debug_assert_eq!(*left, left.normalized(db));
|
||||
debug_assert_eq!(*right, right.normalized(db));
|
||||
@@ -120,10 +126,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
|
||||
(Type::SubclassOf(_), _) => Ordering::Less,
|
||||
(_, Type::SubclassOf(_)) => Ordering::Greater,
|
||||
(
|
||||
Type::Instance(InstanceType { class: left }),
|
||||
Type::Instance(InstanceType { class: right }),
|
||||
) => left.cmp(right),
|
||||
(Type::Instance(left), Type::Instance(right)) => left.class().cmp(&right.class()),
|
||||
|
||||
(Type::Instance(_), _) => Ordering::Less,
|
||||
(_, Type::Instance(_)) => Ordering::Greater,
|
||||
@@ -155,10 +158,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
||||
(SuperOwnerKind::Class(left), SuperOwnerKind::Class(right)) => left.cmp(right),
|
||||
(SuperOwnerKind::Class(_), _) => Ordering::Less,
|
||||
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
|
||||
(
|
||||
SuperOwnerKind::Instance(InstanceType { class: left }),
|
||||
SuperOwnerKind::Instance(InstanceType { class: right }),
|
||||
) => left.cmp(right),
|
||||
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
|
||||
left.class().cmp(&right.class())
|
||||
}
|
||||
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
|
||||
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,
|
||||
(SuperOwnerKind::Dynamic(left), SuperOwnerKind::Dynamic(right)) => {
|
||||
|
||||
@@ -134,35 +134,45 @@ impl<'db> Unpacker<'db> {
|
||||
};
|
||||
|
||||
if let Some(tuple_ty) = ty.into_tuple() {
|
||||
let tuple_ty_elements = self.tuple_ty_elements(target, elts, tuple_ty);
|
||||
let tuple_ty_elements =
|
||||
self.tuple_ty_elements(target, elts, tuple_ty, value_expr);
|
||||
|
||||
let length_mismatch = match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
Ordering::Less => {
|
||||
self.context.report_lint_old(
|
||||
&INVALID_ASSIGNMENT,
|
||||
target,
|
||||
format_args!(
|
||||
"Too many values to unpack (expected {}, got {})",
|
||||
elts.len(),
|
||||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
true
|
||||
}
|
||||
Ordering::Greater => {
|
||||
self.context.report_lint_old(
|
||||
&INVALID_ASSIGNMENT,
|
||||
target,
|
||||
format_args!(
|
||||
"Not enough values to unpack (expected {}, got {})",
|
||||
elts.len(),
|
||||
tuple_ty_elements.len()
|
||||
),
|
||||
);
|
||||
true
|
||||
}
|
||||
Ordering::Equal => false,
|
||||
};
|
||||
let length_mismatch =
|
||||
match elts.len().cmp(&tuple_ty_elements.len()) {
|
||||
Ordering::Less => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Too many values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(self.context.secondary(value_expr).message(
|
||||
format_args!("Got {}", tuple_ty_elements.len()),
|
||||
));
|
||||
}
|
||||
true
|
||||
}
|
||||
Ordering::Greater => {
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&INVALID_ASSIGNMENT, target)
|
||||
{
|
||||
let mut diag =
|
||||
builder.into_diagnostic("Not enough values to unpack");
|
||||
diag.set_primary_message(format_args!(
|
||||
"Expected {}",
|
||||
elts.len(),
|
||||
));
|
||||
diag.annotate(self.context.secondary(value_expr).message(
|
||||
format_args!("Got {}", tuple_ty_elements.len()),
|
||||
));
|
||||
}
|
||||
true
|
||||
}
|
||||
Ordering::Equal => false,
|
||||
};
|
||||
|
||||
for (index, ty) in tuple_ty_elements.iter().enumerate() {
|
||||
if let Some(element_types) = target_types.get_mut(index) {
|
||||
@@ -203,11 +213,15 @@ impl<'db> Unpacker<'db> {
|
||||
|
||||
/// Returns the [`Type`] elements inside the given [`TupleType`] taking into account that there
|
||||
/// can be a starred expression in the `elements`.
|
||||
///
|
||||
/// `value_expr` is an AST reference to the value being unpacked. It is
|
||||
/// only used for diagnostics.
|
||||
fn tuple_ty_elements(
|
||||
&self,
|
||||
expr: &ast::Expr,
|
||||
targets: &[ast::Expr],
|
||||
tuple_ty: TupleType<'db>,
|
||||
value_expr: AnyNodeRef<'_>,
|
||||
) -> Cow<'_, [Type<'db>]> {
|
||||
// If there is a starred expression, it will consume all of the types at that location.
|
||||
let Some(starred_index) = targets.iter().position(ast::Expr::is_starred_expr) else {
|
||||
@@ -254,15 +268,15 @@ impl<'db> Unpacker<'db> {
|
||||
|
||||
Cow::Owned(element_types)
|
||||
} else {
|
||||
self.context.report_lint_old(
|
||||
&INVALID_ASSIGNMENT,
|
||||
expr,
|
||||
format_args!(
|
||||
"Not enough values to unpack (expected {} or more, got {})",
|
||||
targets.len() - 1,
|
||||
tuple_ty.len(self.db())
|
||||
),
|
||||
);
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, expr) {
|
||||
let mut diag = builder.into_diagnostic("Not enough values to unpack");
|
||||
diag.set_primary_message(format_args!("Expected {} or more", targets.len() - 1));
|
||||
diag.annotate(
|
||||
self.context
|
||||
.secondary(value_expr)
|
||||
.message(format_args!("Got {}", tuple_ty.len(self.db()))),
|
||||
);
|
||||
}
|
||||
|
||||
Cow::Owned(vec![Type::unknown(); targets.len()])
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ test-case = { workspace = true }
|
||||
# Used via macro expansion.
|
||||
ignored = ["jiff"]
|
||||
|
||||
[package.metadata.dist]
|
||||
dist = true
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
|
||||
@@ -260,3 +260,9 @@ def f():
|
||||
for i in range(5):
|
||||
if j := i:
|
||||
items.append(j)
|
||||
|
||||
def f():
|
||||
values = [1, 2, 3]
|
||||
result = list() # this should be replaced with a comprehension
|
||||
for i in values:
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
@@ -270,6 +270,15 @@ pub(crate) fn manual_list_comprehension(checker: &Checker, for_stmt: &ast::StmtF
|
||||
list_binding_value.is_some_and(|binding_value| match binding_value {
|
||||
// `value = []`
|
||||
Expr::List(list_expr) => list_expr.is_empty(),
|
||||
// `value = list()`
|
||||
// This might be linted against, but turning it into a list comprehension will also remove it
|
||||
Expr::Call(call) => {
|
||||
checker
|
||||
.semantic()
|
||||
.resolve_builtin_symbol(&call.func)
|
||||
.is_some_and(|name| name == "list")
|
||||
&& call.arguments.is_empty()
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
|
||||
@@ -208,5 +208,16 @@ PERF401.py:262:13: PERF401 Use a list comprehension to create a transformed list
|
||||
261 | if j := i:
|
||||
262 | items.append(j)
|
||||
| ^^^^^^^^^^^^^^^ PERF401
|
||||
263 |
|
||||
264 | def f():
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
PERF401.py:268:9: PERF401 Use a list comprehension to create a transformed list
|
||||
|
|
||||
266 | result = list() # this should be replaced with a comprehension
|
||||
267 | for i in values:
|
||||
268 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -492,6 +492,8 @@ PERF401.py:262:13: PERF401 [*] Use a list comprehension to create a transformed
|
||||
261 | if j := i:
|
||||
262 | items.append(j)
|
||||
| ^^^^^^^^^^^^^^^ PERF401
|
||||
263 |
|
||||
264 | def f():
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
@@ -505,3 +507,25 @@ PERF401.py:262:13: PERF401 [*] Use a list comprehension to create a transformed
|
||||
261 |- if j := i:
|
||||
262 |- items.append(j)
|
||||
259 |+ items = [j for i in range(5) if (j := i)]
|
||||
263 260 |
|
||||
264 261 | def f():
|
||||
265 262 | values = [1, 2, 3]
|
||||
|
||||
PERF401.py:268:9: PERF401 [*] Use a list comprehension to create a transformed list
|
||||
|
|
||||
266 | result = list() # this should be replaced with a comprehension
|
||||
267 | for i in values:
|
||||
268 | result.append(i + 1) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
|
|
||||
= help: Replace for loop with list comprehension
|
||||
|
||||
ℹ Unsafe fix
|
||||
263 263 |
|
||||
264 264 | def f():
|
||||
265 265 | values = [1, 2, 3]
|
||||
266 |- result = list() # this should be replaced with a comprehension
|
||||
267 |- for i in values:
|
||||
268 |- result.append(i + 1) # PERF401
|
||||
266 |+ # this should be replaced with a comprehension
|
||||
267 |+ result = [i + 1 for i in values] # PERF401
|
||||
|
||||
@@ -39,6 +39,10 @@ use crate::Locator;
|
||||
/// "{}, {}".format("Hello", "World") # "Hello, World"
|
||||
/// ```
|
||||
///
|
||||
/// This fix is marked as unsafe because:
|
||||
/// - Comments attached to arguments are not moved, which can cause comments to mismatch the actual arguments.
|
||||
/// - If arguments have side effects (e.g., print), reordering may change program behavior.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: Format String Syntax](https://docs.python.org/3/library/string.html#format-string-syntax)
|
||||
/// - [Python documentation: `str.format`](https://docs.python.org/3/library/stdtypes.html#str.format)
|
||||
|
||||
6
playground/api/package-lock.json
generated
6
playground/api/package-lock.json
generated
@@ -1708,9 +1708,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "11.0.5",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
|
||||
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
|
||||
"integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
|
||||
22
playground/package-lock.json
generated
22
playground/package-lock.json
generated
@@ -41,10 +41,10 @@
|
||||
"pyodide": "^0.27.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-resizable-panels": "^2.1.8",
|
||||
"red_knot_wasm": "file:red_knot_wasm",
|
||||
"shared": "0.0.0",
|
||||
"smol-toml": "^1.3.1"
|
||||
"smol-toml": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite-plugin-static-copy": "^2.3.0"
|
||||
@@ -4969,9 +4969,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-resizable-panels": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz",
|
||||
"integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.8.tgz",
|
||||
"integrity": "sha512-oDvD0sw34Ecx00cQFLiRJpAE2fCgNLBr8DMrBzkrsaUiLpAycIQoY3eAWfMblDql3pTIMZ60wJ/P89RO1htM2w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
@@ -5393,9 +5393,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/smol-toml": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz",
|
||||
"integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==",
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.3.tgz",
|
||||
"integrity": "sha512-KMVLNWu490KlNfD0lbfDBUktJIEaZRBj1eeK0SMfdpO/rfyARIzlnPVI1Ge4l0vtSJmQUAiGKxMyLGrCT38iyA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@@ -6087,10 +6087,10 @@
|
||||
"monaco-editor": "^0.52.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable-panels": "^2.0.0",
|
||||
"react-resizable-panels": "^2.1.8",
|
||||
"ruff_wasm": "file:ruff_wasm",
|
||||
"shared": "0.0.0",
|
||||
"smol-toml": "^1.3.0"
|
||||
"smol-toml": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"ruff/ruff_wasm": {
|
||||
@@ -6103,7 +6103,7 @@
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"classnames": "^2.3.2",
|
||||
"react": "^19.0.0",
|
||||
"react-resizable-panels": "^2.1.7"
|
||||
"react-resizable-panels": "^2.1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user