Compare commits
69 Commits
0.11.6
...
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 | ||
|
|
a4531bf865 | ||
|
|
be54b840e9 | ||
|
|
45b5dedee2 | ||
|
|
9ff4772a2c | ||
|
|
c077b109ce | ||
|
|
8a2dd01db4 | ||
|
|
f888e51a34 | ||
|
|
d11e959ad5 | ||
|
|
a56eef444a | ||
|
|
14ff67fd46 | ||
|
|
ada7d4da0d | ||
|
|
4cafb44ba7 | ||
|
|
1445836872 | ||
|
|
da6b68cb58 | ||
|
|
2a478ce1b2 | ||
|
|
8fe2dd5e03 | ||
|
|
454ad15aee | ||
|
|
fd3fc34a9e | ||
|
|
c550b4d565 | ||
|
|
f8061e8b99 | ||
|
|
27a315b740 | ||
|
|
08221454f6 | ||
|
|
5fec1039ed | ||
|
|
787bcd1c6a | ||
|
|
5853eb28dd | ||
|
|
84d064a14c | ||
|
|
e4e405d2a1 | ||
|
|
1918c61623 | ||
|
|
44ad201262 | ||
|
|
c7372d218d | ||
|
|
de8f4e62e2 | ||
|
|
edfa03a692 | ||
|
|
9965cee998 | ||
|
|
58807b2980 | ||
|
|
9c47b6dbb0 | ||
|
|
d2ebfd6ed7 | ||
|
|
c36f3f5304 |
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
|
||||
|
||||
2
.github/workflows/build-binaries.yml
vendored
2
.github/workflows/build-binaries.yml
vendored
@@ -377,7 +377,7 @@ jobs:
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel"
|
||||
if: matrix.target == 'x86_64-unknown-linux-musl'
|
||||
uses: addnab/docker-run-action@v3
|
||||
uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3
|
||||
with:
|
||||
image: alpine:latest
|
||||
options: -v ${{ github.workspace }}:/io -w /io
|
||||
|
||||
38
.github/workflows/ci.yaml
vendored
38
.github/workflows/ci.yaml
vendored
@@ -237,13 +237,13 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: Red-knot mdtests (GitHub annotations)
|
||||
@@ -291,13 +291,13 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -320,7 +320,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Run tests"
|
||||
@@ -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"
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Build"
|
||||
run: cargo build --release --locked
|
||||
|
||||
@@ -401,13 +401,13 @@ jobs:
|
||||
MSRV: ${{ steps.msrv.outputs.value }}
|
||||
run: rustup default "${MSRV}"
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- name: "Install cargo nextest"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install cargo insta"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Run tests"
|
||||
@@ -433,7 +433,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -455,7 +455,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -641,7 +641,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@main
|
||||
- uses: cargo-bins/cargo-binstall@63aaa5c1932cebabc34eceda9d92a70215dcead6 # v1.12.3
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -681,7 +681,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- name: "Cache pre-commit"
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
@@ -720,7 +720,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -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"
|
||||
@@ -857,7 +857,7 @@ jobs:
|
||||
run: rustup show
|
||||
|
||||
- name: "Install codspeed"
|
||||
uses: taiki-e/install-action@be7c31b6745feec79dec5eb79178466c0670bb2d # v2
|
||||
uses: taiki-e/install-action@09dc018eee06ae1c9e0409786563f534210ceb83 # v2
|
||||
with:
|
||||
tool: cargo-codspeed
|
||||
|
||||
|
||||
4
.github/workflows/daily_fuzz.yaml
vendored
4
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,11 +34,11 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
- uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build ruff
|
||||
# A debug build means the script runs slower once it gets started,
|
||||
|
||||
2
.github/workflows/daily_property_tests.yaml
vendored
2
.github/workflows/daily_property_tests.yaml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
uses: rui314/setup-mold@v1
|
||||
uses: rui314/setup-mold@e16410e7f8d9e167b74ad5697a9089a35126eb50 # v1
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
- name: Build Red Knot
|
||||
# A release build takes longer (2 min vs 1 min), but the property tests run much faster in release
|
||||
|
||||
12
.github/workflows/mypy_primer.yaml
vendored
12
.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
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
|
||||
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
||||
with:
|
||||
@@ -45,13 +46,15 @@ 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
|
||||
run: |
|
||||
cd ruff
|
||||
|
||||
PRIMER_SELECTOR="$(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt)"
|
||||
|
||||
echo "new commit"
|
||||
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
|
||||
|
||||
@@ -62,13 +65,14 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
echo "Project selector: $PRIMER_SELECTOR"
|
||||
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
|
||||
uvx mypy_primer \
|
||||
--repo ruff \
|
||||
--type-checker knot \
|
||||
--old base_commit \
|
||||
--new "$GITHUB_SHA" \
|
||||
--project-selector '/(mypy_primer|black|pyp|git-revise|zipp|arrow|isort|itsdangerous|rich|packaging|pybind11|pyinstrument|typeshed-stats|scrapy|werkzeug|bidict|async-utils|python-chess|dacite|python-htmlgen|paroxython|porcupine|psycopg)$' \
|
||||
--project-selector "/($PRIMER_SELECTOR)\$" \
|
||||
--output concise \
|
||||
--debug > mypy_primer.diff || [ $? -eq 1 ]
|
||||
|
||||
|
||||
@@ -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-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@0c5e2b8115b80b4c7c5ddf6ffdd634974642d182 # v5.4.1
|
||||
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
||||
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
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
|
||||
|
||||
@@ -79,7 +79,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.11.5
|
||||
rev: v0.11.6
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
@@ -97,7 +97,7 @@ repos:
|
||||
# zizmor detects security vulnerabilities in GitHub Actions workflows.
|
||||
# Additional configuration for the tool is found in `.github/zizmor.yml`
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
rev: v1.5.2
|
||||
rev: v1.6.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
|
||||
55
Cargo.lock
generated
55
Cargo.lock
generated
@@ -334,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.36"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
|
||||
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -344,9 +344,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.36"
|
||||
version = "4.5.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
|
||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -478,7 +478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -487,7 +487,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -918,7 +918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1499,7 +1499,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi 0.5.0",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1553,9 +1553,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.4"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
|
||||
checksum = "59ec30f7142be6fe14e1b021f50b85db8df2d4324ea6e91ec3e5dcde092021d0"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -1563,14 +1563,14 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.4"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
|
||||
checksum = "526b834d727fd59d37b076b0c3236d9adde1b1729a4361e20b2026f738cc1dbe"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1645,9 +1645,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
@@ -2327,9 +2327,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2420,13 +2420,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3084,7 +3083,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
"rand 0.9.0",
|
||||
"rand 0.9.1",
|
||||
"ruff_diagnostics",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
@@ -3428,7 +3427,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3441,7 +3440,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.3",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3675,9 +3674,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "3.1.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
||||
checksum = "8b1fdf65dd6331831494dd616b30351c38e96e45921a27745cf98490458b90bb"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
]
|
||||
@@ -3827,7 +3826,7 @@ dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix 1.0.2",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4328,7 +4327,7 @@ checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"js-sys",
|
||||
"rand 0.9.0",
|
||||
"rand 0.9.1",
|
||||
"uuid-macro-internal",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -4599,7 +4598,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
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]).
|
||||
@@ -31,7 +31,7 @@ mypy_primer \
|
||||
```
|
||||
|
||||
This will show the diagnostics diff for the `black` project between the `main` branch and your `my/feature` branch. To run the
|
||||
diff for all projects, you currently need to copy the project-selector regex from the CI pipeline in `.github/workflows/mypy_primer.yaml`.
|
||||
diff for all projects we currently enable in CI, use `--project-selector "/($(paste -s -d'|' crates/red_knot_python_semantic/resources/primer/good.txt))\$"`.
|
||||
|
||||
You can also take a look at the [full list of ecosystem projects]. Note that some of them might still need a `knot_paths` configuration
|
||||
option to work correctly.
|
||||
@@ -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)
|
||||
|
|
||||
@@ -252,7 +252,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
|
||||
r#"
|
||||
y = 4 / 0
|
||||
|
||||
for a in range(0, y):
|
||||
for a in range(0, int(y)):
|
||||
x = a
|
||||
|
||||
print(x) # possibly-unresolved-reference
|
||||
@@ -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, y):
|
||||
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,13 +301,13 @@ 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, y):
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -328,7 +328,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
|
||||
|
||||
y = 4 / 0
|
||||
|
||||
for a in range(0, y):
|
||||
for a in range(0, int(y)):
|
||||
x = a
|
||||
|
||||
print(x) # possibly-unresolved-reference
|
||||
@@ -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, y):
|
||||
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,24 +388,24 @@ 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, y):
|
||||
6 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
Found 2 diagnostics
|
||||
@@ -426,7 +426,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
|
||||
r#"
|
||||
y = 4 / 0
|
||||
|
||||
for a in range(0, y):
|
||||
for a in range(0, int(y)):
|
||||
x = a
|
||||
|
||||
print(x) # possibly-unresolved-reference
|
||||
@@ -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, y):
|
||||
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,13 +476,13 @@ 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, y):
|
||||
4 | for a in range(0, int(y)):
|
||||
|
|
||||
|
||||
Found 1 diagnostic
|
||||
@@ -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
|
||||
@@ -814,7 +814,7 @@ fn user_configuration() -> anyhow::Result<()> {
|
||||
r#"
|
||||
y = 4 / 0
|
||||
|
||||
for a in range(0, y):
|
||||
for a in range(0, int(y)):
|
||||
x = a
|
||||
|
||||
print(x)
|
||||
@@ -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, y):
|
||||
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, y):
|
||||
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)
|
||||
|
|
||||
|
||||
@@ -10,7 +10,7 @@ pub(crate) mod tests {
|
||||
|
||||
use super::Db;
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb};
|
||||
use red_knot_python_semantic::{default_lint_registry, Db as SemanticDb, Program};
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
@@ -83,6 +83,10 @@ pub(crate) mod tests {
|
||||
fn files(&self) -> &Files {
|
||||
&self.files
|
||||
}
|
||||
|
||||
fn python_version(&self) -> ruff_python_ast::PythonVersion {
|
||||
Program::get(self).python_version(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn SourceDb> for TestDb {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from __future__ import annotations
|
||||
|
||||
def foo(a: foo()):
|
||||
pass
|
||||
@@ -149,6 +149,10 @@ impl SourceDb for ProjectDatabase {
|
||||
fn files(&self) -> &Files {
|
||||
&self.files
|
||||
}
|
||||
|
||||
fn python_version(&self) -> ruff_python_ast::PythonVersion {
|
||||
Program::get(self).python_version(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::db]
|
||||
@@ -207,7 +211,7 @@ pub(crate) mod tests {
|
||||
use salsa::Event;
|
||||
|
||||
use red_knot_python_semantic::lint::{LintRegistry, RuleSelection};
|
||||
use red_knot_python_semantic::Db as SemanticDb;
|
||||
use red_knot_python_semantic::{Db as SemanticDb, Program};
|
||||
use ruff_db::files::Files;
|
||||
use ruff_db::system::{DbWithTestSystem, System, TestSystem};
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
@@ -281,6 +285,10 @@ pub(crate) mod tests {
|
||||
fn files(&self) -> &Files {
|
||||
&self.files
|
||||
}
|
||||
|
||||
fn python_version(&self) -> ruff_python_ast::PythonVersion {
|
||||
Program::get(self).python_version(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Upcast<dyn SemanticDb> for TestDb {
|
||||
|
||||
@@ -10,7 +10,8 @@ use red_knot_python_semantic::lint::{LintRegistry, LintRegistryBuilder, RuleSele
|
||||
use red_knot_python_semantic::register_lints;
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use ruff_db::diagnostic::{
|
||||
create_parse_diagnostic, Annotation, Diagnostic, DiagnosticId, Severity, Span,
|
||||
create_parse_diagnostic, create_unsupported_syntax_diagnostic, Annotation, Diagnostic,
|
||||
DiagnosticId, Severity, Span,
|
||||
};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::parsed_module;
|
||||
@@ -424,6 +425,13 @@ fn check_file_impl(db: &dyn Db, file: File) -> Vec<Diagnostic> {
|
||||
.map(|error| create_parse_diagnostic(file, error)),
|
||||
);
|
||||
|
||||
diagnostics.extend(
|
||||
parsed
|
||||
.unsupported_syntax_errors()
|
||||
.iter()
|
||||
.map(|error| create_unsupported_syntax_diagnostic(file, error)),
|
||||
);
|
||||
|
||||
diagnostics.extend(check_types(db.upcast(), file).into_iter().cloned());
|
||||
|
||||
diagnostics.sort_unstable_by_key(|diagnostic| {
|
||||
@@ -520,11 +528,13 @@ mod tests {
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::{check_file_impl, ProjectMetadata};
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use red_knot_python_semantic::{Program, ProgramSettings, PythonPlatform, SearchPathSettings};
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{DbWithTestSystem, DbWithWritableSystem as _, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
#[test]
|
||||
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
|
||||
@@ -532,6 +542,16 @@ mod tests {
|
||||
let mut db = TestDb::new(project);
|
||||
let path = SystemPath::new("test.py");
|
||||
|
||||
Program::from_settings(
|
||||
&db,
|
||||
ProgramSettings {
|
||||
python_version: PythonVersion::default(),
|
||||
python_platform: PythonPlatform::default(),
|
||||
search_paths: SearchPathSettings::new(vec![SystemPathBuf::from(".")]),
|
||||
},
|
||||
)
|
||||
.expect("Failed to configure program settings");
|
||||
|
||||
db.write_file(path, "x = 10")?;
|
||||
let file = system_path_to_file(&db, path).unwrap();
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf, TestSystem};
|
||||
use ruff_python_ast::visitor::source_order;
|
||||
use ruff_python_ast::visitor::source_order::SourceOrderVisitor;
|
||||
use ruff_python_ast::{self as ast, Alias, Expr, Parameter, ParameterWithDefault, Stmt};
|
||||
use ruff_python_ast::{
|
||||
self as ast, Alias, Comprehension, Expr, Parameter, ParameterWithDefault, Stmt,
|
||||
};
|
||||
|
||||
fn setup_db(project_root: &SystemPath, system: TestSystem) -> anyhow::Result<ProjectDatabase> {
|
||||
let project = ProjectMetadata::discover(project_root, &system)?;
|
||||
@@ -258,6 +260,14 @@ impl SourceOrderVisitor<'_> for PullTypesVisitor<'_> {
|
||||
source_order::walk_expr(self, expr);
|
||||
}
|
||||
|
||||
fn visit_comprehension(&mut self, comprehension: &Comprehension) {
|
||||
self.visit_expr(&comprehension.iter);
|
||||
self.visit_target(&comprehension.target);
|
||||
for if_expr in &comprehension.ifs {
|
||||
self.visit_expr(if_expr);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_parameter(&mut self, parameter: &Parameter) {
|
||||
let _ty = parameter.inferred_type(&self.model);
|
||||
|
||||
|
||||
@@ -237,6 +237,11 @@ def _(c: Callable[[Concatenate[int, str, ...], int], int]):
|
||||
|
||||
## Using `typing.ParamSpec`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Using a `ParamSpec` in a `Callable` annotation:
|
||||
|
||||
```py
|
||||
|
||||
@@ -48,6 +48,11 @@ reveal_type(get_foo()) # revealed: Foo
|
||||
|
||||
## Deferred self-reference annotations in a class definition
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -94,6 +99,11 @@ class Foo:
|
||||
|
||||
## Non-deferred self-reference annotations in a class definition
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# error: [unresolved-reference]
|
||||
@@ -146,3 +156,24 @@ def _():
|
||||
def f(self) -> C:
|
||||
return self
|
||||
```
|
||||
|
||||
## Base class references
|
||||
|
||||
### Not deferred by __future__.annotations
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
class A(B): # error: [unresolved-reference]
|
||||
pass
|
||||
|
||||
class B:
|
||||
pass
|
||||
```
|
||||
|
||||
### Deferred in stub files
|
||||
|
||||
```pyi
|
||||
class A(B): ...
|
||||
class B: ...
|
||||
```
|
||||
|
||||
@@ -89,7 +89,7 @@ def _(
|
||||
reveal_type(k) # revealed: Unknown
|
||||
reveal_type(p) # revealed: Unknown
|
||||
reveal_type(q) # revealed: int | Unknown
|
||||
reveal_type(r) # revealed: @Todo(generics)
|
||||
reveal_type(r) # revealed: @Todo(unknown type subscript)
|
||||
```
|
||||
|
||||
## Invalid Collection based AST nodes
|
||||
|
||||
@@ -137,7 +137,7 @@ from other import Literal
|
||||
a1: Literal[26]
|
||||
|
||||
def f():
|
||||
reveal_type(a1) # revealed: @Todo(generics)
|
||||
reveal_type(a1) # revealed: @Todo(unknown type subscript)
|
||||
```
|
||||
|
||||
## Detecting typing_extensions.Literal
|
||||
|
||||
@@ -72,13 +72,11 @@ reveal_type(baz) # revealed: Literal["bazfoo"]
|
||||
qux = (foo, bar)
|
||||
reveal_type(qux) # revealed: tuple[Literal["foo"], Literal["bar"]]
|
||||
|
||||
# TODO: Infer "LiteralString"
|
||||
reveal_type(foo.join(qux)) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(foo.join(qux)) # revealed: LiteralString
|
||||
|
||||
template: LiteralString = "{}, {}"
|
||||
reveal_type(template) # revealed: Literal["{}, {}"]
|
||||
# TODO: Infer `LiteralString`
|
||||
reveal_type(template.format(foo, bar)) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(template.format(foo, bar)) # revealed: LiteralString
|
||||
```
|
||||
|
||||
### Assignability
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Starred expression annotations
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
```
|
||||
|
||||
Type annotations for `*args` can be starred expressions themselves:
|
||||
|
||||
```py
|
||||
|
||||
@@ -67,21 +67,24 @@ import typing
|
||||
|
||||
####################
|
||||
### Built-ins
|
||||
####################
|
||||
|
||||
class ListSubclass(typing.List): ...
|
||||
|
||||
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[ListSubclass], Literal[list], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(ListSubclass.__mro__)
|
||||
|
||||
class DictSubclass(typing.Dict): ...
|
||||
|
||||
# TODO: should have `Generic`, should not have `Unknown`
|
||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Unknown, Literal[object]]
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[DictSubclass], Literal[dict], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(DictSubclass.__mro__)
|
||||
|
||||
class SetSubclass(typing.Set): ...
|
||||
|
||||
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[SetSubclass], Literal[set], Literal[MutableSet], Literal[AbstractSet], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(SetSubclass.__mro__)
|
||||
|
||||
class FrozenSetSubclass(typing.FrozenSet): ...
|
||||
@@ -92,11 +95,12 @@ reveal_type(FrozenSetSubclass.__mro__)
|
||||
|
||||
####################
|
||||
### `collections`
|
||||
####################
|
||||
|
||||
class ChainMapSubclass(typing.ChainMap): ...
|
||||
|
||||
# TODO: Should be (ChainMapSubclass, ChainMap, MutableMapping, Mapping, Collection, Sized, Iterable, Container, Generic, object)
|
||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Unknown, Literal[object]]
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[ChainMapSubclass], Literal[ChainMap], Literal[MutableMapping], Literal[Mapping], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(ChainMapSubclass.__mro__)
|
||||
|
||||
class CounterSubclass(typing.Counter): ...
|
||||
@@ -113,7 +117,8 @@ reveal_type(DefaultDictSubclass.__mro__)
|
||||
|
||||
class DequeSubclass(typing.Deque): ...
|
||||
|
||||
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(protocol), Literal[object]]
|
||||
# TODO: generic protocols
|
||||
# revealed: tuple[Literal[DequeSubclass], Literal[deque], Literal[MutableSequence], Literal[Sequence], Literal[Reversible], Literal[Collection], Literal[Iterable], Literal[Container], @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(DequeSubclass.__mro__)
|
||||
|
||||
class OrderedDictSubclass(typing.OrderedDict): ...
|
||||
|
||||
@@ -41,7 +41,7 @@ class Foo:
|
||||
One thing that is supported is error messages for using special forms in type expressions.
|
||||
|
||||
```py
|
||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec
|
||||
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, Generic
|
||||
|
||||
def _(
|
||||
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
|
||||
@@ -49,6 +49,7 @@ def _(
|
||||
c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression"
|
||||
d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` requires at least two arguments when used in a type expression"
|
||||
e: ParamSpec,
|
||||
f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions"
|
||||
) -> None:
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
@@ -65,7 +66,7 @@ You can't inherit from most of these. `typing.Callable` is an exception.
|
||||
|
||||
```py
|
||||
from typing import Callable
|
||||
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate
|
||||
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
|
||||
|
||||
class A(Self): ... # error: [invalid-base]
|
||||
class B(Unpack): ... # error: [invalid-base]
|
||||
@@ -73,12 +74,18 @@ class C(TypeGuard): ... # error: [invalid-base]
|
||||
class D(TypeIs): ... # error: [invalid-base]
|
||||
class E(Concatenate): ... # error: [invalid-base]
|
||||
class F(Callable): ...
|
||||
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`"
|
||||
|
||||
reveal_type(F.__mro__) # revealed: tuple[Literal[F], @Todo(Support for Callable as a base class), Literal[object]]
|
||||
```
|
||||
|
||||
## Subscriptability
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Some of these are not subscriptable:
|
||||
|
||||
```py
|
||||
|
||||
@@ -25,6 +25,11 @@ x = "foo" # error: [invalid-assignment] "Object of type `Literal["foo"]` is not
|
||||
|
||||
## Tuple annotations are understood
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
`module.py`:
|
||||
|
||||
```py
|
||||
@@ -56,7 +61,7 @@ reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
|
||||
reveal_type(e) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(f) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(h) # revealed: tuple[@Todo(generics), @Todo(generics)]
|
||||
reveal_type(h) # revealed: tuple[@Todo(specialized non-generic class), @Todo(specialized non-generic class)]
|
||||
|
||||
reveal_type(i) # revealed: tuple[str | int, str | int]
|
||||
reveal_type(j) # revealed: tuple[str | int]
|
||||
|
||||
@@ -302,7 +302,7 @@ class C:
|
||||
|
||||
c_instance = C()
|
||||
reveal_type(c_instance.a) # revealed: Unknown | Literal[1]
|
||||
reveal_type(c_instance.b) # revealed: Unknown | @Todo(starred unpacking)
|
||||
reveal_type(c_instance.b) # revealed: Unknown
|
||||
```
|
||||
|
||||
#### Attributes defined in for-loop (unpacking)
|
||||
@@ -397,15 +397,27 @@ class IntIterable:
|
||||
def __iter__(self) -> IntIterator:
|
||||
return IntIterator()
|
||||
|
||||
class TupleIterator:
|
||||
def __next__(self) -> tuple[int, str]:
|
||||
return (1, "a")
|
||||
|
||||
class TupleIterable:
|
||||
def __iter__(self) -> TupleIterator:
|
||||
return TupleIterator()
|
||||
|
||||
class C:
|
||||
def __init__(self) -> None:
|
||||
[... for self.a in IntIterable()]
|
||||
[... for (self.b, self.c) in TupleIterable()]
|
||||
[... for self.d in IntIterable() for self.e in IntIterable()]
|
||||
|
||||
c_instance = C()
|
||||
|
||||
# TODO: Should be `Unknown | int`
|
||||
# error: [unresolved-attribute]
|
||||
reveal_type(c_instance.a) # revealed: Unknown
|
||||
reveal_type(c_instance.a) # revealed: Unknown | int
|
||||
reveal_type(c_instance.b) # revealed: Unknown | int
|
||||
reveal_type(c_instance.c) # revealed: Unknown | str
|
||||
reveal_type(c_instance.d) # revealed: Unknown | int
|
||||
reveal_type(c_instance.e) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
#### Conditionally declared / bound attributes
|
||||
@@ -1665,7 +1677,7 @@ functions are instances of that class:
|
||||
def f(): ...
|
||||
|
||||
reveal_type(f.__defaults__) # revealed: @Todo(full tuple[...] support) | None
|
||||
reveal_type(f.__kwdefaults__) # revealed: @Todo(generics) | None
|
||||
reveal_type(f.__kwdefaults__) # revealed: @Todo(specialized non-generic class) | None
|
||||
```
|
||||
|
||||
Some attributes are special-cased, however:
|
||||
@@ -1698,9 +1710,9 @@ Most attribute accesses on bool-literal types are delegated to `builtins.bool`,
|
||||
bools are instances of that class:
|
||||
|
||||
```py
|
||||
# revealed: bound method Literal[True].__and__(**kwargs: @Todo(todo signature **kwargs)) -> @Todo(return type of overloaded function)
|
||||
# revealed: Overload[(value: bool, /) -> bool, (value: int, /) -> int]
|
||||
reveal_type(True.__and__)
|
||||
# revealed: bound method Literal[False].__or__(**kwargs: @Todo(todo signature **kwargs)) -> @Todo(return type of overloaded function)
|
||||
# revealed: Overload[(value: bool, /) -> bool, (value: int, /) -> int]
|
||||
reveal_type(False.__or__)
|
||||
```
|
||||
|
||||
@@ -1716,7 +1728,8 @@ reveal_type(False.real) # revealed: Literal[0]
|
||||
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
|
||||
|
||||
```py
|
||||
reveal_type(b"foo".join) # revealed: bound method Literal[b"foo"].join(iterable_of_bytes: @Todo(generics), /) -> bytes
|
||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: @Todo(specialized non-generic class), /) -> bytes
|
||||
reveal_type(b"foo".join)
|
||||
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`), start: SupportsIndex | None = ellipsis, end: SupportsIndex | None = ellipsis, /) -> bool
|
||||
reveal_type(b"foo".endswith)
|
||||
```
|
||||
@@ -1819,6 +1832,89 @@ def f(never: Never):
|
||||
never.another_attribute = never
|
||||
```
|
||||
|
||||
### Cyclic implicit attributes
|
||||
|
||||
Inferring types for undeclared implicit attributes can be cyclic:
|
||||
|
||||
```py
|
||||
class C:
|
||||
def __init__(self):
|
||||
self.x = 1
|
||||
|
||||
def copy(self, other: "C"):
|
||||
self.x = other.x
|
||||
|
||||
reveal_type(C().x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
If the only assignment to a name is cyclic, we just infer `Unknown` for that attribute:
|
||||
|
||||
```py
|
||||
class D:
|
||||
def copy(self, other: "D"):
|
||||
self.x = other.x
|
||||
|
||||
reveal_type(D().x) # revealed: Unknown
|
||||
```
|
||||
|
||||
If there is an annotation for a name, we don't try to infer any type from the RHS of assignments to
|
||||
that name, so these cases don't trigger any cycle:
|
||||
|
||||
```py
|
||||
class E:
|
||||
def __init__(self):
|
||||
self.x: int = 1
|
||||
|
||||
def copy(self, other: "E"):
|
||||
self.x = other.x
|
||||
|
||||
reveal_type(E().x) # revealed: int
|
||||
|
||||
class F:
|
||||
def __init__(self):
|
||||
self.x = 1
|
||||
|
||||
def copy(self, other: "F"):
|
||||
self.x: int = other.x
|
||||
|
||||
reveal_type(F().x) # revealed: int
|
||||
|
||||
class G:
|
||||
def copy(self, other: "G"):
|
||||
self.x: int = other.x
|
||||
|
||||
reveal_type(G().x) # revealed: int
|
||||
```
|
||||
|
||||
We can even handle cycles involving multiple classes:
|
||||
|
||||
```py
|
||||
class A:
|
||||
def __init__(self):
|
||||
self.x = 1
|
||||
|
||||
def copy(self, other: "B"):
|
||||
self.x = other.x
|
||||
|
||||
class B:
|
||||
def copy(self, other: "A"):
|
||||
self.x = other.x
|
||||
|
||||
reveal_type(B().x) # revealed: Unknown | Literal[1]
|
||||
reveal_type(A().x) # revealed: Unknown | Literal[1]
|
||||
```
|
||||
|
||||
This case additionally tests our union/intersection simplification logic:
|
||||
|
||||
```py
|
||||
class H:
|
||||
def __init__(self):
|
||||
self.x = 1
|
||||
|
||||
def copy(self, other: "H"):
|
||||
self.x = other.x or self.x
|
||||
```
|
||||
|
||||
### Builtin types attributes
|
||||
|
||||
This test can probably be removed eventually, but we currently include it because we do not yet
|
||||
|
||||
@@ -310,9 +310,7 @@ reveal_type(A() + 1) # revealed: A
|
||||
reveal_type(1 + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + "foo") # revealed: A
|
||||
# TODO should be `A` since `str.__add__` doesn't support `A` instances
|
||||
# TODO overloads
|
||||
reveal_type("foo" + A()) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type("foo" + A()) # revealed: A
|
||||
|
||||
reveal_type(A() + b"foo") # revealed: A
|
||||
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
|
||||
@@ -320,16 +318,14 @@ reveal_type(b"foo" + A()) # revealed: bytes
|
||||
|
||||
reveal_type(A() + ()) # revealed: A
|
||||
# TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances
|
||||
reveal_type(() + A()) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(() + A()) # revealed: @Todo(full tuple[...] support)
|
||||
|
||||
literal_string_instance = "foo" * 1_000_000_000
|
||||
# the test is not testing what it's meant to be testing if this isn't a `LiteralString`:
|
||||
reveal_type(literal_string_instance) # revealed: LiteralString
|
||||
|
||||
reveal_type(A() + literal_string_instance) # revealed: A
|
||||
# TODO should be `A` since `str.__add__` doesn't support `A` instances
|
||||
# TODO overloads
|
||||
reveal_type(literal_string_instance + A()) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(literal_string_instance + A()) # revealed: A
|
||||
```
|
||||
|
||||
## Operations involving instances of classes inheriting from `Any`
|
||||
|
||||
@@ -50,9 +50,11 @@ reveal_type(1 ** (largest_u32 + 1)) # revealed: int
|
||||
reveal_type(2**largest_u32) # revealed: int
|
||||
|
||||
def variable(x: int):
|
||||
reveal_type(x**2) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(2**x) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(x**x) # revealed: @Todo(return type of overloaded function)
|
||||
reveal_type(x**2) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(2**x) # revealed: int
|
||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
||||
reveal_type(x**x) # revealed: int
|
||||
```
|
||||
|
||||
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
|
||||
|
||||
@@ -43,7 +43,7 @@ if True and (x := 1):
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
flag or (x := 1) or reveal_type(x) # revealed: Literal[1]
|
||||
flag or (x := 1) or reveal_type(x) # revealed: Never
|
||||
|
||||
# error: [unresolved-reference]
|
||||
flag or reveal_type(y) or (y := 1) # revealed: Unknown
|
||||
|
||||
@@ -21,6 +21,11 @@ reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
|
||||
|
||||
## Generic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def get_int[T]() -> int:
|
||||
return 42
|
||||
|
||||
@@ -94,7 +94,7 @@ function object. We model this explicitly, which means that we can access `__kwd
|
||||
methods, even though it is not available on `types.MethodType`:
|
||||
|
||||
```py
|
||||
reveal_type(bound_method.__kwdefaults__) # revealed: @Todo(generics) | None
|
||||
reveal_type(bound_method.__kwdefaults__) # revealed: @Todo(specialized non-generic class) | None
|
||||
```
|
||||
|
||||
## Basic method calls on class objects and instances
|
||||
@@ -399,6 +399,11 @@ reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound metho
|
||||
|
||||
### Classmethods mixed with other decorators
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
When a `@classmethod` is additionally decorated with another decorator, it is still treated as a
|
||||
class method:
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -201,3 +226,15 @@ def _(literals_2: Literal[0, 1], b: bool, flag: bool):
|
||||
# Now union the two:
|
||||
reveal_type(bool_and_literals_128 if flag else literals_128_shifted) # revealed: int
|
||||
```
|
||||
|
||||
## Simplifying gradually-equivalent types
|
||||
|
||||
If two types are gradually equivalent, we can keep just one of them in a union:
|
||||
|
||||
```py
|
||||
from typing import Any, Union
|
||||
from knot_extensions import Intersection, Not
|
||||
|
||||
def _(x: Union[Intersection[Any, Not[int]], Intersection[Any, Not[int]]]):
|
||||
reveal_type(x) # revealed: Any & ~int
|
||||
```
|
||||
|
||||
@@ -265,6 +265,11 @@ def f(flag: bool):
|
||||
|
||||
## Supers with Generic Classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from knot_extensions import TypeOf, static_assert, is_subtype_of
|
||||
|
||||
@@ -316,6 +321,11 @@ class A:
|
||||
|
||||
### Failing Condition Checks
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
`super()` requires its first argument to be a valid class, and its second argument to be either an
|
||||
instance or a subclass of the first. If either condition is violated, a `TypeError` is raised at
|
||||
runtime.
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Pattern matching
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
## With wildcard
|
||||
|
||||
```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
|
||||
@@ -297,6 +297,11 @@ reveal_type(WithoutEq(1) == WithoutEq(2)) # revealed: bool
|
||||
|
||||
### `order`
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
`order` is set to `False` by default. If `order=True`, `__lt__`, `__le__`, `__gt__`, and `__ge__`
|
||||
methods will be generated:
|
||||
|
||||
@@ -471,6 +476,11 @@ reveal_type(C.__init__) # revealed: (x: int = Literal[15], y: int = Literal[0],
|
||||
|
||||
## Generic dataclasses
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -537,14 +547,14 @@ the descriptor's `__get__` method as if it had been called on the class itself,
|
||||
for the `instance` argument.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
from typing import Literal, overload
|
||||
from dataclasses import dataclass
|
||||
|
||||
class ConvertToLength:
|
||||
_len: int = 0
|
||||
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: type) -> str: ...
|
||||
def __get__(self, instance: None, owner: type) -> Literal[""]: ...
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: type | None) -> int: ...
|
||||
def __get__(self, instance: object | None, owner: type | None) -> str | int:
|
||||
@@ -560,12 +570,10 @@ class ConvertToLength:
|
||||
class C:
|
||||
converter: ConvertToLength = ConvertToLength()
|
||||
|
||||
# TODO: Should be `(converter: str = Literal[""]) -> None` once we understand overloads
|
||||
reveal_type(C.__init__) # revealed: (converter: str = str | int) -> None
|
||||
reveal_type(C.__init__) # revealed: (converter: str = Literal[""]) -> None
|
||||
|
||||
c = C("abc")
|
||||
# TODO: Should be `int` once we understand overloads
|
||||
reveal_type(c.converter) # revealed: str | int
|
||||
reveal_type(c.converter) # revealed: int
|
||||
|
||||
# This is also okay:
|
||||
C()
|
||||
@@ -601,8 +609,7 @@ class AcceptsStrAndInt:
|
||||
class C:
|
||||
field: AcceptsStrAndInt = AcceptsStrAndInt()
|
||||
|
||||
# TODO: Should be `field: str | int = int` once we understand overloads
|
||||
reveal_type(C.__init__) # revealed: (field: Unknown = int) -> None
|
||||
reveal_type(C.__init__) # revealed: (field: str | int = int) -> None
|
||||
```
|
||||
|
||||
## `dataclasses.field`
|
||||
@@ -682,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:
|
||||
|
||||
@@ -145,10 +145,10 @@ def f(x: int) -> int:
|
||||
return x**2
|
||||
|
||||
# TODO: Should be `_lru_cache_wrapper[int]`
|
||||
reveal_type(f) # revealed: @Todo(generics)
|
||||
reveal_type(f) # revealed: @Todo(specialized non-generic class)
|
||||
|
||||
# TODO: Should be `int`
|
||||
reveal_type(f(1)) # revealed: @Todo(generics)
|
||||
reveal_type(f(1)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
## Lambdas as decorators
|
||||
|
||||
@@ -459,11 +459,9 @@ class Descriptor:
|
||||
class C:
|
||||
d: Descriptor = Descriptor()
|
||||
|
||||
# TODO: should be `Literal["called on class object"]
|
||||
reveal_type(C.d) # revealed: LiteralString
|
||||
reveal_type(C.d) # revealed: Literal["called on class object"]
|
||||
|
||||
# TODO: should be `Literal["called on instance"]
|
||||
reveal_type(C().d) # revealed: LiteralString
|
||||
reveal_type(C().d) # revealed: Literal["called on instance"]
|
||||
```
|
||||
|
||||
## Descriptor protocol for dunder methods
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Shadowing
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
## Implicit class shadowing
|
||||
|
||||
```py
|
||||
class C: ...
|
||||
|
||||
C = 1 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Implicit function shadowing
|
||||
|
||||
```py
|
||||
def f(): ...
|
||||
|
||||
f = 1 # error: [invalid-assignment]
|
||||
```
|
||||
@@ -8,14 +8,20 @@
|
||||
a, b = 1 # error: [not-iterable]
|
||||
```
|
||||
|
||||
## Too many values to unpack
|
||||
## Exactly too many values to unpack
|
||||
|
||||
```py
|
||||
a, b = (1, 2, 3) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Too few values to unpack
|
||||
## Exactly too few values to unpack
|
||||
|
||||
```py
|
||||
a, b = (1,) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Too few values to unpack
|
||||
|
||||
```py
|
||||
[a, *b, c, d] = (1, 2) # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Version-related syntax error diagnostics
|
||||
|
||||
## `match` statement
|
||||
|
||||
The `match` statement was introduced in Python 3.10.
|
||||
|
||||
### Before 3.10
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
We should emit a syntax error before 3.10.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
match 2: # error: 1 [invalid-syntax] "Cannot use `match` statement on Python 3.9 (syntax was added in Python 3.10)"
|
||||
case 1:
|
||||
print("it's one")
|
||||
```
|
||||
|
||||
### After 3.10
|
||||
|
||||
On or after 3.10, no error should be reported.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
match 2:
|
||||
case 1:
|
||||
print("it's one")
|
||||
```
|
||||
@@ -26,6 +26,11 @@ def _(never: Never, any_: Any, unknown: Unknown, flag: bool):
|
||||
|
||||
## Use case: Type narrowing and exhaustiveness checking
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
`assert_never` can be used in combination with type narrowing as a way to make sure that all cases
|
||||
are handled in a series of `isinstance` checks or other narrowing patterns that are supported.
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ from knot_extensions import Unknown
|
||||
|
||||
def f(x: Any, y: Unknown, z: Any | str | int):
|
||||
a = cast(dict[str, Any], x)
|
||||
reveal_type(a) # revealed: @Todo(generics)
|
||||
reveal_type(a) # revealed: @Todo(specialized non-generic class)
|
||||
|
||||
b = cast(Any, y)
|
||||
reveal_type(b) # revealed: Any
|
||||
|
||||
@@ -76,6 +76,11 @@ def g(x: Any = "foo"):
|
||||
|
||||
## Stub functions
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
### In Protocol
|
||||
|
||||
```py
|
||||
|
||||
@@ -56,6 +56,11 @@ def f() -> int:
|
||||
|
||||
### In Protocol
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from typing import Protocol, TypeVar
|
||||
|
||||
@@ -69,8 +74,6 @@ class Baz(Bar):
|
||||
T = TypeVar("T")
|
||||
|
||||
class Qux(Protocol[T]):
|
||||
# TODO: no error
|
||||
# error: [invalid-return-type]
|
||||
def f(self) -> int: ...
|
||||
|
||||
class Foo(Protocol):
|
||||
@@ -85,6 +88,11 @@ class Lorem(t[0]):
|
||||
|
||||
### In abstract method
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Generic classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
## PEP 695 syntax
|
||||
|
||||
TODO: Add a `red_knot_extension` function that asserts whether a function or class is generic.
|
||||
@@ -40,8 +45,6 @@ from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-base]
|
||||
class C(Generic[T]): ...
|
||||
```
|
||||
|
||||
@@ -159,12 +162,12 @@ consistent with each other.
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
def __new__(cls, x: T) -> "C"[T]:
|
||||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -176,7 +179,7 @@ class C[T]:
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -184,14 +187,14 @@ wrong_innards: C[int] = C("five")
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
def __new__(cls, x: T) -> "C"[T]:
|
||||
def __new__(cls, x: T) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
```
|
||||
|
||||
@@ -199,25 +202,25 @@ wrong_innards: C[int] = C("five")
|
||||
|
||||
```py
|
||||
class C[T]:
|
||||
def __new__(cls, *args, **kwargs) -> "C"[T]:
|
||||
def __new__(cls, *args, **kwargs) -> "C[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, x: T) -> None: ...
|
||||
|
||||
reveal_type(C(1)) # revealed: C[Literal[1]]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
wrong_innards: C[int] = C("five")
|
||||
|
||||
class D[T]:
|
||||
def __new__(cls, x: T) -> "D"[T]:
|
||||
def __new__(cls, x: T) -> "D[T]":
|
||||
return object.__new__(cls)
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
|
||||
reveal_type(D(1)) # revealed: D[Literal[1]]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`"
|
||||
wrong_innards: D[int] = D("five")
|
||||
```
|
||||
|
||||
@@ -242,7 +245,7 @@ reveal_type(C(1, "string")) # revealed: C[Unknown]
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(C(1, True)) # revealed: C[Unknown]
|
||||
|
||||
# TODO: error for the correct reason
|
||||
# TODO: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`"
|
||||
# error: [invalid-argument-type] "Argument to this function is incorrect: Expected `S`, found `Literal[1]`"
|
||||
wrong_innards: C[int] = C("five", 1)
|
||||
```
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Generic functions
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
## Typevar must be used at least twice
|
||||
|
||||
If you're only using a typevar for a single parameter, you don't need the typevar — just use
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# PEP 695 Generics
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
[PEP 695] and Python 3.12 introduced new, more ergonomic syntax for type variables.
|
||||
|
||||
## Type variables
|
||||
@@ -59,19 +64,19 @@ is.)
|
||||
from knot_extensions import is_fully_static, static_assert
|
||||
from typing import Any
|
||||
|
||||
def unbounded_unconstrained[T](t: list[T]) -> None:
|
||||
def unbounded_unconstrained[T](t: T) -> None:
|
||||
static_assert(is_fully_static(T))
|
||||
|
||||
def bounded[T: int](t: list[T]) -> None:
|
||||
def bounded[T: int](t: T) -> None:
|
||||
static_assert(is_fully_static(T))
|
||||
|
||||
def bounded_by_gradual[T: Any](t: list[T]) -> None:
|
||||
def bounded_by_gradual[T: Any](t: T) -> None:
|
||||
static_assert(not is_fully_static(T))
|
||||
|
||||
def constrained[T: (int, str)](t: list[T]) -> None:
|
||||
def constrained[T: (int, str)](t: T) -> None:
|
||||
static_assert(is_fully_static(T))
|
||||
|
||||
def constrained_by_gradual[T: (int, Any)](t: list[T]) -> None:
|
||||
def constrained_by_gradual[T: (int, Any)](t: T) -> None:
|
||||
static_assert(not is_fully_static(T))
|
||||
```
|
||||
|
||||
@@ -94,7 +99,7 @@ class Base(Super): ...
|
||||
class Sub(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def unbounded_unconstrained[T, U](t: list[T], u: list[U]) -> None:
|
||||
def unbounded_unconstrained[T, U](t: T, u: U) -> None:
|
||||
static_assert(is_assignable_to(T, T))
|
||||
static_assert(is_assignable_to(T, object))
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
@@ -124,7 +129,7 @@ is a final class, since the typevar can still be specialized to `Never`.)
|
||||
from typing import Any
|
||||
from typing_extensions import final
|
||||
|
||||
def bounded[T: Super](t: list[T]) -> None:
|
||||
def bounded[T: Super](t: T) -> None:
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
static_assert(not is_assignable_to(Super, T))
|
||||
@@ -135,7 +140,7 @@ def bounded[T: Super](t: list[T]) -> None:
|
||||
static_assert(not is_subtype_of(Super, T))
|
||||
static_assert(not is_subtype_of(Sub, T))
|
||||
|
||||
def bounded_by_gradual[T: Any](t: list[T]) -> None:
|
||||
def bounded_by_gradual[T: Any](t: T) -> None:
|
||||
static_assert(is_assignable_to(T, Any))
|
||||
static_assert(is_assignable_to(Any, T))
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
@@ -153,7 +158,7 @@ def bounded_by_gradual[T: Any](t: list[T]) -> None:
|
||||
@final
|
||||
class FinalClass: ...
|
||||
|
||||
def bounded_final[T: FinalClass](t: list[T]) -> None:
|
||||
def bounded_final[T: FinalClass](t: T) -> None:
|
||||
static_assert(is_assignable_to(T, FinalClass))
|
||||
static_assert(not is_assignable_to(FinalClass, T))
|
||||
|
||||
@@ -167,14 +172,14 @@ true even if both typevars are bounded by the same final class, since you can sp
|
||||
typevars to `Never` in addition to that final class.
|
||||
|
||||
```py
|
||||
def two_bounded[T: Super, U: Super](t: list[T], u: list[U]) -> None:
|
||||
def two_bounded[T: Super, U: Super](t: T, u: U) -> None:
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
static_assert(not is_subtype_of(T, U))
|
||||
static_assert(not is_subtype_of(U, T))
|
||||
|
||||
def two_final_bounded[T: FinalClass, U: FinalClass](t: list[T], u: list[U]) -> None:
|
||||
def two_final_bounded[T: FinalClass, U: FinalClass](t: T, u: U) -> None:
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
@@ -189,7 +194,7 @@ intersection of all of its constraints is a subtype of the typevar.
|
||||
```py
|
||||
from knot_extensions import Intersection
|
||||
|
||||
def constrained[T: (Base, Unrelated)](t: list[T]) -> None:
|
||||
def constrained[T: (Base, Unrelated)](t: T) -> None:
|
||||
static_assert(not is_assignable_to(T, Super))
|
||||
static_assert(not is_assignable_to(T, Base))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
@@ -214,7 +219,7 @@ def constrained[T: (Base, Unrelated)](t: list[T]) -> None:
|
||||
static_assert(not is_subtype_of(Super | Unrelated, T))
|
||||
static_assert(is_subtype_of(Intersection[Base, Unrelated], T))
|
||||
|
||||
def constrained_by_gradual[T: (Base, Any)](t: list[T]) -> None:
|
||||
def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
|
||||
static_assert(is_assignable_to(T, Super))
|
||||
static_assert(is_assignable_to(T, Base))
|
||||
static_assert(not is_assignable_to(T, Sub))
|
||||
@@ -256,7 +261,7 @@ distinct constraints, meaning that there is (still) no guarantee that they will
|
||||
the same type.
|
||||
|
||||
```py
|
||||
def two_constrained[T: (int, str), U: (int, str)](t: list[T], u: list[U]) -> None:
|
||||
def two_constrained[T: (int, str), U: (int, str)](t: T, u: U) -> None:
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
@@ -266,7 +271,7 @@ def two_constrained[T: (int, str), U: (int, str)](t: list[T], u: list[U]) -> Non
|
||||
@final
|
||||
class AnotherFinalClass: ...
|
||||
|
||||
def two_final_constrained[T: (FinalClass, AnotherFinalClass), U: (FinalClass, AnotherFinalClass)](t: list[T], u: list[U]) -> None:
|
||||
def two_final_constrained[T: (FinalClass, AnotherFinalClass), U: (FinalClass, AnotherFinalClass)](t: T, u: U) -> None:
|
||||
static_assert(not is_assignable_to(T, U))
|
||||
static_assert(not is_assignable_to(U, T))
|
||||
|
||||
@@ -285,7 +290,7 @@ non-singleton type.
|
||||
```py
|
||||
from knot_extensions import is_singleton, is_single_valued, static_assert
|
||||
|
||||
def unbounded_unconstrained[T](t: list[T]) -> None:
|
||||
def unbounded_unconstrained[T](t: T) -> None:
|
||||
static_assert(not is_singleton(T))
|
||||
static_assert(not is_single_valued(T))
|
||||
```
|
||||
@@ -294,7 +299,7 @@ A bounded typevar is not a singleton, even if its bound is a singleton, since it
|
||||
specialized to `Never`.
|
||||
|
||||
```py
|
||||
def bounded[T: None](t: list[T]) -> None:
|
||||
def bounded[T: None](t: T) -> None:
|
||||
static_assert(not is_singleton(T))
|
||||
static_assert(not is_single_valued(T))
|
||||
```
|
||||
@@ -305,14 +310,14 @@ specialize a constrained typevar to a subtype of a constraint.)
|
||||
```py
|
||||
from typing_extensions import Literal
|
||||
|
||||
def constrained_non_singletons[T: (int, str)](t: list[T]) -> None:
|
||||
def constrained_non_singletons[T: (int, str)](t: T) -> None:
|
||||
static_assert(not is_singleton(T))
|
||||
static_assert(not is_single_valued(T))
|
||||
|
||||
def constrained_singletons[T: (Literal[True], Literal[False])](t: list[T]) -> None:
|
||||
def constrained_singletons[T: (Literal[True], Literal[False])](t: T) -> None:
|
||||
static_assert(is_singleton(T))
|
||||
|
||||
def constrained_single_valued[T: (Literal[True], tuple[()])](t: list[T]) -> None:
|
||||
def constrained_single_valued[T: (Literal[True], tuple[()])](t: T) -> None:
|
||||
static_assert(is_single_valued(T))
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Scoping rules for type variables
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Most of these tests come from the [Scoping rules for type variables][scoping] section of the typing
|
||||
spec.
|
||||
|
||||
@@ -132,8 +137,6 @@ from typing import TypeVar, Generic
|
||||
T = TypeVar("T")
|
||||
S = TypeVar("S")
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-base]
|
||||
class Legacy(Generic[T]):
|
||||
def m(self, x: T, y: S) -> S:
|
||||
return y
|
||||
@@ -169,13 +172,11 @@ S = TypeVar("S")
|
||||
|
||||
def f(x: T) -> None:
|
||||
x: list[T] = []
|
||||
# TODO: error
|
||||
# TODO: invalid-assignment error
|
||||
y: list[S] = []
|
||||
|
||||
# TODO: no error
|
||||
# error: [invalid-base]
|
||||
class C(Generic[T]):
|
||||
# TODO: error
|
||||
# TODO: error: cannot use S if it's not in the current generic context
|
||||
x: list[S] = []
|
||||
|
||||
# This is not an error, as shown in the previous test
|
||||
@@ -195,11 +196,11 @@ S = TypeVar("S")
|
||||
|
||||
def f[T](x: T) -> None:
|
||||
x: list[T] = []
|
||||
# TODO: error
|
||||
# TODO: invalid assignment error
|
||||
y: list[S] = []
|
||||
|
||||
class C[T]:
|
||||
# TODO: error
|
||||
# TODO: error: cannot use S if it's not in the current generic context
|
||||
x: list[S] = []
|
||||
|
||||
def m1(self, x: S) -> S:
|
||||
@@ -254,8 +255,7 @@ def f[T](x: T, y: T) -> None:
|
||||
class Ok[S]: ...
|
||||
# TODO: error for reuse of typevar
|
||||
class Bad1[T]: ...
|
||||
# TODO: no non-subscriptable error, error for reuse of typevar
|
||||
# error: [non-subscriptable]
|
||||
# TODO: error for reuse of typevar
|
||||
class Bad2(Iterable[T]): ...
|
||||
```
|
||||
|
||||
@@ -268,8 +268,7 @@ class C[T]:
|
||||
class Ok1[S]: ...
|
||||
# TODO: error for reuse of typevar
|
||||
class Bad1[T]: ...
|
||||
# TODO: no non-subscriptable error, error for reuse of typevar
|
||||
# error: [non-subscriptable]
|
||||
# TODO: error for reuse of typevar
|
||||
class Bad2(Iterable[T]): ...
|
||||
```
|
||||
|
||||
@@ -283,7 +282,7 @@ class C[T]:
|
||||
ok1: list[T] = []
|
||||
|
||||
class Bad:
|
||||
# TODO: error
|
||||
# TODO: error: cannot refer to T in nested scope
|
||||
bad: list[T] = []
|
||||
|
||||
class Inner[S]: ...
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
# Variance
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
Type variables have a property called _variance_ that affects the subtyping and assignability
|
||||
relations. Much more detail can be found in the [spec]. To summarize, each typevar is either
|
||||
**covariant**, **contravariant**, **invariant**, or **bivariant**. (Note that bivariance is not
|
||||
currently mentioned in the typing spec, but is a fourth case that we must consider.)
|
||||
|
||||
For all of the examples below, we will consider a typevar `T`, a generic class using that typevar
|
||||
`C[T]`, and two types `A` and `B`.
|
||||
|
||||
## Covariance
|
||||
|
||||
With a covariant typevar, subtyping is in "alignment": if `A <: B`, then `C[A] <: C[B]`.
|
||||
|
||||
Types that "produce" data on demand are covariant in their typevar. If you expect a sequence of
|
||||
`int`s, someone can safely provide a sequence of `bool`s, since each `bool` element that you would
|
||||
get from the sequence is a valid `int`.
|
||||
|
||||
```py
|
||||
from knot_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
|
||||
from typing import Any
|
||||
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
class C[T]:
|
||||
def receive(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(C[B], C[A]))
|
||||
static_assert(not is_assignable_to(C[A], C[B]))
|
||||
static_assert(is_assignable_to(C[A], C[Any]))
|
||||
static_assert(is_assignable_to(C[B], C[Any]))
|
||||
static_assert(is_assignable_to(C[Any], C[A]))
|
||||
static_assert(is_assignable_to(C[Any], C[B]))
|
||||
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(C[B], C[A]))
|
||||
static_assert(not is_subtype_of(C[A], C[B]))
|
||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||
static_assert(not is_subtype_of(C[B], C[Any]))
|
||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||
|
||||
static_assert(is_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_equivalent_to(C[B], C[B]))
|
||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||
```
|
||||
|
||||
## Contravariance
|
||||
|
||||
With a contravariant typevar, subtyping is in "opposition": if `A <: B`, then `C[B] <: C[A]`.
|
||||
|
||||
Types that "consume" data are contravariant in their typevar. If you expect a consumer that receives
|
||||
`bool`s, someone can safely provide a consumer that expects to receive `int`s, since each `bool`
|
||||
that you pass into the consumer is a valid `int`.
|
||||
|
||||
```py
|
||||
from knot_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
|
||||
from typing import Any
|
||||
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
class C[T]:
|
||||
def send(self, value: T): ...
|
||||
|
||||
static_assert(not is_assignable_to(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(C[A], C[B]))
|
||||
static_assert(is_assignable_to(C[A], C[Any]))
|
||||
static_assert(is_assignable_to(C[B], C[Any]))
|
||||
static_assert(is_assignable_to(C[Any], C[A]))
|
||||
static_assert(is_assignable_to(C[Any], C[B]))
|
||||
|
||||
static_assert(not is_subtype_of(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(C[A], C[B]))
|
||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||
static_assert(not is_subtype_of(C[B], C[Any]))
|
||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||
|
||||
static_assert(is_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_equivalent_to(C[B], C[B]))
|
||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||
```
|
||||
|
||||
## Invariance
|
||||
|
||||
With an invariant typevar, _no_ specializations of the generic class are subtypes of each other.
|
||||
|
||||
This often occurs for types that are both producers _and_ consumers, like a mutable `list`.
|
||||
Iterating over the elements in a list would work with a covariant typevar, just like with the
|
||||
"producer" type above. Appending elements to a list would work with a contravariant typevar, just
|
||||
like with the "consumer" type above. However, a typevar cannot be both covariant and contravariant
|
||||
at the same time!
|
||||
|
||||
If you expect a mutable list of `int`s, it's not safe for someone to provide you with a mutable list
|
||||
of `bool`s, since you might try to add an element to the list: if you try to add an `int`, the list
|
||||
would no longer only contain elements that are subtypes of `bool`.
|
||||
|
||||
Conversely, if you expect a mutable list of `bool`s, it's not safe for someone to provide you with a
|
||||
mutable list of `int`s, since you might try to extract elements from the list: you expect every
|
||||
element that you extract to be a subtype of `bool`, but the list can contain any `int`.
|
||||
|
||||
In the end, if you expect a mutable list, you must always be given a list of exactly that type,
|
||||
since we can't know in advance which of the allowed methods you'll want to use.
|
||||
|
||||
```py
|
||||
from knot_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
|
||||
from typing import Any
|
||||
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
class C[T]:
|
||||
def send(self, value: T): ...
|
||||
def receive(self) -> T:
|
||||
raise ValueError
|
||||
|
||||
static_assert(not is_assignable_to(C[B], C[A]))
|
||||
static_assert(not is_assignable_to(C[A], C[B]))
|
||||
static_assert(is_assignable_to(C[A], C[Any]))
|
||||
static_assert(is_assignable_to(C[B], C[Any]))
|
||||
static_assert(is_assignable_to(C[Any], C[A]))
|
||||
static_assert(is_assignable_to(C[Any], C[B]))
|
||||
|
||||
static_assert(not is_subtype_of(C[B], C[A]))
|
||||
static_assert(not is_subtype_of(C[A], C[B]))
|
||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||
static_assert(not is_subtype_of(C[B], C[Any]))
|
||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||
|
||||
static_assert(is_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_equivalent_to(C[B], C[B]))
|
||||
static_assert(not is_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_gradual_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_gradual_equivalent_to(C[Any], C[B]))
|
||||
```
|
||||
|
||||
## Bivariance
|
||||
|
||||
With a bivariant typevar, _all_ specializations of the generic class are subtypes of (and in fact,
|
||||
equivalent to) each other.
|
||||
|
||||
This is a bit of pathological case, which really only happens when the class doesn't use the typevar
|
||||
at all. (If it did, it would have to be covariant, contravariant, or invariant, depending on _how_
|
||||
the typevar was used.)
|
||||
|
||||
```py
|
||||
from knot_extensions import is_assignable_to, is_equivalent_to, is_gradual_equivalent_to, is_subtype_of, static_assert, Unknown
|
||||
from typing import Any
|
||||
|
||||
class A: ...
|
||||
class B(A): ...
|
||||
|
||||
class C[T]:
|
||||
pass
|
||||
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_assignable_to(C[A], C[B]))
|
||||
static_assert(is_assignable_to(C[A], C[Any]))
|
||||
static_assert(is_assignable_to(C[B], C[Any]))
|
||||
static_assert(is_assignable_to(C[Any], C[A]))
|
||||
static_assert(is_assignable_to(C[Any], C[B]))
|
||||
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_subtype_of(C[A], C[B]))
|
||||
static_assert(not is_subtype_of(C[A], C[Any]))
|
||||
static_assert(not is_subtype_of(C[B], C[Any]))
|
||||
static_assert(not is_subtype_of(C[Any], C[A]))
|
||||
static_assert(not is_subtype_of(C[Any], C[B]))
|
||||
|
||||
static_assert(is_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_equivalent_to(C[B], C[B]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_equivalent_to(C[A], C[B]))
|
||||
static_assert(not is_equivalent_to(C[A], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[B], C[Any]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[A]))
|
||||
static_assert(not is_equivalent_to(C[Any], C[B]))
|
||||
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[A]))
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[B]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Any]))
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[Unknown]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[B]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[A], C[Any]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[B], C[Any]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[A]))
|
||||
# TODO: no error
|
||||
# error: [static-assert-error]
|
||||
static_assert(is_gradual_equivalent_to(C[Any], C[B]))
|
||||
```
|
||||
|
||||
[spec]: https://typing.python.org/en/latest/spec/generics.html#variance
|
||||
@@ -122,6 +122,11 @@ from c import Y # error: [unresolved-import]
|
||||
|
||||
## Esoteric definitions and redefinintions
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
We understand all public symbols defined in an external module as being imported by a `*` import,
|
||||
not just those that are defined in `StmtAssign` nodes and `StmtAnnAssign` nodes. This section
|
||||
provides tests for definitions, and redefinitions, that use more esoteric AST nodes.
|
||||
|
||||
@@ -842,7 +842,7 @@ def unknown(
|
||||
|
||||
### Mixed dynamic types
|
||||
|
||||
We currently do not simplify mixed dynamic types, but might consider doing so in the future:
|
||||
Gradually-equivalent types can be simplified out of intersections:
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
@@ -854,10 +854,10 @@ def mixed(
|
||||
i3: Intersection[Not[Any], Unknown],
|
||||
i4: Intersection[Not[Any], Not[Unknown]],
|
||||
) -> None:
|
||||
reveal_type(i1) # revealed: Any & Unknown
|
||||
reveal_type(i2) # revealed: Any & Unknown
|
||||
reveal_type(i3) # revealed: Any & Unknown
|
||||
reveal_type(i4) # revealed: Any & Unknown
|
||||
reveal_type(i1) # revealed: Any
|
||||
reveal_type(i2) # revealed: Any
|
||||
reveal_type(i3) # revealed: Any
|
||||
reveal_type(i4) # revealed: Any
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
@@ -216,6 +216,11 @@ reveal_type(A.__class__) # revealed: type[Unknown]
|
||||
|
||||
## PEP 695 generic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A[T: str](metaclass=M): ...
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Narrowing with assert statements
|
||||
|
||||
## `assert` a value `is None` or `is not None`
|
||||
|
||||
```py
|
||||
def _(x: str | None, y: str | None):
|
||||
assert x is not None
|
||||
reveal_type(x) # revealed: str
|
||||
assert y is None
|
||||
reveal_type(y) # revealed: None
|
||||
```
|
||||
|
||||
## `assert` a value is truthy or falsy
|
||||
|
||||
```py
|
||||
def _(x: bool, y: bool):
|
||||
assert x
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
assert not y
|
||||
reveal_type(y) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## `assert` with `is` and `==` for literals
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(x: Literal[1, 2, 3], y: Literal[1, 2, 3]):
|
||||
assert x is 2
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
assert y == 2
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
## `assert` with `isinstance`
|
||||
|
||||
```py
|
||||
def _(x: int | str):
|
||||
assert isinstance(x, int)
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## `assert` a value `in` a tuple
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(x: Literal[1, 2, 3], y: Literal[1, 2, 3]):
|
||||
assert x in (1, 2)
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
assert y not in (1, 2)
|
||||
reveal_type(y) # revealed: Literal[3]
|
||||
```
|
||||
@@ -223,3 +223,15 @@ def _(x: str | None, y: str | None):
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: str | None
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
def f() -> bool:
|
||||
return True
|
||||
|
||||
if x := f():
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
@@ -47,3 +47,16 @@ def _(flag1: bool, flag2: bool):
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
def f() -> int | str | None: ...
|
||||
|
||||
if isinstance(x := f(), int):
|
||||
reveal_type(x) # revealed: int
|
||||
elif isinstance(x, str):
|
||||
reveal_type(x) # revealed: str & ~int
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
```
|
||||
|
||||
@@ -78,3 +78,17 @@ def _(x: Literal[1, "a", "b", "c", "d"]):
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, "d"]
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f() -> Literal[1, 2, 3]:
|
||||
return 1
|
||||
|
||||
if (x := f()) in (1,):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
```
|
||||
|
||||
@@ -100,3 +100,16 @@ def _(flag: bool):
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[42]
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f() -> Literal[1, 2] | None: ...
|
||||
|
||||
if (x := f()) is None:
|
||||
reveal_type(x) # revealed: None
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
@@ -82,3 +82,14 @@ def _(x_flag: bool, y_flag: bool):
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
def f() -> int | str | None: ...
|
||||
|
||||
if (x := f()) is not None:
|
||||
reveal_type(x) # revealed: int | str
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
```
|
||||
|
||||
@@ -89,3 +89,18 @@ def _(flag1: bool, flag2: bool, a: int):
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f() -> Literal[1, 2, 3]:
|
||||
return 1
|
||||
|
||||
if (x := f()) != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
else:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Narrowing for `match` statements
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
## Single `match` pattern
|
||||
|
||||
```py
|
||||
@@ -34,8 +39,7 @@ match x:
|
||||
case A():
|
||||
reveal_type(x) # revealed: A
|
||||
case B():
|
||||
# TODO could be `B & ~A`
|
||||
reveal_type(x) # revealed: B
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
@@ -83,7 +87,7 @@ match x:
|
||||
case 6.0:
|
||||
reveal_type(x) # revealed: float
|
||||
case 1j:
|
||||
reveal_type(x) # revealed: complex
|
||||
reveal_type(x) # revealed: complex & ~float
|
||||
case b"foo":
|
||||
reveal_type(x) # revealed: Literal[b"foo"]
|
||||
|
||||
@@ -129,11 +133,11 @@ match x:
|
||||
case "foo" | 42 | None:
|
||||
reveal_type(x) # revealed: Literal["foo", 42] | None
|
||||
case "foo" | tuple():
|
||||
reveal_type(x) # revealed: Literal["foo"] | tuple
|
||||
reveal_type(x) # revealed: tuple
|
||||
case True | False:
|
||||
reveal_type(x) # revealed: bool
|
||||
case 3.14 | 2.718 | 1.414:
|
||||
reveal_type(x) # revealed: float
|
||||
reveal_type(x) # revealed: float & ~tuple
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
@@ -160,3 +164,49 @@ match x:
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Narrowing due to guard
|
||||
|
||||
```py
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
x = get_object()
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
match x:
|
||||
case str() | float() if type(x) is str:
|
||||
reveal_type(x) # revealed: str
|
||||
case "foo" | 42 | None if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[42]
|
||||
case False if x:
|
||||
reveal_type(x) # revealed: Never
|
||||
case "foo" if x := "bar":
|
||||
reveal_type(x) # revealed: Literal["bar"]
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
## Guard and reveal_type in guard
|
||||
|
||||
```py
|
||||
def get_object() -> object:
|
||||
return object()
|
||||
|
||||
x = get_object()
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
|
||||
match x:
|
||||
case str() | float() if type(x) is str and reveal_type(x): # revealed: str
|
||||
pass
|
||||
case "foo" | 42 | None if isinstance(x, int) and reveal_type(x): # revealed: Literal[42]
|
||||
pass
|
||||
case False if x and reveal_type(x): # revealed: Never
|
||||
pass
|
||||
case "foo" if (x := "bar") and reveal_type(x): # revealed: Literal["bar"]
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: object
|
||||
```
|
||||
|
||||
@@ -246,7 +246,7 @@ class MetaTruthy(type):
|
||||
|
||||
class MetaDeferred(type):
|
||||
def __bool__(self) -> MetaAmbiguous:
|
||||
return MetaAmbiguous()
|
||||
raise NotImplementedError
|
||||
|
||||
class AmbiguousClass(metaclass=MetaAmbiguous): ...
|
||||
class FalsyClass(metaclass=MetaFalsy): ...
|
||||
|
||||
@@ -111,6 +111,11 @@ def _(x: A | B):
|
||||
|
||||
## Narrowing for generic classes
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
Note that `type` returns the runtime class of an object, which does _not_ include specializations in
|
||||
the case of a generic class. (The typevars are erased.) That means we cannot narrow the type to the
|
||||
specialization that we compare with; we must narrow to an unknown specialization of the generic
|
||||
@@ -139,3 +144,13 @@ def _(x: Base):
|
||||
# express a constraint like `Base & ~ProperSubtypeOf[Base]`.
|
||||
reveal_type(x) # revealed: Base
|
||||
```
|
||||
|
||||
## Assignment expressions
|
||||
|
||||
```py
|
||||
def _(x: object):
|
||||
if (y := type(x)) is bool:
|
||||
reveal_type(y) # revealed: Literal[bool]
|
||||
if (type(y := x)) is bool:
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
638
crates/red_knot_python_semantic/resources/mdtest/overloads.md
Normal file
638
crates/red_knot_python_semantic/resources/mdtest/overloads.md
Normal file
@@ -0,0 +1,638 @@
|
||||
# Overloads
|
||||
|
||||
Reference: <https://typing.python.org/en/latest/spec/overload.html>
|
||||
|
||||
## `typing.overload`
|
||||
|
||||
The definition of `typing.overload` in typeshed is an identity function.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
def foo(x: int) -> int:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
bar = overload(foo)
|
||||
reveal_type(bar) # revealed: def foo(x: int) -> int
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def add() -> None: ...
|
||||
@overload
|
||||
def add(x: int) -> int: ...
|
||||
@overload
|
||||
def add(x: int, y: int) -> int: ...
|
||||
def add(x: int | None = None, y: int | None = None) -> int | None:
|
||||
return (x or 0) + (y or 0)
|
||||
|
||||
reveal_type(add) # revealed: Overload[() -> None, (x: int) -> int, (x: int, y: int) -> int]
|
||||
reveal_type(add()) # revealed: None
|
||||
reveal_type(add(1)) # revealed: int
|
||||
reveal_type(add(1, 2)) # revealed: int
|
||||
```
|
||||
|
||||
## Overriding
|
||||
|
||||
These scenarios are to verify that the overloaded and non-overloaded definitions are correctly
|
||||
overridden by each other.
|
||||
|
||||
An overloaded function is overriding another overloaded function:
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: int) -> int: ...
|
||||
def foo(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo(1)) # revealed: int
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: str) -> str: ...
|
||||
def foo(x: str | None = None) -> str | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: str) -> str]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo("")) # revealed: str
|
||||
```
|
||||
|
||||
A non-overloaded function is overriding an overloaded function:
|
||||
|
||||
```py
|
||||
def foo(x: int) -> int:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
```
|
||||
|
||||
An overloaded function is overriding a non-overloaded function:
|
||||
|
||||
```py
|
||||
reveal_type(foo) # revealed: def foo(x: int) -> int
|
||||
|
||||
@overload
|
||||
def foo() -> None: ...
|
||||
@overload
|
||||
def foo(x: bytes) -> bytes: ...
|
||||
def foo(x: bytes | None = None) -> bytes | None:
|
||||
return x
|
||||
|
||||
reveal_type(foo) # revealed: Overload[() -> None, (x: bytes) -> bytes]
|
||||
reveal_type(foo()) # revealed: None
|
||||
reveal_type(foo(b"")) # revealed: bytes
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo1:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
def method(self, x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
foo1 = Foo1()
|
||||
reveal_type(foo1.method) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(foo1.method()) # revealed: None
|
||||
reveal_type(foo1.method(1)) # revealed: int
|
||||
|
||||
class Foo2:
|
||||
@overload
|
||||
def method(self) -> None: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: str | None = None) -> str | None:
|
||||
return x
|
||||
|
||||
foo2 = Foo2()
|
||||
reveal_type(foo2.method) # revealed: Overload[() -> None, (x: str) -> str]
|
||||
reveal_type(foo2.method()) # revealed: None
|
||||
reveal_type(foo2.method("")) # revealed: str
|
||||
```
|
||||
|
||||
## Constructor
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: int) -> None: ...
|
||||
def __init__(self, x: int | None = None) -> None:
|
||||
self.x = x
|
||||
|
||||
foo = Foo()
|
||||
reveal_type(foo) # revealed: Foo
|
||||
reveal_type(foo.x) # revealed: Unknown | int | None
|
||||
|
||||
foo1 = Foo(1)
|
||||
reveal_type(foo1) # revealed: Foo
|
||||
reveal_type(foo1.x) # revealed: Unknown | int | None
|
||||
```
|
||||
|
||||
## Version specific
|
||||
|
||||
Function definitions can vary between multiple Python versions.
|
||||
|
||||
### Overload and non-overload (3.9)
|
||||
|
||||
Here, the same function is overloaded in one version and not in another.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
def func(x: int) -> int:
|
||||
return x
|
||||
|
||||
elif sys.version_info <= (3, 12):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
def func(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: def func(x: int) -> int
|
||||
func() # error: [missing-argument]
|
||||
```
|
||||
|
||||
### Overload and non-overload (3.10)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
```py
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
def func(x: int) -> int:
|
||||
return x
|
||||
|
||||
elif sys.version_info <= (3, 12):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
def func(x: int | None = None) -> int | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: int) -> int]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: int
|
||||
```
|
||||
|
||||
### Some overloads are version specific (3.9)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.9"
|
||||
```
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from overloaded import func
|
||||
|
||||
reveal_type(func) # revealed: Overload[(x: int) -> int, (x: str) -> str]
|
||||
func() # error: [no-matching-overload]
|
||||
reveal_type(func(1)) # revealed: int
|
||||
reveal_type(func("")) # revealed: str
|
||||
```
|
||||
|
||||
### Some overloads are version specific (3.10)
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.10"
|
||||
```
|
||||
|
||||
`overloaded.pyi`:
|
||||
|
||||
```pyi
|
||||
import sys
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
`main.py`:
|
||||
|
||||
```py
|
||||
from overloaded import func
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: int) -> int, (x: str) -> str]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: int
|
||||
reveal_type(func("")) # revealed: str
|
||||
```
|
||||
|
||||
## Generic
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
For an overloaded generic function, it's not necessary for all overloads to be generic.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func() -> None: ...
|
||||
@overload
|
||||
def func[T](x: T) -> T: ...
|
||||
def func[T](x: T | None = None) -> T | None:
|
||||
return x
|
||||
|
||||
reveal_type(func) # revealed: Overload[() -> None, (x: T) -> T]
|
||||
reveal_type(func()) # revealed: None
|
||||
reveal_type(func(1)) # revealed: Literal[1]
|
||||
reveal_type(func("")) # revealed: Literal[""]
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
### At least two overloads
|
||||
|
||||
At least two `@overload`-decorated definitions must be present.
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
# TODO: error
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
def func(x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
### Overload without an implementation
|
||||
|
||||
#### Regular modules
|
||||
|
||||
In regular modules, a series of `@overload`-decorated definitions must be followed by exactly one
|
||||
non-`@overload`-decorated definition (for the same function/method).
|
||||
|
||||
```py
|
||||
from typing import overload
|
||||
|
||||
# TODO: error because implementation does not exists
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
|
||||
class Foo:
|
||||
# TODO: error because implementation does not exists
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Stub files
|
||||
|
||||
Overload definitions within stub files are exempt from this check.
|
||||
|
||||
```pyi
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def func(x: int) -> int: ...
|
||||
@overload
|
||||
def func(x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Protocols
|
||||
|
||||
Overload definitions within protocols are exempt from this check.
|
||||
|
||||
```py
|
||||
from typing import Protocol, overload
|
||||
|
||||
class Foo(Protocol):
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
#### Abstract methods
|
||||
|
||||
Overload definitions within abstract base classes are exempt from this check.
|
||||
|
||||
```py
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import overload
|
||||
|
||||
class AbstractFoo(ABC):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
Using the `@abstractmethod` decorator requires that the class's metaclass is `ABCMeta` or is derived
|
||||
from it.
|
||||
|
||||
```py
|
||||
class Foo:
|
||||
# TODO: Error because implementation does not exists
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
And, the `@abstractmethod` decorator must be present on all the `@overload`-ed methods.
|
||||
|
||||
```py
|
||||
class PartialFoo1(ABC):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
def f(self, x: str) -> str: ...
|
||||
|
||||
class PartialFoo(ABC):
|
||||
@overload
|
||||
def f(self, x: int) -> int: ...
|
||||
@overload
|
||||
@abstractmethod
|
||||
def f(self, x: str) -> str: ...
|
||||
```
|
||||
|
||||
### Inconsistent decorators
|
||||
|
||||
#### `@staticmethod` / `@classmethod`
|
||||
|
||||
If one overload signature is decorated with `@staticmethod` or `@classmethod`, all overload
|
||||
signatures must be similarly decorated. The implementation, if present, must also have a consistent
|
||||
decorator.
|
||||
|
||||
```py
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import overload
|
||||
|
||||
class CheckStaticMethod:
|
||||
# TODO: error because `@staticmethod` does not exist on all overloads
|
||||
@overload
|
||||
def method1(x: int) -> int: ...
|
||||
@overload
|
||||
def method1(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method1(x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@staticmethod` does not exist on all overloads
|
||||
@overload
|
||||
def method2(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method2(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method2(x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@staticmethod` does not exist on the implementation
|
||||
@overload
|
||||
@staticmethod
|
||||
def method3(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method3(x: str) -> str: ...
|
||||
def method3(x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
@overload
|
||||
@staticmethod
|
||||
def method4(x: int) -> int: ...
|
||||
@overload
|
||||
@staticmethod
|
||||
def method4(x: str) -> str: ...
|
||||
@staticmethod
|
||||
def method4(x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class CheckClassMethod:
|
||||
def __init__(self, x: int) -> None:
|
||||
self.x = x
|
||||
# TODO: error because `@classmethod` does not exist on all overloads
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from1(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
def try_from1(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
def try_from1(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
# TODO: error because `@classmethod` does not exist on all overloads
|
||||
@overload
|
||||
def try_from2(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from2(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
def try_from2(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
# TODO: error because `@classmethod` does not exist on the implementation
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from3(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from3(cls, x: str) -> None: ...
|
||||
def try_from3(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from4(cls, x: int) -> CheckClassMethod: ...
|
||||
@overload
|
||||
@classmethod
|
||||
def try_from4(cls, x: str) -> None: ...
|
||||
@classmethod
|
||||
def try_from4(cls, x: int | str) -> CheckClassMethod | None:
|
||||
if isinstance(x, int):
|
||||
return cls(x)
|
||||
return None
|
||||
```
|
||||
|
||||
#### `@final` / `@override`
|
||||
|
||||
If a `@final` or `@override` decorator is supplied for a function with overloads, the decorator
|
||||
should be applied only to the overload implementation if it is present.
|
||||
|
||||
```py
|
||||
from typing_extensions import final, overload, override
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
def method1(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method1(self, x: str) -> str: ...
|
||||
@final
|
||||
def method1(self, x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@final` is not on the implementation
|
||||
@overload
|
||||
@final
|
||||
def method2(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method2(self, x: str) -> str: ...
|
||||
def method2(self, x: int | str) -> int | str:
|
||||
return x
|
||||
# TODO: error because `@final` is not on the implementation
|
||||
@overload
|
||||
def method3(self, x: int) -> int: ...
|
||||
@overload
|
||||
@final
|
||||
def method3(self, x: str) -> str: ...
|
||||
def method3(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Base:
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub1(Base):
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
@override
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub2(Base):
|
||||
# TODO: error because `@override` is not on the implementation
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
|
||||
class Sub3(Base):
|
||||
# TODO: error because `@override` is not on the implementation
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
def method(self, x: int | str) -> int | str:
|
||||
return x
|
||||
```
|
||||
|
||||
#### `@final` / `@override` in stub files
|
||||
|
||||
If an overload implementation isn’t present (for example, in a stub file), the `@final` or
|
||||
`@override` decorator should be applied only to the first overload.
|
||||
|
||||
```pyi
|
||||
from typing_extensions import final, overload, override
|
||||
|
||||
class Foo:
|
||||
@overload
|
||||
@final
|
||||
def method1(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method1(self, x: str) -> str: ...
|
||||
|
||||
# TODO: error because `@final` is not on the first overload
|
||||
@overload
|
||||
def method2(self, x: int) -> int: ...
|
||||
@final
|
||||
@overload
|
||||
def method2(self, x: str) -> str: ...
|
||||
|
||||
class Base:
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
|
||||
class Sub1(Base):
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
def method(self, x: str) -> str: ...
|
||||
|
||||
class Sub2(Base):
|
||||
# TODO: error because `@override` is not on the first overload
|
||||
@overload
|
||||
def method(self, x: int) -> int: ...
|
||||
@overload
|
||||
@override
|
||||
def method(self, x: str) -> str: ...
|
||||
```
|
||||
@@ -15,6 +15,11 @@ types, on the other hand: a type which is defined by its properties and behaviou
|
||||
|
||||
## Defining a protocol
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
A protocol is defined by inheriting from the `Protocol` class, which is annotated as an instance of
|
||||
`_SpecialForm` in typeshed's stubs.
|
||||
|
||||
@@ -23,8 +28,7 @@ from typing import Protocol
|
||||
|
||||
class MyProtocol(Protocol): ...
|
||||
|
||||
# TODO: at runtime this is `(<class '__main__.MyProtocol'>, <class 'typing.Protocol'>, <class 'typing.Generic'>, <class 'object'>)`
|
||||
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], @Todo(protocol), Literal[object]]
|
||||
reveal_type(MyProtocol.__mro__) # revealed: tuple[Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||
```
|
||||
|
||||
Just like for any other class base, it is an error for `Protocol` to appear multiple times in a
|
||||
@@ -36,27 +40,63 @@ class Foo(Protocol, Protocol): ... # error: [inconsistent-mro]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
Protocols can also be generic, either by including `Generic[]` in the bases list, subscripting
|
||||
`Protocol` directly in the bases list, using PEP-695 type parameters, or some combination of the
|
||||
above:
|
||||
|
||||
```py
|
||||
from typing import TypeVar, Generic
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class Bar0(Protocol[T]):
|
||||
x: T
|
||||
|
||||
class Bar1(Protocol[T], Generic[T]):
|
||||
x: T
|
||||
|
||||
class Bar2[T](Protocol):
|
||||
x: T
|
||||
|
||||
class Bar3[T](Protocol[T]):
|
||||
x: T
|
||||
```
|
||||
|
||||
It's an error to include both bare `Protocol` and subscripted `Protocol[]` in the bases list
|
||||
simultaneously:
|
||||
|
||||
```py
|
||||
# TODO: should emit a `[duplicate-bases]` error here:
|
||||
class DuplicateBases(Protocol, Protocol[T]):
|
||||
x: T
|
||||
|
||||
# TODO: should not have `Generic` multiple times and `Protocol` multiple times
|
||||
# revealed: tuple[Literal[DuplicateBases], typing.Protocol, typing.Generic, @Todo(`Protocol[]` subscript), @Todo(`Generic[]` subscript), Literal[object]]
|
||||
reveal_type(DuplicateBases.__mro__)
|
||||
```
|
||||
|
||||
The introspection helper `typing(_extensions).is_protocol` can be used to verify whether a class is
|
||||
a protocol class or not:
|
||||
|
||||
```py
|
||||
from typing_extensions import is_protocol
|
||||
|
||||
# TODO: should be `Literal[True]`
|
||||
reveal_type(is_protocol(MyProtocol)) # revealed: bool
|
||||
reveal_type(is_protocol(MyProtocol)) # revealed: Literal[True]
|
||||
reveal_type(is_protocol(Bar0)) # revealed: Literal[True]
|
||||
reveal_type(is_protocol(Bar1)) # revealed: Literal[True]
|
||||
reveal_type(is_protocol(Bar2)) # revealed: Literal[True]
|
||||
reveal_type(is_protocol(Bar3)) # revealed: Literal[True]
|
||||
|
||||
class NotAProtocol: ...
|
||||
|
||||
# TODO: should be `Literal[False]`
|
||||
reveal_type(is_protocol(NotAProtocol)) # revealed: bool
|
||||
reveal_type(is_protocol(NotAProtocol)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
A type checker should follow the typeshed stubs if a non-class is passed in, and typeshed's stubs
|
||||
indicate that the argument passed in must be an instance of `type`. `Literal[False]` should be
|
||||
inferred as the return type, however.
|
||||
indicate that the argument passed in must be an instance of `type`.
|
||||
|
||||
```py
|
||||
# TODO: the diagnostic is correct, but should infer `Literal[False]`
|
||||
# We could also reasonably infer `Literal[False]` here, but it probably doesn't matter that much:
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(is_protocol("not a class")) # revealed: bool
|
||||
```
|
||||
@@ -67,12 +107,10 @@ it is not sufficient for it to have `Protocol` in its MRO.
|
||||
```py
|
||||
class SubclassOfMyProtocol(MyProtocol): ...
|
||||
|
||||
# TODO
|
||||
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], @Todo(protocol), Literal[object]]
|
||||
# revealed: tuple[Literal[SubclassOfMyProtocol], Literal[MyProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||
reveal_type(SubclassOfMyProtocol.__mro__)
|
||||
|
||||
# TODO: should be `Literal[False]`
|
||||
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: bool
|
||||
reveal_type(is_protocol(SubclassOfMyProtocol)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
A protocol class may inherit from other protocols, however, as long as it re-inherits from
|
||||
@@ -81,38 +119,33 @@ A protocol class may inherit from other protocols, however, as long as it re-inh
|
||||
```py
|
||||
class SubProtocol(MyProtocol, Protocol): ...
|
||||
|
||||
# TODO: should be `Literal[True]`
|
||||
reveal_type(is_protocol(SubProtocol)) # revealed: bool
|
||||
reveal_type(is_protocol(SubProtocol)) # revealed: Literal[True]
|
||||
|
||||
class OtherProtocol(Protocol):
|
||||
some_attribute: str
|
||||
|
||||
class ComplexInheritance(SubProtocol, OtherProtocol, Protocol): ...
|
||||
|
||||
# TODO
|
||||
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[object]]
|
||||
# revealed: tuple[Literal[ComplexInheritance], Literal[SubProtocol], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||
reveal_type(ComplexInheritance.__mro__)
|
||||
|
||||
# TODO: should be `Literal[True]`
|
||||
reveal_type(is_protocol(ComplexInheritance)) # revealed: bool
|
||||
reveal_type(is_protocol(ComplexInheritance)) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
If `Protocol` is present in the bases tuple, all other bases in the tuple must be protocol classes,
|
||||
or `TypeError` is raised at runtime when the class is created.
|
||||
|
||||
```py
|
||||
# TODO: should emit `[invalid-protocol]`
|
||||
# error: [invalid-protocol] "Protocol class `Invalid` cannot inherit from non-protocol class `NotAProtocol`"
|
||||
class Invalid(NotAProtocol, Protocol): ...
|
||||
|
||||
# TODO
|
||||
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
|
||||
# revealed: tuple[Literal[Invalid], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||
reveal_type(Invalid.__mro__)
|
||||
|
||||
# TODO: should emit an `[invalid-protocol`] error
|
||||
# error: [invalid-protocol] "Protocol class `AlsoInvalid` cannot inherit from non-protocol class `NotAProtocol`"
|
||||
class AlsoInvalid(MyProtocol, OtherProtocol, NotAProtocol, Protocol): ...
|
||||
|
||||
# TODO
|
||||
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], @Todo(protocol), Literal[object]]
|
||||
# revealed: tuple[Literal[AlsoInvalid], Literal[MyProtocol], Literal[OtherProtocol], Literal[NotAProtocol], typing.Protocol, typing.Generic, Literal[object]]
|
||||
reveal_type(AlsoInvalid.__mro__)
|
||||
```
|
||||
|
||||
@@ -130,12 +163,12 @@ T = TypeVar("T")
|
||||
# type checkers.
|
||||
class Fine(Protocol, object): ...
|
||||
|
||||
# TODO
|
||||
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], @Todo(protocol), Literal[object]]
|
||||
reveal_type(Fine.__mro__) # revealed: tuple[Literal[Fine], typing.Protocol, typing.Generic, Literal[object]]
|
||||
|
||||
# TODO: should not error
|
||||
class StillFine(Protocol, Generic[T], object): ... # error: [invalid-base]
|
||||
class StillFine(Protocol, Generic[T], object): ...
|
||||
class EvenThis[T](Protocol, object): ...
|
||||
class OrThis(Protocol[T], Generic[T]): ...
|
||||
class AndThis(Protocol[T], Generic[T], object): ...
|
||||
```
|
||||
|
||||
And multiple inheritance from a mix of protocol and non-protocol classes is fine as long as
|
||||
@@ -144,8 +177,7 @@ And multiple inheritance from a mix of protocol and non-protocol classes is fine
|
||||
```py
|
||||
class FineAndDandy(MyProtocol, OtherProtocol, NotAProtocol): ...
|
||||
|
||||
# TODO
|
||||
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], @Todo(protocol), Literal[NotAProtocol], Literal[object]]
|
||||
# revealed: tuple[Literal[FineAndDandy], Literal[MyProtocol], Literal[OtherProtocol], typing.Protocol, typing.Generic, Literal[NotAProtocol], Literal[object]]
|
||||
reveal_type(FineAndDandy.__mro__)
|
||||
```
|
||||
|
||||
@@ -153,8 +185,7 @@ But if `Protocol` is not present in the bases list, the resulting class doesn't
|
||||
class anymore:
|
||||
|
||||
```py
|
||||
# TODO: should reveal `Literal[False]`
|
||||
reveal_type(is_protocol(FineAndDandy)) # revealed: bool
|
||||
reveal_type(is_protocol(FineAndDandy)) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
A class does not *have* to inherit from a protocol class in order for it to be considered a subtype
|
||||
@@ -222,18 +253,21 @@ reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool
|
||||
```py
|
||||
import typing
|
||||
import typing_extensions
|
||||
from knot_extensions import static_assert, is_equivalent_to
|
||||
from knot_extensions import static_assert, is_equivalent_to, TypeOf
|
||||
|
||||
static_assert(is_equivalent_to(TypeOf[typing.Protocol], TypeOf[typing_extensions.Protocol]))
|
||||
static_assert(is_equivalent_to(int | str | TypeOf[typing.Protocol], TypeOf[typing_extensions.Protocol] | str | int))
|
||||
|
||||
class Foo(typing.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: should not error
|
||||
class Bar(typing_extensions.Protocol): # error: [invalid-base]
|
||||
class Bar(typing_extensions.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(typing_extensions.is_protocol(Foo)) # error: [static-assert-error]
|
||||
static_assert(typing_extensions.is_protocol(Bar)) # error: [static-assert-error]
|
||||
static_assert(typing_extensions.is_protocol(Foo))
|
||||
static_assert(typing_extensions.is_protocol(Bar))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(Foo, Bar)) # error: [static-assert-error]
|
||||
```
|
||||
|
||||
@@ -244,14 +278,14 @@ The same goes for `typing.runtime_checkable` and `typing_extensions.runtime_chec
|
||||
class RuntimeCheckableFoo(typing.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: should not error
|
||||
@typing.runtime_checkable
|
||||
class RuntimeCheckableBar(typing_extensions.Protocol): # error: [invalid-base]
|
||||
class RuntimeCheckableBar(typing_extensions.Protocol):
|
||||
x: int
|
||||
|
||||
# TODO: these should pass
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableFoo)) # error: [static-assert-error]
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableBar)) # error: [static-assert-error]
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableFoo))
|
||||
static_assert(typing_extensions.is_protocol(RuntimeCheckableBar))
|
||||
|
||||
# TODO: should pass
|
||||
static_assert(is_equivalent_to(RuntimeCheckableFoo, RuntimeCheckableBar)) # error: [static-assert-error]
|
||||
|
||||
# These should not error because the protocols are decorated with `@runtime_checkable`
|
||||
@@ -259,6 +293,15 @@ isinstance(object(), RuntimeCheckableFoo)
|
||||
isinstance(object(), RuntimeCheckableBar)
|
||||
```
|
||||
|
||||
However, we understand that they are not necessarily the same symbol at the same memory address at
|
||||
runtime -- these reveal `bool` rather than `Literal[True]` or `Literal[False]`, which would be
|
||||
incorrect:
|
||||
|
||||
```py
|
||||
reveal_type(typing.Protocol is typing_extensions.Protocol) # revealed: bool
|
||||
reveal_type(typing.Protocol is not typing_extensions.Protocol) # revealed: bool
|
||||
```
|
||||
|
||||
## Calls to protocol classes
|
||||
|
||||
Neither `Protocol`, nor any protocol class, can be directly instantiated:
|
||||
@@ -304,8 +347,7 @@ via `typing_extensions`.
|
||||
```py
|
||||
from typing_extensions import Protocol, get_protocol_members
|
||||
|
||||
# TODO: should not error
|
||||
class Foo(Protocol): # error: [invalid-base]
|
||||
class Foo(Protocol):
|
||||
x: int
|
||||
|
||||
@property
|
||||
@@ -330,7 +372,7 @@ class Foo(Protocol): # error: [invalid-base]
|
||||
# `tuple[Literal["x"], Literal["y"], Literal["z"], Literal["method_member"]]`
|
||||
#
|
||||
# `frozenset[Literal["x", "y", "z", "method_member"]]`
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: @Todo(generics)
|
||||
reveal_type(get_protocol_members(Foo)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
Calling `get_protocol_members` on a non-protocol class raises an error at runtime:
|
||||
@@ -339,15 +381,14 @@ Calling `get_protocol_members` on a non-protocol class raises an error at runtim
|
||||
class NotAProtocol: ...
|
||||
|
||||
# TODO: should emit `[invalid-protocol]` error, should reveal `Unknown`
|
||||
reveal_type(get_protocol_members(NotAProtocol)) # revealed: @Todo(generics)
|
||||
reveal_type(get_protocol_members(NotAProtocol)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
Certain special attributes and methods are not considered protocol members at runtime, and should
|
||||
not be considered protocol members by type checkers either:
|
||||
|
||||
```py
|
||||
# TODO: should not error
|
||||
class Lumberjack(Protocol): # error: [invalid-base]
|
||||
class Lumberjack(Protocol):
|
||||
__slots__ = ()
|
||||
__match_args__ = ()
|
||||
x: int
|
||||
@@ -359,7 +400,7 @@ class Lumberjack(Protocol): # error: [invalid-base]
|
||||
self.x = x
|
||||
|
||||
# TODO: `tuple[Literal["x"]]` or `frozenset[Literal["x"]]`
|
||||
reveal_type(get_protocol_members(Lumberjack)) # revealed: @Todo(generics)
|
||||
reveal_type(get_protocol_members(Lumberjack)) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
## Subtyping of protocols with attribute members
|
||||
|
||||
@@ -14,7 +14,7 @@ reveal_type(__package__) # revealed: str | None
|
||||
reveal_type(__doc__) # revealed: str | None
|
||||
reveal_type(__spec__) # revealed: ModuleSpec | None
|
||||
|
||||
reveal_type(__path__) # revealed: @Todo(generics)
|
||||
reveal_type(__path__) # revealed: @Todo(specialized non-generic class)
|
||||
|
||||
class X:
|
||||
reveal_type(__name__) # revealed: str
|
||||
@@ -59,7 +59,7 @@ reveal_type(typing.__eq__) # revealed: bound method ModuleType.__eq__(value: ob
|
||||
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
|
||||
|
||||
# TODO: needs support generics; should be `dict[str, Any]`:
|
||||
reveal_type(typing.__dict__) # revealed: @Todo(generics)
|
||||
reveal_type(typing.__dict__) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
Typeshed includes a fake `__getattr__` method in the stub for `types.ModuleType` to help out with
|
||||
@@ -92,8 +92,8 @@ import foo
|
||||
from foo import __dict__ as foo_dict
|
||||
|
||||
# TODO: needs support generics; should be `dict[str, Any]` for both of these:
|
||||
reveal_type(foo.__dict__) # revealed: @Todo(generics)
|
||||
reveal_type(foo_dict) # revealed: @Todo(generics)
|
||||
reveal_type(foo.__dict__) # revealed: @Todo(specialized non-generic class)
|
||||
reveal_type(foo_dict) # revealed: @Todo(specialized non-generic class)
|
||||
```
|
||||
|
||||
## Conditionally global or `ModuleType` attribute
|
||||
|
||||
@@ -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
|
||||
|
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user