Compare commits
3 Commits
0.8.3
...
micha/erro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6b2544993 | ||
|
|
fe78d50560 | ||
|
|
b39def2915 |
20
.github/workflows/build-binaries.yml
vendored
20
.github/workflows/build-binaries.yml
vendored
@@ -53,9 +53,9 @@ jobs:
|
||||
args: --out dist
|
||||
- name: "Test sdist"
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.tar.gz --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload sdist"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
args: --release --locked --out dist
|
||||
- name: "Test wheel - aarch64"
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
- name: "Upload wheels"
|
||||
@@ -186,9 +186,9 @@ jobs:
|
||||
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -236,9 +236,9 @@ jobs:
|
||||
- name: "Test wheel"
|
||||
if: ${{ startsWith(matrix.target, 'x86_64') }}
|
||||
run: |
|
||||
pip install dist/${PACKAGE_NAME}-*.whl --force-reinstall
|
||||
"${MODULE_NAME}" --help
|
||||
python -m "${MODULE_NAME}" --help
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
|
||||
${{ env.MODULE_NAME }} --help
|
||||
python -m ${{ env.MODULE_NAME }} --help
|
||||
- name: "Upload wheels"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
15
.github/workflows/build-docker.yml
vendored
15
.github/workflows/build-docker.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Normalize Platform Pair (replace / with -)
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_TUPLE=${platform//\//-}" >> "$GITHUB_ENV"
|
||||
echo "PLATFORM_TUPLE=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
- name: Build and push by digest
|
||||
@@ -87,10 +87,9 @@ jobs:
|
||||
outputs: type=image,name=${{ env.RUFF_BASE_IMG }},push-by-digest=true,name-canonical=true,push=${{ inputs.plan != '' && !fromJson(inputs.plan).announcement_tag_is_implicit }}
|
||||
|
||||
- name: Export digests
|
||||
env:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digests
|
||||
@@ -144,7 +143,7 @@ jobs:
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf "${RUFF_BASE_IMG}@sha256:%s " *)
|
||||
$(printf '${{ env.RUFF_BASE_IMG }}@sha256:%s ' *)
|
||||
|
||||
docker-publish-extra:
|
||||
name: Publish additional Docker image based on ${{ matrix.image-mapping }}
|
||||
@@ -183,7 +182,7 @@ jobs:
|
||||
# Generate Dockerfile content
|
||||
cat <<EOF > Dockerfile
|
||||
FROM ${BASE_IMAGE}
|
||||
COPY --from=${RUFF_BASE_IMG}:latest /ruff /usr/local/bin/ruff
|
||||
COPY --from=${{ env.RUFF_BASE_IMG }}:latest /ruff /usr/local/bin/ruff
|
||||
ENTRYPOINT []
|
||||
CMD ["/usr/local/bin/ruff"]
|
||||
EOF
|
||||
@@ -203,14 +202,14 @@ jobs:
|
||||
TAG_PATTERNS="${TAG_PATTERNS%\\n}"
|
||||
|
||||
# Export image cache name
|
||||
echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> "$GITHUB_ENV"
|
||||
echo "IMAGE_REF=${BASE_IMAGE//:/-}" >> $GITHUB_ENV
|
||||
|
||||
# Export tag patterns using the multiline env var syntax
|
||||
{
|
||||
echo "TAG_PATTERNS<<EOF"
|
||||
echo -e "${TAG_PATTERNS}"
|
||||
echo EOF
|
||||
} >> "$GITHUB_ENV"
|
||||
} >> $GITHUB_ENV
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
@@ -289,4 +288,4 @@ jobs:
|
||||
docker buildx imagetools create \
|
||||
"${annotations[@]}" \
|
||||
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf "${RUFF_BASE_IMG}@sha256:%s " *)
|
||||
$(printf '${{ env.RUFF_BASE_IMG }}@sha256:%s ' *)
|
||||
|
||||
28
.github/workflows/publish-docs.yml
vendored
28
.github/workflows/publish-docs.yml
vendored
@@ -49,11 +49,13 @@ jobs:
|
||||
|
||||
- name: "Set branch name"
|
||||
run: |
|
||||
version="${{ env.version }}"
|
||||
display_name="${{ env.display_name }}"
|
||||
timestamp="$(date +%s)"
|
||||
|
||||
# create branch_display_name from display_name by replacing all
|
||||
# characters disallowed in git branch names with hyphens
|
||||
branch_display_name="$(echo "${display_name}" | tr -c '[:alnum:]._' '-' | tr -s '-')"
|
||||
branch_display_name="$(echo "$display_name" | tr -c '[:alnum:]._' '-' | tr -s '-')"
|
||||
|
||||
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> $GITHUB_ENV
|
||||
echo "timestamp=$timestamp" >> $GITHUB_ENV
|
||||
@@ -91,7 +93,9 @@ jobs:
|
||||
run: mkdocs build --strict -f mkdocs.public.yml
|
||||
|
||||
- name: "Clone docs repo"
|
||||
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
run: |
|
||||
version="${{ env.version }}"
|
||||
git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs
|
||||
|
||||
- name: "Copy docs"
|
||||
run: rm -rf astral-docs/site/ruff && mkdir -p astral-docs/site && cp -r site/ruff astral-docs/site/
|
||||
@@ -99,10 +103,12 @@ jobs:
|
||||
- name: "Commit docs"
|
||||
working-directory: astral-docs
|
||||
run: |
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
git config user.name "astral-docs-bot"
|
||||
git config user.email "176161322+astral-docs-bot@users.noreply.github.com"
|
||||
|
||||
git checkout -b "${branch_name}"
|
||||
git checkout -b $branch_name
|
||||
git add site/ruff
|
||||
git commit -m "Update ruff documentation for $version"
|
||||
|
||||
@@ -111,8 +117,12 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
version="${{ env.version }}"
|
||||
display_name="${{ env.display_name }}"
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
# set the PR title
|
||||
pull_request_title="Update ruff documentation for "${display_name}""
|
||||
pull_request_title="Update ruff documentation for $display_name"
|
||||
|
||||
# Delete any existing pull requests that are open for this version
|
||||
# by checking against pull_request_title because the new PR will
|
||||
@@ -121,12 +131,12 @@ jobs:
|
||||
xargs -I {} gh pr close {}
|
||||
|
||||
# push the branch to GitHub
|
||||
git push origin "${branch_name}"
|
||||
git push origin $branch_name
|
||||
|
||||
# create the PR
|
||||
gh pr create --base main --head "${branch_name}" \
|
||||
gh pr create --base main --head $branch_name \
|
||||
--title "$pull_request_title" \
|
||||
--body "Automated documentation update for "${display_name}"" \
|
||||
--body "Automated documentation update for $display_name" \
|
||||
--label "documentation"
|
||||
|
||||
- name: "Merge Pull Request"
|
||||
@@ -135,7 +145,9 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ASTRAL_DOCS_PAT }}
|
||||
run: |
|
||||
branch_name="${{ env.branch_name }}"
|
||||
|
||||
# auto-merge the PR if the build was triggered by a release. Manual builds should be reviewed by a human.
|
||||
# give the PR a few seconds to be created before trying to auto-merge it
|
||||
sleep 10
|
||||
gh pr merge --squash "${branch_name}"
|
||||
gh pr merge --squash $branch_name
|
||||
|
||||
2
.github/workflows/sync_typeshed.yaml
vendored
2
.github/workflows/sync_typeshed.yaml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
with:
|
||||
repository: python/typeshed
|
||||
path: typeshed
|
||||
persist-credentials: false
|
||||
persist-credentials: true
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
@@ -95,6 +95,8 @@ repos:
|
||||
# `release.yml` is autogenerated by `dist`; security issues need to be fixed there
|
||||
# (https://opensource.axo.dev/cargo-dist/)
|
||||
exclude: .github/workflows/release.yml
|
||||
# We could consider enabling the low-severity warnings, but they're noisy
|
||||
args: [--min-severity=medium]
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.30.0
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,40 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- Fix fstring formatting removing overlong implicit concatenated string in expression part ([#14811](https://github.com/astral-sh/ruff/pull/14811))
|
||||
- \[`airflow`\] Add fix to remove deprecated keyword arguments (`AIR302`) ([#14887](https://github.com/astral-sh/ruff/pull/14887))
|
||||
- \[`airflow`\]: Extend rule to include deprecated names for Airflow 3.0 (`AIR302`) ([#14765](https://github.com/astral-sh/ruff/pull/14765) and [#14804](https://github.com/astral-sh/ruff/pull/14804))
|
||||
- \[`flake8-bugbear`\] Improve error messages for `except*` (`B025`, `B029`, `B030`, `B904`) ([#14815](https://github.com/astral-sh/ruff/pull/14815))
|
||||
- \[`flake8-bugbear`\] `itertools.batched()` without explicit `strict` (`B911`) ([#14408](https://github.com/astral-sh/ruff/pull/14408))
|
||||
- \[`flake8-use-pathlib`\] Dotless suffix passed to `Path.with_suffix()` (`PTH210`) ([#14779](https://github.com/astral-sh/ruff/pull/14779))
|
||||
- \[`pylint`\] Include parentheses and multiple comparators in check for `boolean-chained-comparison` (`PLR1716`) ([#14781](https://github.com/astral-sh/ruff/pull/14781))
|
||||
- \[`ruff`\] Do not simplify `round()` calls (`RUF046`) ([#14832](https://github.com/astral-sh/ruff/pull/14832))
|
||||
- \[`ruff`\] Don't emit `used-dummy-variable` on function parameters (`RUF052`) ([#14818](https://github.com/astral-sh/ruff/pull/14818))
|
||||
- \[`ruff`\] Implement `if-key-in-dict-del` (`RUF051`) ([#14553](https://github.com/astral-sh/ruff/pull/14553))
|
||||
- \[`ruff`\] Mark autofix for `RUF052` as always unsafe ([#14824](https://github.com/astral-sh/ruff/pull/14824))
|
||||
- \[`ruff`\] Teach autofix for `used-dummy-variable` about TypeVars etc. (`RUF052`) ([#14819](https://github.com/astral-sh/ruff/pull/14819))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-bugbear`\] Offer unsafe autofix for `no-explicit-stacklevel` (`B028`) ([#14829](https://github.com/astral-sh/ruff/pull/14829))
|
||||
- \[`flake8-pyi`\] Skip all type definitions in `string-or-bytes-too-long` (`PYI053`) ([#14797](https://github.com/astral-sh/ruff/pull/14797))
|
||||
- \[`pyupgrade`\] Do not report when a UTF-8 comment is followed by a non-UTF-8 one (`UP009`) ([#14728](https://github.com/astral-sh/ruff/pull/14728))
|
||||
- \[`pyupgrade`\] Mark fixes for `convert-typed-dict-functional-to-class` and `convert-named-tuple-functional-to-class` as unsafe if they will remove comments (`UP013`, `UP014`) ([#14842](https://github.com/astral-sh/ruff/pull/14842))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Raise syntax error for mixing `except` and `except*` ([#14895](https://github.com/astral-sh/ruff/pull/14895))
|
||||
- \[`flake8-bugbear`\] Fix `B028` to allow `stacklevel` to be explicitly assigned as a positional argument ([#14868](https://github.com/astral-sh/ruff/pull/14868))
|
||||
- \[`flake8-bugbear`\] Skip `B028` if `warnings.warn` is called with `*args` or `**kwargs` ([#14870](https://github.com/astral-sh/ruff/pull/14870))
|
||||
- \[`flake8-comprehensions`\] Skip iterables with named expressions in `unnecessary-map` (`C417`) ([#14827](https://github.com/astral-sh/ruff/pull/14827))
|
||||
- \[`flake8-pyi`\] Also remove `self` and `cls`'s annotation (`PYI034`) ([#14801](https://github.com/astral-sh/ruff/pull/14801))
|
||||
- \[`flake8-pytest-style`\] Fix `pytest-parametrize-names-wrong-type` (`PT006`) to edit both `argnames` and `argvalues` if both of them are single-element tuples/lists ([#14699](https://github.com/astral-sh/ruff/pull/14699))
|
||||
- \[`perflint`\] Improve autofix for `PERF401` ([#14369](https://github.com/astral-sh/ruff/pull/14369))
|
||||
- \[`pylint`\] Fix `PLW1508` false positive for default string created via a mult operation ([#14841](https://github.com/astral-sh/ruff/pull/14841))
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Preview features
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -2517,7 +2517,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2736,7 +2736,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -3051,7 +3051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
@@ -140,8 +140,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.8.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.8.3/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.8.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.8.2/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -174,7 +174,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.8.3
|
||||
rev: v0.8.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -34,7 +34,8 @@ If you define your own class named `Any`, using that in a type expression refers
|
||||
isn't a spelling of the Any type.
|
||||
|
||||
```py
|
||||
class Any: ...
|
||||
class Any:
|
||||
pass
|
||||
|
||||
x: Any
|
||||
|
||||
@@ -58,7 +59,8 @@ assignable to `int`.
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
class Subclass(Any): ...
|
||||
class Subclass(Any):
|
||||
pass
|
||||
|
||||
reveal_type(Subclass.__mro__) # revealed: tuple[Literal[Subclass], Any, Literal[object]]
|
||||
|
||||
@@ -66,18 +68,8 @@ x: Subclass = 1 # error: [invalid-assignment]
|
||||
# TODO: no diagnostic
|
||||
y: int = Subclass() # error: [invalid-assignment]
|
||||
|
||||
def _(s: Subclass):
|
||||
reveal_type(s) # revealed: Subclass
|
||||
```
|
||||
|
||||
## Invalid
|
||||
|
||||
`Any` cannot be parameterized:
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
# error: [invalid-type-parameter] "Type `typing.Any` expected no type parameter"
|
||||
def f(x: Any[int]):
|
||||
reveal_type(x) # revealed: Unknown
|
||||
def f() -> Subclass:
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: Subclass
|
||||
```
|
||||
|
||||
@@ -89,26 +89,28 @@ vice versa.
|
||||
```py
|
||||
from typing_extensions import Literal, LiteralString
|
||||
|
||||
def _(flag: bool):
|
||||
foo_1: Literal["foo"] = "foo"
|
||||
bar_1: LiteralString = foo_1 # fine
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
foo_2 = "foo" if flag else "bar"
|
||||
reveal_type(foo_2) # revealed: Literal["foo", "bar"]
|
||||
bar_2: LiteralString = foo_2 # fine
|
||||
foo_1: Literal["foo"] = "foo"
|
||||
bar_1: LiteralString = foo_1 # fine
|
||||
|
||||
foo_3: LiteralString = "foo" * 1_000_000_000
|
||||
bar_3: str = foo_2 # fine
|
||||
foo_2 = "foo" if coinflip() else "bar"
|
||||
reveal_type(foo_2) # revealed: Literal["foo", "bar"]
|
||||
bar_2: LiteralString = foo_2 # fine
|
||||
|
||||
baz_1: str = str()
|
||||
qux_1: LiteralString = baz_1 # error: [invalid-assignment]
|
||||
foo_3: LiteralString = "foo" * 1_000_000_000
|
||||
bar_3: str = foo_2 # fine
|
||||
|
||||
baz_2: LiteralString = "baz" * 1_000_000_000
|
||||
qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment]
|
||||
baz_1: str = str()
|
||||
qux_1: LiteralString = baz_1 # error: [invalid-assignment]
|
||||
|
||||
baz_3 = "foo" if flag else 1
|
||||
reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1]
|
||||
qux_3: LiteralString = baz_3 # error: [invalid-assignment]
|
||||
baz_2: LiteralString = "baz" * 1_000_000_000
|
||||
qux_2: Literal["qux"] = baz_2 # error: [invalid-assignment]
|
||||
|
||||
baz_3 = "foo" if coinflip() else 1
|
||||
reveal_type(baz_3) # revealed: Literal["foo"] | Literal[1]
|
||||
qux_3: LiteralString = baz_3 # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
### Narrowing
|
||||
|
||||
@@ -3,56 +3,75 @@
|
||||
## Simple
|
||||
|
||||
```py
|
||||
def f(v: "int"):
|
||||
reveal_type(v) # revealed: int
|
||||
def f() -> "int":
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Nested
|
||||
|
||||
```py
|
||||
def f(v: "'int'"):
|
||||
reveal_type(v) # revealed: int
|
||||
def f() -> "'int'":
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: int
|
||||
```
|
||||
|
||||
## Type expression
|
||||
|
||||
```py
|
||||
def f1(v: "int | str", w: "tuple[int, str]"):
|
||||
reveal_type(v) # revealed: int | str
|
||||
reveal_type(w) # revealed: tuple[int, str]
|
||||
def f1() -> "int | str":
|
||||
return 1
|
||||
|
||||
def f2() -> "tuple[int, str]":
|
||||
return 1
|
||||
|
||||
reveal_type(f1()) # revealed: int | str
|
||||
reveal_type(f2()) # revealed: tuple[int, str]
|
||||
```
|
||||
|
||||
## Partial
|
||||
|
||||
```py
|
||||
def f(v: tuple[int, "str"]):
|
||||
reveal_type(v) # revealed: tuple[int, str]
|
||||
def f() -> tuple[int, "str"]:
|
||||
return 1
|
||||
|
||||
reveal_type(f()) # revealed: tuple[int, str]
|
||||
```
|
||||
|
||||
## Deferred
|
||||
|
||||
```py
|
||||
def f(v: "Foo"):
|
||||
reveal_type(v) # revealed: Foo
|
||||
def f() -> "Foo":
|
||||
return Foo()
|
||||
|
||||
class Foo: ...
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: Foo
|
||||
```
|
||||
|
||||
## Deferred (undefined)
|
||||
|
||||
```py
|
||||
# error: [unresolved-reference]
|
||||
def f(v: "Foo"):
|
||||
reveal_type(v) # revealed: Unknown
|
||||
def f() -> "Foo":
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Partial deferred
|
||||
|
||||
```py
|
||||
def f(v: int | "Foo"):
|
||||
reveal_type(v) # revealed: int | Foo
|
||||
def f() -> int | "Foo":
|
||||
return 1
|
||||
|
||||
class Foo: ...
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f()) # revealed: int | Foo
|
||||
```
|
||||
|
||||
## `typing.Literal`
|
||||
@@ -60,43 +79,65 @@ class Foo: ...
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f1(v: Literal["Foo", "Bar"], w: 'Literal["Foo", "Bar"]'):
|
||||
reveal_type(v) # revealed: Literal["Foo", "Bar"]
|
||||
reveal_type(w) # revealed: Literal["Foo", "Bar"]
|
||||
def f1() -> Literal["Foo", "Bar"]:
|
||||
return "Foo"
|
||||
|
||||
class Foo: ...
|
||||
def f2() -> 'Literal["Foo", "Bar"]':
|
||||
return "Foo"
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
reveal_type(f1()) # revealed: Literal["Foo", "Bar"]
|
||||
reveal_type(f2()) # revealed: Literal["Foo", "Bar"]
|
||||
```
|
||||
|
||||
## Various string kinds
|
||||
|
||||
```py
|
||||
def f1(
|
||||
# error: [raw-string-type-annotation] "Type expressions cannot use raw string literal"
|
||||
a: r"int",
|
||||
# error: [fstring-type-annotation] "Type expressions cannot use f-strings"
|
||||
b: f"int",
|
||||
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
|
||||
c: b"int",
|
||||
d: "int",
|
||||
# error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals"
|
||||
e: "in" "t",
|
||||
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
|
||||
f: "\N{LATIN SMALL LETTER I}nt",
|
||||
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
|
||||
g: "\x69nt",
|
||||
h: """int""",
|
||||
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
|
||||
i: "b'int'",
|
||||
):
|
||||
reveal_type(a) # revealed: Unknown
|
||||
reveal_type(b) # revealed: Unknown
|
||||
reveal_type(c) # revealed: Unknown
|
||||
reveal_type(d) # revealed: int
|
||||
reveal_type(e) # revealed: Unknown
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
reveal_type(h) # revealed: int
|
||||
reveal_type(i) # revealed: Unknown
|
||||
# error: [raw-string-type-annotation] "Type expressions cannot use raw string literal"
|
||||
def f1() -> r"int":
|
||||
return 1
|
||||
|
||||
# error: [fstring-type-annotation] "Type expressions cannot use f-strings"
|
||||
def f2() -> f"int":
|
||||
return 1
|
||||
|
||||
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
|
||||
def f3() -> b"int":
|
||||
return 1
|
||||
|
||||
def f4() -> "int":
|
||||
return 1
|
||||
|
||||
# error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals"
|
||||
def f5() -> "in" "t":
|
||||
return 1
|
||||
|
||||
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
|
||||
def f6() -> "\N{LATIN SMALL LETTER I}nt":
|
||||
return 1
|
||||
|
||||
# error: [escape-character-in-forward-annotation] "Type expressions cannot contain escape characters"
|
||||
def f7() -> "\x69nt":
|
||||
return 1
|
||||
|
||||
def f8() -> """int""":
|
||||
return 1
|
||||
|
||||
# error: [byte-string-type-annotation] "Type expressions cannot use bytes literal"
|
||||
def f9() -> "b'int'":
|
||||
return 1
|
||||
|
||||
reveal_type(f1()) # revealed: Unknown
|
||||
reveal_type(f2()) # revealed: Unknown
|
||||
reveal_type(f3()) # revealed: Unknown
|
||||
reveal_type(f4()) # revealed: int
|
||||
reveal_type(f5()) # revealed: Unknown
|
||||
reveal_type(f6()) # revealed: Unknown
|
||||
reveal_type(f7()) # revealed: Unknown
|
||||
reveal_type(f8()) # revealed: int
|
||||
reveal_type(f9()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Various string kinds in `typing.Literal`
|
||||
@@ -104,8 +145,10 @@ def f1(
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]):
|
||||
reveal_type(v) # revealed: Literal["a", "b", "de", "f", "g", "h"] | Literal[b"c"]
|
||||
def f() -> Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]:
|
||||
return "normal"
|
||||
|
||||
reveal_type(f()) # revealed: Literal["a", "b", "de", "f", "g", "h"] | Literal[b"c"]
|
||||
```
|
||||
|
||||
## Class variables
|
||||
@@ -132,7 +175,8 @@ c: "Foo"
|
||||
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`"
|
||||
d: "Foo" = 1
|
||||
|
||||
class Foo: ...
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
c = Foo()
|
||||
|
||||
|
||||
@@ -78,10 +78,20 @@ c: tuple[str | int, str] = ([], "foo")
|
||||
## PEP-604 annotations are supported
|
||||
|
||||
```py
|
||||
def foo(v: str | int | None, w: str | str | None, x: str | str):
|
||||
reveal_type(v) # revealed: str | int | None
|
||||
reveal_type(w) # revealed: str | None
|
||||
reveal_type(x) # revealed: str
|
||||
def foo() -> str | int | None:
|
||||
return None
|
||||
|
||||
reveal_type(foo()) # revealed: str | int | None
|
||||
|
||||
def bar() -> str | str | None:
|
||||
return None
|
||||
|
||||
reveal_type(bar()) # revealed: str | None
|
||||
|
||||
def baz() -> str | str:
|
||||
return "Hello, world!"
|
||||
|
||||
reveal_type(baz()) # revealed: str
|
||||
```
|
||||
|
||||
## Attribute expressions in type annotations are understood
|
||||
@@ -108,7 +118,8 @@ from __future__ import annotations
|
||||
|
||||
x: Foo
|
||||
|
||||
class Foo: ...
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
@@ -119,7 +130,8 @@ reveal_type(x) # revealed: Foo
|
||||
```pyi path=main.pyi
|
||||
x: Foo
|
||||
|
||||
class Foo: ...
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
x = Foo()
|
||||
reveal_type(x) # revealed: Foo
|
||||
|
||||
@@ -49,116 +49,134 @@ reveal_type(x) # revealed: int
|
||||
## Method union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Foo:
|
||||
if flag:
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
else:
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
f = Foo()
|
||||
f += 12
|
||||
flag = bool_instance()
|
||||
|
||||
reveal_type(f) # revealed: str | int
|
||||
class Foo:
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
else:
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
|
||||
f = Foo()
|
||||
f += 12
|
||||
|
||||
reveal_type(f) # revealed: str | int
|
||||
```
|
||||
|
||||
## Partially bound `__iadd__`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Foo:
|
||||
if flag:
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
f = Foo()
|
||||
class Foo:
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
# TODO: We should emit an `unsupported-operator` error here, possibly with the information
|
||||
# that `Foo.__iadd__` may be unbound as additional context.
|
||||
f += "Hello, world!"
|
||||
f = Foo()
|
||||
|
||||
reveal_type(f) # revealed: int | Unknown
|
||||
# TODO: We should emit an `unsupported-operator` error here, possibly with the information
|
||||
# that `Foo.__iadd__` may be unbound as additional context.
|
||||
f += "Hello, world!"
|
||||
|
||||
reveal_type(f) # revealed: int | Unknown
|
||||
```
|
||||
|
||||
## Partially bound with `__add__`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Foo:
|
||||
def __add__(self, other: str) -> str:
|
||||
return "Hello, world!"
|
||||
if flag:
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
f = Foo()
|
||||
f += "Hello, world!"
|
||||
class Foo:
|
||||
def __add__(self, other: str) -> str:
|
||||
return "Hello, world!"
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: str) -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(f) # revealed: int | str
|
||||
f = Foo()
|
||||
f += "Hello, world!"
|
||||
|
||||
reveal_type(f) # revealed: int | str
|
||||
```
|
||||
|
||||
## Partially bound target union
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
class Foo:
|
||||
def __add__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
if flag1:
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag2:
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
class Foo:
|
||||
def __add__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(f) # revealed: int | str | float
|
||||
if bool_instance():
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
|
||||
reveal_type(f) # revealed: int | str | float
|
||||
```
|
||||
|
||||
## Target union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Foo:
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
flag = bool_instance()
|
||||
|
||||
reveal_type(f) # revealed: str | float
|
||||
class Foo:
|
||||
def __iadd__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
|
||||
if flag:
|
||||
f = Foo()
|
||||
else:
|
||||
f = 42.0
|
||||
f += 12
|
||||
|
||||
reveal_type(f) # revealed: str | float
|
||||
```
|
||||
|
||||
## Partially bound target union with `__add__`
|
||||
|
||||
```py
|
||||
def f(flag: bool, flag2: bool):
|
||||
class Foo:
|
||||
def __add__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
if flag:
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class Bar:
|
||||
def __add__(self, other: int) -> bytes:
|
||||
return b"Hello, world!"
|
||||
flag = bool_instance()
|
||||
|
||||
def __iadd__(self, other: int) -> float:
|
||||
return 42.0
|
||||
class Foo:
|
||||
def __add__(self, other: int) -> str:
|
||||
return "Hello, world!"
|
||||
if bool_instance():
|
||||
def __iadd__(self, other: int) -> int:
|
||||
return 42
|
||||
|
||||
if flag2:
|
||||
f = Foo()
|
||||
else:
|
||||
f = Bar()
|
||||
f += 12
|
||||
class Bar:
|
||||
def __add__(self, other: int) -> bytes:
|
||||
return b"Hello, world!"
|
||||
|
||||
reveal_type(f) # revealed: int | str | float
|
||||
def __iadd__(self, other: int) -> float:
|
||||
return 42.0
|
||||
|
||||
if flag:
|
||||
f = Foo()
|
||||
else:
|
||||
f = Bar()
|
||||
f += 12
|
||||
|
||||
reveal_type(f) # revealed: int | str | float
|
||||
```
|
||||
|
||||
@@ -3,23 +3,27 @@
|
||||
## Union of attributes
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
else:
|
||||
class C1:
|
||||
x = 2
|
||||
|
||||
class C2:
|
||||
if flag:
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
x = 3
|
||||
else:
|
||||
class C1:
|
||||
x = 2
|
||||
x = 4
|
||||
|
||||
class C2:
|
||||
if flag:
|
||||
x = 3
|
||||
else:
|
||||
x = 4
|
||||
|
||||
reveal_type(C1.x) # revealed: Literal[1, 2]
|
||||
reveal_type(C2.x) # revealed: Literal[3, 4]
|
||||
reveal_type(C1.x) # revealed: Literal[1, 2]
|
||||
reveal_type(C2.x) # revealed: Literal[3, 4]
|
||||
```
|
||||
|
||||
## Inherited attributes
|
||||
@@ -64,19 +68,24 @@ reveal_type(A.X) # revealed: Literal[42]
|
||||
In this example, the `x` attribute is not defined in the `C2` element of the union:
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
class C1:
|
||||
x = 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class C2: ...
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
class C3:
|
||||
x = 3
|
||||
class C2: ...
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
class C3:
|
||||
x = 3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 3]
|
||||
flag1 = bool_instance()
|
||||
flag2 = bool_instance()
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 3]
|
||||
```
|
||||
|
||||
### Possibly-unbound within a class
|
||||
@@ -85,21 +94,26 @@ We raise the same diagnostic if the attribute is possibly-unbound in at least on
|
||||
union:
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag1: bool, flag2: bool):
|
||||
class C1:
|
||||
x = 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class C2:
|
||||
if flag:
|
||||
x = 2
|
||||
class C1:
|
||||
x = 1
|
||||
|
||||
class C3:
|
||||
x = 3
|
||||
class C2:
|
||||
if bool_instance():
|
||||
x = 2
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
class C3:
|
||||
x = 3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 2, 3]
|
||||
flag1 = bool_instance()
|
||||
flag2 = bool_instance()
|
||||
|
||||
C = C1 if flag1 else C2 if flag2 else C3
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `x` on type `Literal[C1, C2, C3]` is possibly unbound"
|
||||
reveal_type(C.x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
## Unions with all paths unbound
|
||||
@@ -107,51 +121,16 @@ def _(flag: bool, flag1: bool, flag2: bool):
|
||||
If the symbol is unbound in all elements of the union, we detect that:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class C1: ...
|
||||
class C2: ...
|
||||
C = C1 if flag else C2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Objects of all types have a `__class__` method
|
||||
|
||||
```py
|
||||
import typing_extensions
|
||||
|
||||
reveal_type(typing_extensions.__class__) # revealed: Literal[ModuleType]
|
||||
|
||||
a = 42
|
||||
reveal_type(a.__class__) # revealed: Literal[int]
|
||||
|
||||
b = "42"
|
||||
reveal_type(b.__class__) # revealed: Literal[str]
|
||||
|
||||
c = b"42"
|
||||
reveal_type(c.__class__) # revealed: Literal[bytes]
|
||||
|
||||
d = True
|
||||
reveal_type(d.__class__) # revealed: Literal[bool]
|
||||
|
||||
e = (42, 42)
|
||||
reveal_type(e.__class__) # revealed: Literal[tuple]
|
||||
|
||||
def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
||||
reveal_type(a.__class__) # revealed: type[int]
|
||||
reveal_type(b.__class__) # revealed: Literal[str]
|
||||
reveal_type(c.__class__) # revealed: type[int] | type[str]
|
||||
|
||||
# `type[type]`, a.k.a., either the class `type` or some subclass of `type`.
|
||||
# It would be incorrect to infer `Literal[type]` here,
|
||||
# as `c` could be some subclass of `str` with a custom metaclass.
|
||||
# All we know is that the metaclass must be a (non-strict) subclass of `type`.
|
||||
reveal_type(d.__class__) # revealed: type[type]
|
||||
|
||||
reveal_type(f.__class__) # revealed: Literal[FunctionType]
|
||||
|
||||
class Foo: ...
|
||||
|
||||
reveal_type(Foo.__class__) # revealed: Literal[type]
|
||||
class C1: ...
|
||||
class C2: ...
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
C = C1 if flag else C2
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[C1, C2]` has no attribute `x`"
|
||||
reveal_type(C.x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -281,12 +281,20 @@ reveal_type(42 + 4.2) # revealed: int
|
||||
# TODO should be complex, need to check arg type and fall back to `rhs.__radd__`
|
||||
reveal_type(3 + 3j) # revealed: int
|
||||
|
||||
def _(x: bool, y: int):
|
||||
reveal_type(x + y) # revealed: int
|
||||
reveal_type(4.2 + x) # revealed: float
|
||||
def returns_int() -> int:
|
||||
return 42
|
||||
|
||||
# TODO should be float, need to check arg type and fall back to `rhs.__radd__`
|
||||
reveal_type(y + 4.12) # revealed: int
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
x = returns_bool()
|
||||
y = returns_int()
|
||||
|
||||
reveal_type(x + y) # revealed: int
|
||||
reveal_type(4.2 + x) # revealed: float
|
||||
|
||||
# TODO should be float, need to check arg type and fall back to `rhs.__radd__`
|
||||
reveal_type(y + 4.12) # revealed: int
|
||||
```
|
||||
|
||||
## With literal types
|
||||
|
||||
@@ -7,25 +7,29 @@ Similarly, in `and` expressions, if the left-hand side is falsy, the right-hand
|
||||
evaluated.
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag or (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag and (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
if bool_instance() or (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if bool_instance() and (x := 1):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## First expression is always evaluated
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if (x := 1) or flag:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if (x := 1) and flag:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
if (x := 1) or bool_instance():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if (x := 1) and bool_instance():
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Statically known truthiness
|
||||
@@ -45,26 +49,30 @@ if True and (x := 1):
|
||||
## Later expressions can always use variables from earlier expressions
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
flag or (x := 1) or reveal_type(x) # revealed: Literal[1]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# error: [unresolved-reference]
|
||||
flag or reveal_type(y) or (y := 1) # revealed: Unknown
|
||||
bool_instance() or (x := 1) or reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
# error: [unresolved-reference]
|
||||
bool_instance() or reveal_type(y) or (y := 1) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Nested expressions
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
if flag1 or ((x := 1) and flag2):
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if ((y := 1) and flag1) or flag2:
|
||||
reveal_type(y) # revealed: Literal[1]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if bool_instance() or ((x := 1) and bool_instance()):
|
||||
# error: [possibly-unresolved-reference]
|
||||
if (flag1 and (z := 1)) or reveal_type(z): # revealed: Literal[1]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(z) # revealed: Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if ((y := 1) and bool_instance()) or bool_instance():
|
||||
reveal_type(y) # revealed: Literal[1]
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
if (bool_instance() and (z := 1)) or reveal_type(z): # revealed: Literal[1]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(z) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -22,27 +22,29 @@ reveal_type(b) # revealed: Unknown
|
||||
## Possibly unbound `__call__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class PossiblyNotCallable:
|
||||
if flag:
|
||||
def __call__(self) -> int: ...
|
||||
def flag() -> bool: ...
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
reveal_type(result) # revealed: int
|
||||
class PossiblyNotCallable:
|
||||
if flag():
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly unbound callable
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int: ...
|
||||
def flag() -> bool: ...
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
a = PossiblyUnbound()
|
||||
reveal_type(a()) # revealed: int
|
||||
if flag():
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
a = PossiblyUnbound()
|
||||
reveal_type(a()) # revealed: int
|
||||
```
|
||||
|
||||
## Non-callable `__call__`
|
||||
@@ -59,14 +61,15 @@ reveal_type(a()) # revealed: Unknown
|
||||
## Possibly non-callable `__call__`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class NonCallable:
|
||||
if flag:
|
||||
__call__ = 1
|
||||
else:
|
||||
def __call__(self) -> int: ...
|
||||
def flag() -> bool: ...
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
class NonCallable:
|
||||
if flag():
|
||||
__call__ = 1
|
||||
else:
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -57,10 +57,12 @@ x = nonsense() # error: "Object of type `Literal[123]` is not callable"
|
||||
## Potentially unbound function
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
def foo() -> int:
|
||||
return 42
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag():
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -3,14 +3,22 @@
|
||||
## Union of return types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
def f() -> int:
|
||||
return 1
|
||||
else:
|
||||
def f() -> str:
|
||||
return "foo"
|
||||
reveal_type(f()) # revealed: int | str
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
else:
|
||||
|
||||
def f() -> str:
|
||||
return "foo"
|
||||
|
||||
reveal_type(f()) # revealed: int | str
|
||||
```
|
||||
|
||||
## Calling with an unknown union
|
||||
@@ -18,10 +26,13 @@ def _(flag: bool):
|
||||
```py
|
||||
from nonexistent import f # error: [unresolved-import] "Cannot resolve import `nonexistent`"
|
||||
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
@@ -33,14 +44,20 @@ reveal_type(f()) # revealed: Unknown | int
|
||||
Calling a union with a non-callable element should emit a diagnostic.
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = 1
|
||||
else:
|
||||
def f() -> int:
|
||||
return 1
|
||||
x = f() # error: "Object of type `Literal[1] | Literal[f]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(x) # revealed: Unknown | int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
f = 1
|
||||
else:
|
||||
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
x = f() # error: "Object of type `Literal[1] | Literal[f]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(x) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
## Multiple non-callable elements in a union
|
||||
@@ -48,17 +65,23 @@ def _(flag: bool):
|
||||
Calling a union with multiple non-callable elements should mention all of them in the diagnostic.
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
if flag:
|
||||
f = 1
|
||||
elif flag2:
|
||||
f = "foo"
|
||||
else:
|
||||
def f() -> int:
|
||||
return 1
|
||||
# error: "Object of type `Literal[1] | Literal["foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])"
|
||||
# revealed: Unknown | int
|
||||
reveal_type(f())
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
|
||||
if flag:
|
||||
f = 1
|
||||
elif flag2:
|
||||
f = "foo"
|
||||
else:
|
||||
|
||||
def f() -> int:
|
||||
return 1
|
||||
|
||||
# error: "Object of type `Literal[1] | Literal["foo"] | Literal[f]` is not callable (due to union elements Literal[1], Literal["foo"])"
|
||||
# revealed: Unknown | int
|
||||
reveal_type(f())
|
||||
```
|
||||
|
||||
## All non-callable union elements
|
||||
@@ -66,12 +89,16 @@ def _(flag: bool, flag2: bool):
|
||||
Calling a union with no callable elements can emit a simpler diagnostic.
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
f = 1
|
||||
else:
|
||||
f = "foo"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = f() # error: "Object of type `Literal[1] | Literal["foo"]` is not callable"
|
||||
reveal_type(x) # revealed: Unknown
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
f = 1
|
||||
else:
|
||||
f = "foo"
|
||||
|
||||
x = f() # error: "Object of type `Literal[1] | Literal["foo"]` is not callable"
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -3,31 +3,38 @@
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def _(a1: A, a2: A, o: object):
|
||||
n1 = None
|
||||
n2 = None
|
||||
def get_a() -> A: ...
|
||||
def get_object() -> object: ...
|
||||
|
||||
reveal_type(a1 is a1) # revealed: bool
|
||||
reveal_type(a1 is a2) # revealed: bool
|
||||
a1 = get_a()
|
||||
a2 = get_a()
|
||||
|
||||
reveal_type(n1 is n1) # revealed: Literal[True]
|
||||
reveal_type(n1 is n2) # revealed: Literal[True]
|
||||
n1 = None
|
||||
n2 = None
|
||||
|
||||
reveal_type(a1 is n1) # revealed: Literal[False]
|
||||
reveal_type(n1 is a1) # revealed: Literal[False]
|
||||
o = get_object()
|
||||
|
||||
reveal_type(a1 is o) # revealed: bool
|
||||
reveal_type(n1 is o) # revealed: bool
|
||||
reveal_type(a1 is a1) # revealed: bool
|
||||
reveal_type(a1 is a2) # revealed: bool
|
||||
|
||||
reveal_type(a1 is not a1) # revealed: bool
|
||||
reveal_type(a1 is not a2) # revealed: bool
|
||||
reveal_type(n1 is n1) # revealed: Literal[True]
|
||||
reveal_type(n1 is n2) # revealed: Literal[True]
|
||||
|
||||
reveal_type(n1 is not n1) # revealed: Literal[False]
|
||||
reveal_type(n1 is not n2) # revealed: Literal[False]
|
||||
reveal_type(a1 is n1) # revealed: Literal[False]
|
||||
reveal_type(n1 is a1) # revealed: Literal[False]
|
||||
|
||||
reveal_type(a1 is not n1) # revealed: Literal[True]
|
||||
reveal_type(n1 is not a1) # revealed: Literal[True]
|
||||
reveal_type(a1 is o) # revealed: bool
|
||||
reveal_type(n1 is o) # revealed: bool
|
||||
|
||||
reveal_type(a1 is not o) # revealed: bool
|
||||
reveal_type(n1 is not o) # revealed: bool
|
||||
reveal_type(a1 is not a1) # revealed: bool
|
||||
reveal_type(a1 is not a2) # revealed: bool
|
||||
|
||||
reveal_type(n1 is not n1) # revealed: Literal[False]
|
||||
reveal_type(n1 is not n2) # revealed: Literal[False]
|
||||
|
||||
reveal_type(a1 is not n1) # revealed: Literal[True]
|
||||
reveal_type(n1 is not a1) # revealed: Literal[True]
|
||||
|
||||
reveal_type(a1 is not o) # revealed: bool
|
||||
reveal_type(n1 is not o) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -312,9 +312,17 @@ reveal_type(1 <= 2j) # revealed: bool
|
||||
reveal_type(1 > 2j) # revealed: bool
|
||||
reveal_type(1 >= 2j) # revealed: bool
|
||||
|
||||
def f(x: bool, y: int):
|
||||
reveal_type(x < y) # revealed: bool
|
||||
reveal_type(y < x) # revealed: bool
|
||||
reveal_type(4.2 < x) # revealed: bool
|
||||
reveal_type(x < 4.2) # revealed: bool
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = bool_instance()
|
||||
y = int_instance()
|
||||
|
||||
reveal_type(x < y) # revealed: bool
|
||||
reveal_type(y < x) # revealed: bool
|
||||
reveal_type(4.2 < x) # revealed: bool
|
||||
reveal_type(x < 4.2) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -20,8 +20,10 @@ reveal_type(1 <= "" and 0 < 1) # revealed: bool
|
||||
|
||||
```py
|
||||
# TODO: implement lookup of `__eq__` on typeshed `int` stub.
|
||||
def _(a: int, b: int):
|
||||
reveal_type(1 == a) # revealed: bool
|
||||
reveal_type(9 < a) # revealed: bool
|
||||
reveal_type(a < b) # revealed: bool
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(1 == int_instance()) # revealed: bool
|
||||
reveal_type(9 < int_instance()) # revealed: bool
|
||||
reveal_type(int_instance() < int_instance()) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -14,19 +14,21 @@ class Child1(Base):
|
||||
|
||||
class Child2(Base): ...
|
||||
|
||||
def _(x: Base):
|
||||
c1 = Child1()
|
||||
def get_base() -> Base: ...
|
||||
|
||||
# Create an intersection type through narrowing:
|
||||
if isinstance(x, Child1):
|
||||
if isinstance(x, Child2):
|
||||
reveal_type(x) # revealed: Child1 & Child2
|
||||
x = get_base()
|
||||
c1 = Child1()
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[True]
|
||||
# Create an intersection type through narrowing:
|
||||
if isinstance(x, Child1):
|
||||
if isinstance(x, Child2):
|
||||
reveal_type(x) # revealed: Child1 & Child2
|
||||
|
||||
# Other comparison operators fall back to the base type:
|
||||
reveal_type(x > 1) # revealed: bool
|
||||
reveal_type(x is c1) # revealed: bool
|
||||
reveal_type(x == 1) # revealed: Literal[True]
|
||||
|
||||
# Other comparison operators fall back to the base type:
|
||||
reveal_type(x > 1) # revealed: bool
|
||||
reveal_type(x is c1) # revealed: bool
|
||||
```
|
||||
|
||||
## Negative contributions
|
||||
@@ -71,15 +73,18 @@ if x != "abc":
|
||||
#### Integers
|
||||
|
||||
```py
|
||||
def _(x: int):
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
def get_int() -> int: ...
|
||||
|
||||
reveal_type(x != 1) # revealed: Literal[True]
|
||||
reveal_type(x != 2) # revealed: bool
|
||||
x = get_int()
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[False]
|
||||
reveal_type(x == 2) # revealed: bool
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
|
||||
reveal_type(x != 1) # revealed: Literal[True]
|
||||
reveal_type(x != 2) # revealed: bool
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[False]
|
||||
reveal_type(x == 2) # revealed: bool
|
||||
```
|
||||
|
||||
### Identity comparisons
|
||||
@@ -87,15 +92,18 @@ def _(x: int):
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def _(o: object):
|
||||
a = A()
|
||||
n = None
|
||||
def get_object() -> object: ...
|
||||
|
||||
if o is not None:
|
||||
reveal_type(o) # revealed: object & ~None
|
||||
o = object()
|
||||
|
||||
reveal_type(o is n) # revealed: Literal[False]
|
||||
reveal_type(o is not n) # revealed: Literal[True]
|
||||
a = A()
|
||||
n = None
|
||||
|
||||
if o is not None:
|
||||
reveal_type(o) # revealed: object & ~None
|
||||
|
||||
reveal_type(o is n) # revealed: Literal[False]
|
||||
reveal_type(o is not n) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Diagnostics
|
||||
@@ -111,13 +119,16 @@ class Container:
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
def get_object() -> object: ...
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
### Unsupported operators for negative contributions
|
||||
@@ -131,11 +142,14 @@ class Container:
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def _(x: object):
|
||||
if isinstance(x, Container):
|
||||
if not isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & ~NonContainer
|
||||
def get_object() -> object: ...
|
||||
|
||||
# No error here!
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if not isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & ~NonContainer
|
||||
|
||||
# No error here!
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -3,17 +3,18 @@
|
||||
## String literals
|
||||
|
||||
```py
|
||||
def _(x: str):
|
||||
reveal_type("abc" == "abc") # revealed: Literal[True]
|
||||
reveal_type("ab_cd" <= "ab_ce") # revealed: Literal[True]
|
||||
reveal_type("abc" in "ab cd") # revealed: Literal[False]
|
||||
reveal_type("" not in "hello") # revealed: Literal[False]
|
||||
reveal_type("--" is "--") # revealed: bool
|
||||
reveal_type("A" is "B") # revealed: Literal[False]
|
||||
reveal_type("--" is not "--") # revealed: bool
|
||||
reveal_type("A" is not "B") # revealed: Literal[True]
|
||||
reveal_type(x < "...") # revealed: bool
|
||||
def str_instance() -> str: ...
|
||||
|
||||
# ensure we're not comparing the interned salsa symbols, which compare by order of declaration.
|
||||
reveal_type("ab" < "ab_cd") # revealed: Literal[True]
|
||||
reveal_type("abc" == "abc") # revealed: Literal[True]
|
||||
reveal_type("ab_cd" <= "ab_ce") # revealed: Literal[True]
|
||||
reveal_type("abc" in "ab cd") # revealed: Literal[False]
|
||||
reveal_type("" not in "hello") # revealed: Literal[False]
|
||||
reveal_type("--" is "--") # revealed: bool
|
||||
reveal_type("A" is "B") # revealed: Literal[False]
|
||||
reveal_type("--" is not "--") # revealed: bool
|
||||
reveal_type("A" is not "B") # revealed: Literal[True]
|
||||
reveal_type(str_instance() < "...") # revealed: bool
|
||||
|
||||
# ensure we're not comparing the interned salsa symbols, which compare by order of declaration.
|
||||
reveal_type("ab" < "ab_cd") # revealed: Literal[True]
|
||||
```
|
||||
|
||||
@@ -58,23 +58,28 @@ reveal_type(c >= d) # revealed: Literal[True]
|
||||
#### Results with Ambiguity
|
||||
|
||||
```py
|
||||
def _(x: bool, y: int):
|
||||
a = (x,)
|
||||
b = (y,)
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(a == b) # revealed: bool
|
||||
reveal_type(a != b) # revealed: bool
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
a = (bool_instance(),)
|
||||
b = (int_instance(),)
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
|
||||
reveal_type(a == b) # revealed: bool
|
||||
reveal_type(a != b) # revealed: bool
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
```
|
||||
|
||||
#### Comparison Unsupported
|
||||
@@ -192,7 +197,7 @@ reveal_type((A(), B()) < (A(), B())) # revealed: float | set | Literal[False]
|
||||
|
||||
#### Special Handling of Eq and NotEq in Lexicographic Comparisons
|
||||
|
||||
> Example: `(<int instance>, "foo") == (<int instance>, "bar")`
|
||||
> Example: `(int_instance(), "foo") == (int_instance(), "bar")`
|
||||
|
||||
`Eq` and `NotEq` have unique behavior compared to other operators in lexicographic comparisons.
|
||||
Specifically, for `Eq`, if any non-equal pair exists within the tuples being compared, we can
|
||||
@@ -203,38 +208,42 @@ In contrast, with operators like `<` and `>`, the comparison must consider each
|
||||
sequentially, and the final outcome might remain ambiguous until all pairs are compared.
|
||||
|
||||
```py
|
||||
def _(x: str, y: int):
|
||||
reveal_type("foo" == "bar") # revealed: Literal[False]
|
||||
reveal_type(("foo",) == ("bar",)) # revealed: Literal[False]
|
||||
reveal_type((4, "foo") == (4, "bar")) # revealed: Literal[False]
|
||||
reveal_type((y, "foo") == (y, "bar")) # revealed: Literal[False]
|
||||
def str_instance() -> str:
|
||||
return "hello"
|
||||
|
||||
a = (x, y, "foo")
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
reveal_type("foo" == "bar") # revealed: Literal[False]
|
||||
reveal_type(("foo",) == ("bar",)) # revealed: Literal[False]
|
||||
reveal_type((4, "foo") == (4, "bar")) # revealed: Literal[False]
|
||||
reveal_type((int_instance(), "foo") == (int_instance(), "bar")) # revealed: Literal[False]
|
||||
|
||||
b = (x, y, "bar")
|
||||
a = (str_instance(), int_instance(), "foo")
|
||||
|
||||
reveal_type(a == b) # revealed: Literal[False]
|
||||
reveal_type(a != b) # revealed: Literal[True]
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
reveal_type(a == a) # revealed: bool
|
||||
reveal_type(a != a) # revealed: bool
|
||||
reveal_type(a < a) # revealed: bool
|
||||
reveal_type(a <= a) # revealed: bool
|
||||
reveal_type(a > a) # revealed: bool
|
||||
reveal_type(a >= a) # revealed: bool
|
||||
|
||||
c = (x, y, "foo", "different_length")
|
||||
b = (str_instance(), int_instance(), "bar")
|
||||
|
||||
reveal_type(a == c) # revealed: Literal[False]
|
||||
reveal_type(a != c) # revealed: Literal[True]
|
||||
reveal_type(a < c) # revealed: bool
|
||||
reveal_type(a <= c) # revealed: bool
|
||||
reveal_type(a > c) # revealed: bool
|
||||
reveal_type(a >= c) # revealed: bool
|
||||
reveal_type(a == b) # revealed: Literal[False]
|
||||
reveal_type(a != b) # revealed: Literal[True]
|
||||
reveal_type(a < b) # revealed: bool
|
||||
reveal_type(a <= b) # revealed: bool
|
||||
reveal_type(a > b) # revealed: bool
|
||||
reveal_type(a >= b) # revealed: bool
|
||||
|
||||
c = (str_instance(), int_instance(), "foo", "different_length")
|
||||
reveal_type(a == c) # revealed: Literal[False]
|
||||
reveal_type(a != c) # revealed: Literal[True]
|
||||
reveal_type(a < c) # revealed: bool
|
||||
reveal_type(a <= c) # revealed: bool
|
||||
reveal_type(a > c) # revealed: bool
|
||||
reveal_type(a >= c) # revealed: bool
|
||||
```
|
||||
|
||||
#### Error Propagation
|
||||
@@ -243,36 +252,42 @@ Errors occurring within a tuple comparison should propagate outward. However, if
|
||||
comparison can clearly conclude before encountering an error, the error should not be raised.
|
||||
|
||||
```py
|
||||
def _(n: int, s: str):
|
||||
class A: ...
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`"
|
||||
A() < A()
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`"
|
||||
A() <= A()
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`"
|
||||
A() > A()
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`"
|
||||
A() >= A()
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
a = (0, n, A())
|
||||
def str_instance() -> str:
|
||||
return "hello"
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a < a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a <= a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a > a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a >= a) # revealed: Unknown
|
||||
class A: ...
|
||||
|
||||
# Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`,
|
||||
# and should terminate immediately.
|
||||
b = (99999, n, A())
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`"
|
||||
A() < A()
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`"
|
||||
A() <= A()
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`"
|
||||
A() > A()
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`"
|
||||
A() >= A()
|
||||
|
||||
reveal_type(a < b) # revealed: Literal[True]
|
||||
reveal_type(a <= b) # revealed: Literal[True]
|
||||
reveal_type(a > b) # revealed: Literal[False]
|
||||
reveal_type(a >= b) # revealed: Literal[False]
|
||||
a = (0, int_instance(), A())
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a < a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `<=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a <= a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a > a) # revealed: Unknown
|
||||
# error: [unsupported-operator] "Operator `>=` is not supported for types `A` and `A`, in comparing `tuple[Literal[0], int, A]` with `tuple[Literal[0], int, A]`"
|
||||
reveal_type(a >= a) # revealed: Unknown
|
||||
|
||||
# Comparison between `a` and `b` should only involve the first elements, `Literal[0]` and `Literal[99999]`,
|
||||
# and should terminate immediately.
|
||||
b = (99999, int_instance(), A())
|
||||
|
||||
reveal_type(a < b) # revealed: Literal[True]
|
||||
reveal_type(a <= b) # revealed: Literal[True]
|
||||
reveal_type(a > b) # revealed: Literal[False]
|
||||
reveal_type(a >= b) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
### Membership Test Comparisons
|
||||
@@ -280,20 +295,22 @@ def _(n: int, s: str):
|
||||
"Membership Test Comparisons" refers to the operators `in` and `not in`.
|
||||
|
||||
```py
|
||||
def _(n: int):
|
||||
a = (1, 2)
|
||||
b = ((3, 4), (1, 2))
|
||||
c = ((1, 2, 3), (4, 5, 6))
|
||||
d = ((n, n), (n, n))
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
reveal_type(a in b) # revealed: Literal[True]
|
||||
reveal_type(a not in b) # revealed: Literal[False]
|
||||
a = (1, 2)
|
||||
b = ((3, 4), (1, 2))
|
||||
c = ((1, 2, 3), (4, 5, 6))
|
||||
d = ((int_instance(), int_instance()), (int_instance(), int_instance()))
|
||||
|
||||
reveal_type(a in c) # revealed: Literal[False]
|
||||
reveal_type(a not in c) # revealed: Literal[True]
|
||||
reveal_type(a in b) # revealed: Literal[True]
|
||||
reveal_type(a not in b) # revealed: Literal[False]
|
||||
|
||||
reveal_type(a in d) # revealed: bool
|
||||
reveal_type(a not in d) # revealed: bool
|
||||
reveal_type(a in c) # revealed: Literal[False]
|
||||
reveal_type(a not in c) # revealed: Literal[True]
|
||||
|
||||
reveal_type(a in d) # revealed: bool
|
||||
reveal_type(a not in d) # revealed: bool
|
||||
```
|
||||
|
||||
### Identity Comparisons
|
||||
|
||||
@@ -5,46 +5,49 @@
|
||||
Comparisons on union types need to consider all possible cases:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
one_or_two = 1 if flag else 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(one_or_two <= 2) # revealed: Literal[True]
|
||||
reveal_type(one_or_two <= 1) # revealed: bool
|
||||
reveal_type(one_or_two <= 0) # revealed: Literal[False]
|
||||
flag = bool_instance()
|
||||
one_or_two = 1 if flag else 2
|
||||
|
||||
reveal_type(2 >= one_or_two) # revealed: Literal[True]
|
||||
reveal_type(1 >= one_or_two) # revealed: bool
|
||||
reveal_type(0 >= one_or_two) # revealed: Literal[False]
|
||||
reveal_type(one_or_two <= 2) # revealed: Literal[True]
|
||||
reveal_type(one_or_two <= 1) # revealed: bool
|
||||
reveal_type(one_or_two <= 0) # revealed: Literal[False]
|
||||
|
||||
reveal_type(one_or_two < 1) # revealed: Literal[False]
|
||||
reveal_type(one_or_two < 2) # revealed: bool
|
||||
reveal_type(one_or_two < 3) # revealed: Literal[True]
|
||||
reveal_type(2 >= one_or_two) # revealed: Literal[True]
|
||||
reveal_type(1 >= one_or_two) # revealed: bool
|
||||
reveal_type(0 >= one_or_two) # revealed: Literal[False]
|
||||
|
||||
reveal_type(one_or_two > 0) # revealed: Literal[True]
|
||||
reveal_type(one_or_two > 1) # revealed: bool
|
||||
reveal_type(one_or_two > 2) # revealed: Literal[False]
|
||||
reveal_type(one_or_two < 1) # revealed: Literal[False]
|
||||
reveal_type(one_or_two < 2) # revealed: bool
|
||||
reveal_type(one_or_two < 3) # revealed: Literal[True]
|
||||
|
||||
reveal_type(one_or_two == 3) # revealed: Literal[False]
|
||||
reveal_type(one_or_two == 1) # revealed: bool
|
||||
reveal_type(one_or_two > 0) # revealed: Literal[True]
|
||||
reveal_type(one_or_two > 1) # revealed: bool
|
||||
reveal_type(one_or_two > 2) # revealed: Literal[False]
|
||||
|
||||
reveal_type(one_or_two != 3) # revealed: Literal[True]
|
||||
reveal_type(one_or_two != 1) # revealed: bool
|
||||
reveal_type(one_or_two == 3) # revealed: Literal[False]
|
||||
reveal_type(one_or_two == 1) # revealed: bool
|
||||
|
||||
a_or_ab = "a" if flag else "ab"
|
||||
reveal_type(one_or_two != 3) # revealed: Literal[True]
|
||||
reveal_type(one_or_two != 1) # revealed: bool
|
||||
|
||||
reveal_type(a_or_ab in "ab") # revealed: Literal[True]
|
||||
reveal_type("a" in a_or_ab) # revealed: Literal[True]
|
||||
a_or_ab = "a" if flag else "ab"
|
||||
|
||||
reveal_type("c" not in a_or_ab) # revealed: Literal[True]
|
||||
reveal_type("a" not in a_or_ab) # revealed: Literal[False]
|
||||
reveal_type(a_or_ab in "ab") # revealed: Literal[True]
|
||||
reveal_type("a" in a_or_ab) # revealed: Literal[True]
|
||||
|
||||
reveal_type("b" in a_or_ab) # revealed: bool
|
||||
reveal_type("b" not in a_or_ab) # revealed: bool
|
||||
reveal_type("c" not in a_or_ab) # revealed: Literal[True]
|
||||
reveal_type("a" not in a_or_ab) # revealed: Literal[False]
|
||||
|
||||
one_or_none = 1 if flag else None
|
||||
reveal_type("b" in a_or_ab) # revealed: bool
|
||||
reveal_type("b" not in a_or_ab) # revealed: bool
|
||||
|
||||
reveal_type(one_or_none is None) # revealed: bool
|
||||
reveal_type(one_or_none is not None) # revealed: bool
|
||||
one_or_none = 1 if flag else None
|
||||
|
||||
reveal_type(one_or_none is None) # revealed: bool
|
||||
reveal_type(one_or_none is not None) # revealed: bool
|
||||
```
|
||||
|
||||
## Union on both sides of the comparison
|
||||
@@ -53,15 +56,18 @@ With unions on both sides, we need to consider the full cross product of options
|
||||
resulting (union) type:
|
||||
|
||||
```py
|
||||
def _(flag_s: bool, flag_l: bool):
|
||||
small = 1 if flag_s else 2
|
||||
large = 2 if flag_l else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(small <= large) # revealed: Literal[True]
|
||||
reveal_type(small >= large) # revealed: bool
|
||||
flag_s, flag_l = bool_instance(), bool_instance()
|
||||
small = 1 if flag_s else 2
|
||||
large = 2 if flag_l else 3
|
||||
|
||||
reveal_type(small < large) # revealed: bool
|
||||
reveal_type(small > large) # revealed: Literal[False]
|
||||
reveal_type(small <= large) # revealed: Literal[True]
|
||||
reveal_type(small >= large) # revealed: bool
|
||||
|
||||
reveal_type(small < large) # revealed: bool
|
||||
reveal_type(small > large) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## Unsupported operations
|
||||
@@ -71,9 +77,12 @@ back to `bool` for the result type instead of trying to infer something more pre
|
||||
(supported) variants:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = [1, 2] if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
result = 1 in x # error: "Operator `in` is not supported"
|
||||
reveal_type(result) # revealed: bool
|
||||
flag = bool_instance()
|
||||
x = [1, 2] if flag else 1
|
||||
|
||||
result = 1 in x # error: "Operator `in` is not supported"
|
||||
reveal_type(result) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
# Comparison: Unsupported operators
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag1: bool, flag2: bool):
|
||||
class A: ...
|
||||
a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`"
|
||||
reveal_type(a) # revealed: bool
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`"
|
||||
reveal_type(b) # revealed: bool
|
||||
class A: ...
|
||||
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `object` and `int`")
|
||||
c = object() < 5
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(c) # revealed: bool
|
||||
a = 1 in 7 # error: "Operator `in` is not supported for types `Literal[1]` and `Literal[7]`"
|
||||
reveal_type(a) # revealed: bool
|
||||
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `int` and `object`")
|
||||
d = 5 < object()
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(d) # revealed: bool
|
||||
b = 0 not in 10 # error: "Operator `not in` is not supported for types `Literal[0]` and `Literal[10]`"
|
||||
reveal_type(b) # revealed: bool
|
||||
|
||||
int_literal_or_str_literal = 1 if flag else "foo"
|
||||
# error: "Operator `in` is not supported for types `Literal[42]` and `Literal[1]`, in comparing `Literal[42]` with `Literal[1] | Literal["foo"]`"
|
||||
e = 42 in int_literal_or_str_literal
|
||||
reveal_type(e) # revealed: bool
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `object` and `int`")
|
||||
c = object() < 5
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(c) # revealed: bool
|
||||
|
||||
# TODO: should error, need to check if __lt__ signature is valid for right operand
|
||||
# error may be "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`
|
||||
f = (1, 2) < (1, "hello")
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(f) # revealed: bool
|
||||
# TODO: should error, once operand type check is implemented
|
||||
# ("Operator `<` is not supported for types `int` and `object`")
|
||||
d = 5 < object()
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(d) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`"
|
||||
g = (flag1, A()) < (flag2, A())
|
||||
reveal_type(g) # revealed: Unknown
|
||||
flag = bool_instance()
|
||||
int_literal_or_str_literal = 1 if flag else "foo"
|
||||
# error: "Operator `in` is not supported for types `Literal[42]` and `Literal[1]`, in comparing `Literal[42]` with `Literal[1] | Literal["foo"]`"
|
||||
e = 42 in int_literal_or_str_literal
|
||||
reveal_type(e) # revealed: bool
|
||||
|
||||
# TODO: should error, need to check if __lt__ signature is valid for right operand
|
||||
# error may be "Operator `<` is not supported for types `int` and `str`, in comparing `tuple[Literal[1], Literal[2]]` with `tuple[Literal[1], Literal["hello"]]`
|
||||
f = (1, 2) < (1, "hello")
|
||||
# TODO: should be Unknown, once operand type check is implemented
|
||||
reveal_type(f) # revealed: bool
|
||||
|
||||
# error: [unsupported-operator] "Operator `<` is not supported for types `A` and `A`, in comparing `tuple[bool, A]` with `tuple[bool, A]`"
|
||||
g = (bool_instance(), A()) < (bool_instance(), A())
|
||||
reveal_type(g) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -3,35 +3,47 @@
|
||||
## Simple if-expression
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else 2
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else 2
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## If-expression with walrus operator
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
y = 0
|
||||
z = 0
|
||||
x = (y := 1) if flag else (z := 2)
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
reveal_type(y) # revealed: Literal[0, 1]
|
||||
reveal_type(z) # revealed: Literal[0, 2]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
y = 0
|
||||
z = 0
|
||||
x = (y := 1) if flag else (z := 2)
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
reveal_type(y) # revealed: Literal[0, 1]
|
||||
reveal_type(z) # revealed: Literal[0, 2]
|
||||
```
|
||||
|
||||
## Nested if-expression
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
x = 1 if flag else 2 if flag2 else 3
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
x = 1 if flag else 2 if flag2 else 3
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
## None
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else None
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else None
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
```
|
||||
|
||||
@@ -3,115 +3,128 @@
|
||||
## Simple if
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
y = 1
|
||||
y = 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
y = 3
|
||||
flag = bool_instance()
|
||||
y = 1
|
||||
y = 2
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3]
|
||||
if flag:
|
||||
y = 3
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3]
|
||||
```
|
||||
|
||||
## Simple if-elif-else
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
y = 1
|
||||
y = 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
y = 3
|
||||
elif flag2:
|
||||
y = 4
|
||||
else:
|
||||
r = y
|
||||
y = 5
|
||||
s = y
|
||||
x = y
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
y = 1
|
||||
y = 2
|
||||
if flag:
|
||||
y = 3
|
||||
elif flag2:
|
||||
y = 4
|
||||
else:
|
||||
r = y
|
||||
y = 5
|
||||
s = y
|
||||
x = y
|
||||
|
||||
reveal_type(x) # revealed: Literal[3, 4, 5]
|
||||
reveal_type(x) # revealed: Literal[3, 4, 5]
|
||||
|
||||
# revealed: Literal[2]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(r)
|
||||
# revealed: Literal[2]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(r)
|
||||
|
||||
# revealed: Literal[5]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(s)
|
||||
# revealed: Literal[5]
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(s)
|
||||
```
|
||||
|
||||
## Single symbol across if-elif-else
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
if flag:
|
||||
y = 1
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
y = 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
|
||||
if flag:
|
||||
y = 1
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
y = 3
|
||||
reveal_type(y) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
## if-elif-else without else assignment
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
y = 0
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
y = 1
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(y) # revealed: Literal[0, 1, 2]
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
y = 0
|
||||
if flag:
|
||||
y = 1
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
pass
|
||||
reveal_type(y) # revealed: Literal[0, 1, 2]
|
||||
```
|
||||
|
||||
## if-elif-else with intervening assignment
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
y = 0
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
y = 1
|
||||
z = 3
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(y) # revealed: Literal[0, 1, 2]
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
y = 0
|
||||
if flag:
|
||||
y = 1
|
||||
z = 3
|
||||
elif flag2:
|
||||
y = 2
|
||||
else:
|
||||
pass
|
||||
reveal_type(y) # revealed: Literal[0, 1, 2]
|
||||
```
|
||||
|
||||
## Nested if statement
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
y = 0
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
if flag2:
|
||||
y = 1
|
||||
|
||||
reveal_type(y) # revealed: Literal[0, 1]
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
y = 0
|
||||
if flag:
|
||||
if flag2:
|
||||
y = 1
|
||||
reveal_type(y) # revealed: Literal[0, 1]
|
||||
```
|
||||
|
||||
## if-elif without else
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
y = 1
|
||||
y = 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag:
|
||||
y = 3
|
||||
elif flag2:
|
||||
y = 4
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
y = 1
|
||||
y = 2
|
||||
if flag:
|
||||
y = 3
|
||||
elif flag2:
|
||||
y = 4
|
||||
|
||||
reveal_type(y) # revealed: Literal[2, 3, 4]
|
||||
reveal_type(y) # revealed: Literal[2, 3, 4]
|
||||
```
|
||||
|
||||
@@ -31,7 +31,6 @@ reveal_type(y)
|
||||
```py
|
||||
y = 1
|
||||
y = 2
|
||||
|
||||
match 0:
|
||||
case 1:
|
||||
y = 3
|
||||
|
||||
@@ -10,35 +10,42 @@ x: str # error: [invalid-declaration] "Cannot declare type `str` for inferred t
|
||||
## Incompatible declarations
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: str, int"
|
||||
```
|
||||
|
||||
## Partial declarations
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x: int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int"
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
x: int
|
||||
x = 1 # error: [conflicting-declarations] "Conflicting declared types for `x`: Unknown, int"
|
||||
```
|
||||
|
||||
## Incompatible declarations with bad assignment
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# error: [conflicting-declarations]
|
||||
# error: [invalid-assignment]
|
||||
x = b"foo"
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
|
||||
# error: [conflicting-declarations]
|
||||
# error: [invalid-assignment]
|
||||
x = b"foo"
|
||||
```
|
||||
|
||||
@@ -49,44 +49,12 @@ def foo(
|
||||
try:
|
||||
help()
|
||||
except x as e:
|
||||
reveal_type(e) # revealed: AttributeError
|
||||
# TODO: should be `AttributeError`
|
||||
reveal_type(e) # revealed: @Todo(exception type)
|
||||
except y as f:
|
||||
reveal_type(f) # revealed: OSError | RuntimeError
|
||||
# TODO: should be `OSError | RuntimeError`
|
||||
reveal_type(f) # revealed: @Todo(exception type)
|
||||
except z as g:
|
||||
# TODO: should be `BaseException`
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
```
|
||||
|
||||
## Invalid exception handlers
|
||||
|
||||
```py
|
||||
try:
|
||||
pass
|
||||
# error: [invalid-exception-caught] "Cannot catch object of type `Literal[3]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
|
||||
except 3 as e:
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
try:
|
||||
pass
|
||||
# error: [invalid-exception-caught] "Cannot catch object of type `Literal["foo"]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
|
||||
# error: [invalid-exception-caught] "Cannot catch object of type `Literal[b"bar"]` in an exception handler (must be a `BaseException` subclass or a tuple of `BaseException` subclasses)"
|
||||
except (ValueError, OSError, "foo", b"bar") as e:
|
||||
reveal_type(e) # revealed: ValueError | OSError | Unknown
|
||||
|
||||
def foo(
|
||||
x: type[str],
|
||||
y: tuple[type[OSError], type[RuntimeError], int],
|
||||
z: tuple[type[str], ...],
|
||||
):
|
||||
try:
|
||||
help()
|
||||
# error: [invalid-exception-caught]
|
||||
except x as e:
|
||||
reveal_type(e) # revealed: Unknown
|
||||
# error: [invalid-exception-caught]
|
||||
except y as f:
|
||||
reveal_type(f) # revealed: OSError | RuntimeError | Unknown
|
||||
except z as g:
|
||||
# TODO: should emit a diagnostic here:
|
||||
reveal_type(g) # revealed: @Todo(full tuple[...] support)
|
||||
reveal_type(g) # revealed: @Todo(exception type)
|
||||
```
|
||||
|
||||
@@ -1,59 +1,30 @@
|
||||
# `except*`
|
||||
# Except star
|
||||
|
||||
## `except*` with `BaseException`
|
||||
## Except\* with BaseException
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* BaseException as e:
|
||||
# TODO: should be `BaseExceptionGroup[BaseException]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with specific exception
|
||||
## Except\* with specific exception
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* OSError as e:
|
||||
# TODO: more precise would be `ExceptionGroup[OSError]` --Alex
|
||||
# (needs homogenous tuples + generics)
|
||||
# TODO(Alex): more precise would be `ExceptionGroup[OSError]`
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with multiple exceptions
|
||||
## Except\* with multiple exceptions
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* (TypeError, AttributeError) as e:
|
||||
# TODO: more precise would be `ExceptionGroup[TypeError | AttributeError]` --Alex
|
||||
# (needs homogenous tuples + generics)
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## `except*` with mix of `Exception`s and `BaseException`s
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* (KeyboardInterrupt, AttributeError) as e:
|
||||
# TODO: more precise would be `BaseExceptionGroup[KeyboardInterrupt | AttributeError]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
## Invalid `except*` handlers
|
||||
|
||||
```py
|
||||
try:
|
||||
help()
|
||||
except* 3 as e: # error: [invalid-exception-caught]
|
||||
# TODO: Should be `BaseExceptionGroup[Unknown]` --Alex
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
|
||||
try:
|
||||
help()
|
||||
except* (AttributeError, 42) as e: # error: [invalid-exception-caught]
|
||||
# TODO: Should be `BaseExceptionGroup[AttributeError | Unknown]` --Alex
|
||||
# TODO(Alex): more precise would be `ExceptionGroup[TypeError | AttributeError]`.
|
||||
reveal_type(e) # revealed: BaseExceptionGroup
|
||||
```
|
||||
|
||||
@@ -9,4 +9,5 @@ try:
|
||||
print
|
||||
except as e: # error: [invalid-syntax]
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
```
|
||||
|
||||
@@ -3,25 +3,26 @@
|
||||
## Boundness
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A:
|
||||
always_bound = 1
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag:
|
||||
union = 1
|
||||
else:
|
||||
union = "abc"
|
||||
class A:
|
||||
always_bound = 1
|
||||
|
||||
if flag:
|
||||
possibly_unbound = "abc"
|
||||
if flag():
|
||||
union = 1
|
||||
else:
|
||||
union = "abc"
|
||||
|
||||
reveal_type(A.always_bound) # revealed: Literal[1]
|
||||
if flag():
|
||||
possibly_unbound = "abc"
|
||||
|
||||
reveal_type(A.union) # revealed: Literal[1] | Literal["abc"]
|
||||
reveal_type(A.always_bound) # revealed: Literal[1]
|
||||
|
||||
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound"
|
||||
reveal_type(A.possibly_unbound) # revealed: Literal["abc"]
|
||||
reveal_type(A.union) # revealed: Literal[1] | Literal["abc"]
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`"
|
||||
reveal_type(A.non_existent) # revealed: Unknown
|
||||
# error: [possibly-unbound-attribute] "Attribute `possibly_unbound` on type `Literal[A]` is possibly unbound"
|
||||
reveal_type(A.possibly_unbound) # revealed: Literal["abc"]
|
||||
|
||||
# error: [unresolved-attribute] "Type `Literal[A]` has no attribute `non_existent`"
|
||||
reveal_type(A.non_existent) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -3,45 +3,54 @@
|
||||
## OR
|
||||
|
||||
```py
|
||||
def _(foo: str):
|
||||
reveal_type(True or False) # revealed: Literal[True]
|
||||
reveal_type("x" or "y" or "z") # revealed: Literal["x"]
|
||||
reveal_type("" or "y" or "z") # revealed: Literal["y"]
|
||||
reveal_type(False or "z") # revealed: Literal["z"]
|
||||
reveal_type(False or True) # revealed: Literal[True]
|
||||
reveal_type(False or False) # revealed: Literal[False]
|
||||
reveal_type(foo or False) # revealed: str | Literal[False]
|
||||
reveal_type(foo or True) # revealed: str | Literal[True]
|
||||
def foo() -> str:
|
||||
pass
|
||||
|
||||
reveal_type(True or False) # revealed: Literal[True]
|
||||
reveal_type("x" or "y" or "z") # revealed: Literal["x"]
|
||||
reveal_type("" or "y" or "z") # revealed: Literal["y"]
|
||||
reveal_type(False or "z") # revealed: Literal["z"]
|
||||
reveal_type(False or True) # revealed: Literal[True]
|
||||
reveal_type(False or False) # revealed: Literal[False]
|
||||
reveal_type(foo() or False) # revealed: str | Literal[False]
|
||||
reveal_type(foo() or True) # revealed: str | Literal[True]
|
||||
```
|
||||
|
||||
## AND
|
||||
|
||||
```py
|
||||
def _(foo: str):
|
||||
reveal_type(True and False) # revealed: Literal[False]
|
||||
reveal_type(False and True) # revealed: Literal[False]
|
||||
reveal_type(foo and False) # revealed: str | Literal[False]
|
||||
reveal_type(foo and True) # revealed: str | Literal[True]
|
||||
reveal_type("x" and "y" and "z") # revealed: Literal["z"]
|
||||
reveal_type("x" and "y" and "") # revealed: Literal[""]
|
||||
reveal_type("" and "y") # revealed: Literal[""]
|
||||
def foo() -> str:
|
||||
pass
|
||||
|
||||
reveal_type(True and False) # revealed: Literal[False]
|
||||
reveal_type(False and True) # revealed: Literal[False]
|
||||
reveal_type(foo() and False) # revealed: str | Literal[False]
|
||||
reveal_type(foo() and True) # revealed: str | Literal[True]
|
||||
reveal_type("x" and "y" and "z") # revealed: Literal["z"]
|
||||
reveal_type("x" and "y" and "") # revealed: Literal[""]
|
||||
reveal_type("" and "y") # revealed: Literal[""]
|
||||
```
|
||||
|
||||
## Simple function calls to bool
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x = True
|
||||
else:
|
||||
x = False
|
||||
def returns_bool() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
if returns_bool():
|
||||
x = True
|
||||
else:
|
||||
x = False
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
```
|
||||
|
||||
## Complex
|
||||
|
||||
```py
|
||||
def foo() -> str:
|
||||
pass
|
||||
|
||||
reveal_type("x" and "y" or "z") # revealed: Literal["y"]
|
||||
reveal_type("x" or "y" and "z") # revealed: Literal["x"]
|
||||
reveal_type("" and "y" or "z") # revealed: Literal["z"]
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
## Union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
reveal_type(1 if flag else 2) # revealed: Literal[1, 2]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(1 if bool_instance() else 2) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## Statically known branches
|
||||
@@ -28,12 +30,14 @@ reveal_type(1 if 0 else 2) # revealed: Literal[2]
|
||||
The test inside an if expression should not affect code outside of the expression.
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x: Literal[42, "hello"] = 42 if flag else "hello"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
x: Literal[42, "hello"] = 42 if bool_instance() else "hello"
|
||||
|
||||
_ = ... if isinstance(x, str) else ...
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
_ = ... if isinstance(x, str) else ...
|
||||
|
||||
reveal_type(x) # revealed: Literal[42] | Literal["hello"]
|
||||
```
|
||||
|
||||
@@ -211,7 +211,8 @@ reveal_type(len(SecondRequiredArgument())) # revealed: Literal[1]
|
||||
### No `__len__`
|
||||
|
||||
```py
|
||||
class NoDunderLen: ...
|
||||
class NoDunderLen:
|
||||
pass
|
||||
|
||||
# TODO: Emit a diagnostic
|
||||
reveal_type(len(NoDunderLen())) # revealed: int
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
## Maybe unbound
|
||||
|
||||
```py path=maybe_unbound.py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
y = 3
|
||||
|
||||
x = y # error: [possibly-unresolved-reference]
|
||||
@@ -30,12 +31,13 @@ reveal_type(y) # revealed: Literal[3]
|
||||
## Maybe unbound annotated
|
||||
|
||||
```py path=maybe_unbound_annotated.py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
y: int = 3
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
y: int = 3
|
||||
x = y # error: [possibly-unresolved-reference]
|
||||
|
||||
# revealed: Literal[3]
|
||||
@@ -61,10 +63,10 @@ reveal_type(y) # revealed: int
|
||||
Importing a possibly undeclared name still gives us its declared type:
|
||||
|
||||
```py path=maybe_undeclared.py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
if bool_instance():
|
||||
x: int
|
||||
```
|
||||
|
||||
@@ -81,12 +83,14 @@ def f(): ...
|
||||
```
|
||||
|
||||
```py path=b.py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
from c import f
|
||||
else:
|
||||
|
||||
def f(): ...
|
||||
```
|
||||
|
||||
@@ -107,10 +111,11 @@ x: int
|
||||
```
|
||||
|
||||
```py path=b.py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
flag = bool_instance()
|
||||
if flag:
|
||||
from c import x
|
||||
else:
|
||||
x = 1
|
||||
|
||||
@@ -106,19 +106,23 @@ reveal_type(x)
|
||||
## With non-callable iterator
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class NotIterable:
|
||||
if flag:
|
||||
__iter__ = 1
|
||||
else:
|
||||
__iter__ = None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
pass
|
||||
flag = bool_instance()
|
||||
|
||||
# revealed: Unknown
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
class NotIterable:
|
||||
if flag:
|
||||
__iter__ = 1
|
||||
else:
|
||||
__iter__ = None
|
||||
|
||||
for x in NotIterable(): # error: "Object of type `NotIterable` is not iterable"
|
||||
pass
|
||||
|
||||
# revealed: Unknown
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(x)
|
||||
```
|
||||
|
||||
## Invalid iterable
|
||||
@@ -156,9 +160,13 @@ class Test2:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
def _(flag: bool):
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterator
|
||||
@@ -207,9 +215,13 @@ class Test2:
|
||||
def __iter__(self) -> TestIter3 | TestIter4:
|
||||
return TestIter3()
|
||||
|
||||
def _(flag: bool):
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: int | Exception | str | tuple[int, int] | bytes | memoryview
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has no `__iter__` method
|
||||
@@ -223,10 +235,12 @@ class Test:
|
||||
def __iter__(self) -> TestIter:
|
||||
return TestIter()
|
||||
|
||||
def _(flag: bool):
|
||||
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
|
||||
for x in Test() if flag else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
|
||||
for x in Test() if coinflip() else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## Union type as iterable where one union element has invalid `__iter__` method
|
||||
@@ -244,10 +258,12 @@ class Test2:
|
||||
def __iter__(self) -> int:
|
||||
return 42
|
||||
|
||||
def _(flag: bool):
|
||||
# error: "Object of type `Test | Test2` is not iterable"
|
||||
for x in Test() if flag else Test2():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# error: "Object of type `Test | Test2` is not iterable"
|
||||
for x in Test() if coinflip() else Test2():
|
||||
reveal_type(x) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Union type as iterator where one union element has no `__next__` method
|
||||
|
||||
@@ -3,45 +3,54 @@
|
||||
## Basic While Loop
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1
|
||||
while flag:
|
||||
x = 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
flag = bool_instance()
|
||||
x = 1
|
||||
while flag:
|
||||
x = 2
|
||||
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## While with else (no break)
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1
|
||||
while flag:
|
||||
x = 2
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
x = 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
flag = bool_instance()
|
||||
x = 1
|
||||
while flag:
|
||||
x = 2
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
x = 3
|
||||
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## While with Else (may break)
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag2: bool):
|
||||
x = 1
|
||||
y = 0
|
||||
while flag:
|
||||
x = 2
|
||||
if flag2:
|
||||
y = 4
|
||||
break
|
||||
else:
|
||||
y = x
|
||||
x = 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
reveal_type(y) # revealed: Literal[1, 2, 4]
|
||||
flag, flag2 = bool_instance(), bool_instance()
|
||||
x = 1
|
||||
y = 0
|
||||
while flag:
|
||||
x = 2
|
||||
if flag2:
|
||||
y = 4
|
||||
break
|
||||
else:
|
||||
y = x
|
||||
x = 3
|
||||
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
reveal_type(y) # revealed: Literal[1, 2, 4]
|
||||
```
|
||||
|
||||
## Nested while loops
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
## Narrowing for `bool(..)` checks
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
def flag() -> bool: ...
|
||||
|
||||
x = 1 if flag else None
|
||||
x = 1 if flag() else None
|
||||
|
||||
# valid invocation, positive
|
||||
# valid invocation, positive
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
# valid invocation, negative
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if not bool(x is not None):
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
# no args/narrowing
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if not bool():
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
# valid invocation, negative
|
||||
# invalid invocation, too many positional args
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, 5): # TODO diagnostic
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if not bool(x is not None):
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
# no args/narrowing
|
||||
# invalid invocation, too many kwargs
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, y=5): # TODO diagnostic
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if not bool():
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
|
||||
# invalid invocation, too many positional args
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, 5): # TODO diagnostic
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
|
||||
# invalid invocation, too many kwargs
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
if bool(x is not None, y=5): # TODO diagnostic
|
||||
reveal_type(x) # revealed: Literal[1] | None
|
||||
```
|
||||
|
||||
@@ -9,67 +9,85 @@ Similarly, in `and` expressions, the right-hand side is evaluated only if the le
|
||||
## Narrowing in `or`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A: ...
|
||||
x: A | None = A() if flag else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
x is None or reveal_type(x) # revealed: A
|
||||
reveal_type(x) # revealed: A | None
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
x is None or reveal_type(x) # revealed: A
|
||||
reveal_type(x) # revealed: A | None
|
||||
```
|
||||
|
||||
## Narrowing in `and`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A: ...
|
||||
x: A | None = A() if flag else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
x is None and reveal_type(x) # revealed: None
|
||||
reveal_type(x) # revealed: A | None
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
x is None and reveal_type(x) # revealed: None
|
||||
reveal_type(x) # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple `and` arms
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool, flag3: bool, flag4: bool):
|
||||
class A: ...
|
||||
x: A | None = A() if flag1 else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag2 and isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
isinstance(x, A) and flag2 and reveal_type(x) # revealed: A
|
||||
reveal_type(x) and isinstance(x, A) and flag3 # revealed: A | None
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
bool_instance() and isinstance(x, A) and reveal_type(x) # revealed: A
|
||||
isinstance(x, A) and bool_instance() and reveal_type(x) # revealed: A
|
||||
reveal_type(x) and isinstance(x, A) and bool_instance() # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple `or` arms
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool, flag3: bool, flag4: bool):
|
||||
class A: ...
|
||||
x: A | None = A() if flag1 else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag2 or isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
isinstance(x, A) or flag3 or reveal_type(x) # revealed: None
|
||||
reveal_type(x) or isinstance(x, A) or flag4 # revealed: A | None
|
||||
class A: ...
|
||||
|
||||
x: A | None = A() if bool_instance() else None
|
||||
|
||||
bool_instance() or isinstance(x, A) or reveal_type(x) # revealed: None
|
||||
isinstance(x, A) or bool_instance() or reveal_type(x) # revealed: None
|
||||
reveal_type(x) or isinstance(x, A) or bool_instance() # revealed: A | None
|
||||
```
|
||||
|
||||
## Multiple predicates
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
class A: ...
|
||||
x: A | None | Literal[1] = A() if flag1 else None if flag2 else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1]
|
||||
class A: ...
|
||||
|
||||
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
|
||||
|
||||
x is None or isinstance(x, A) or reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Mix of `and` and `or`
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
class A: ...
|
||||
x: A | None | Literal[1] = A() if flag1 else None if flag2 else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1]
|
||||
class A: ...
|
||||
|
||||
x: A | None | Literal[1] = A() if bool_instance() else None if bool_instance() else 1
|
||||
|
||||
isinstance(x, A) or x is not None and reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(x: A | B):
|
||||
if isinstance(x, A) and isinstance(x, B):
|
||||
reveal_type(x) # revealed: A & B
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | A & ~B
|
||||
def instance() -> A | B:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) and isinstance(x, B):
|
||||
reveal_type(x) # revealed: A & B
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | A & ~B
|
||||
```
|
||||
|
||||
## Arms might not add narrowing constraints
|
||||
@@ -19,18 +23,25 @@ def _(x: A | B):
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(flag: bool, x: A | B):
|
||||
if isinstance(x, A) and flag:
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if flag and isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
def instance() -> A | B:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) and bool_instance():
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if bool_instance() and isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
reveal_type(x) # revealed: A | B
|
||||
```
|
||||
|
||||
## Statically known arms
|
||||
@@ -39,35 +50,39 @@ def _(flag: bool, x: A | B):
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(x: A | B):
|
||||
if isinstance(x, A) and True:
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
def instance() -> A | B:
|
||||
return A()
|
||||
|
||||
if True and isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
x = instance()
|
||||
|
||||
if False and isinstance(x, A):
|
||||
# TODO: should emit an `unreachable code` diagnostic
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
if isinstance(x, A) and True:
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
if False or isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
if True or isinstance(x, A):
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
# TODO: should emit an `unreachable code` diagnostic
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
if True and isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
if False and isinstance(x, A):
|
||||
# TODO: should emit an `unreachable code` diagnostic
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if False or isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
if True or isinstance(x, A):
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
# TODO: should emit an `unreachable code` diagnostic
|
||||
reveal_type(x) # revealed: B & ~A
|
||||
|
||||
reveal_type(x) # revealed: A | B
|
||||
```
|
||||
|
||||
## The type of multiple symbols can be narrowed down
|
||||
@@ -76,17 +91,22 @@ def _(x: A | B):
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(x: A | B, y: A | B):
|
||||
if isinstance(x, A) and isinstance(y, B):
|
||||
reveal_type(x) # revealed: A
|
||||
reveal_type(y) # revealed: B
|
||||
else:
|
||||
# No narrowing: Only-one or both checks might have failed
|
||||
reveal_type(x) # revealed: A | B
|
||||
reveal_type(y) # revealed: A | B
|
||||
def instance() -> A | B:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
y = instance()
|
||||
|
||||
if isinstance(x, A) and isinstance(y, B):
|
||||
reveal_type(x) # revealed: A
|
||||
reveal_type(y) # revealed: B
|
||||
else:
|
||||
# No narrowing: Only-one or both checks might have failed
|
||||
reveal_type(x) # revealed: A | B
|
||||
reveal_type(y) # revealed: A | B
|
||||
|
||||
reveal_type(x) # revealed: A | B
|
||||
reveal_type(y) # revealed: A | B
|
||||
```
|
||||
|
||||
## Narrowing in `or` conditional
|
||||
@@ -96,11 +116,15 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, A) or isinstance(x, B):
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) or isinstance(x, B):
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
```
|
||||
|
||||
## In `or`, all arms should add constraint in order to narrow
|
||||
@@ -110,11 +134,18 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(flag: bool, x: A | B | C):
|
||||
if isinstance(x, A) or isinstance(x, B) or flag:
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) or isinstance(x, B) or bool_instance():
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~A & ~B
|
||||
```
|
||||
|
||||
## in `or`, all arms should narrow the same set of symbols
|
||||
@@ -124,23 +155,28 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C, y: A | B | C):
|
||||
if isinstance(x, A) or isinstance(y, A):
|
||||
# The predicate might be satisfied by the right side, so the type of `x` can’t be narrowed down here.
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
# The same for `y`
|
||||
reveal_type(y) # revealed: A | B | C
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A
|
||||
reveal_type(y) # revealed: B & ~A | C & ~A
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
if (isinstance(x, A) and isinstance(y, A)) or (isinstance(x, B) and isinstance(y, B)):
|
||||
# Here, types of `x` and `y` can be narrowd since all `or` arms constraint them.
|
||||
reveal_type(x) # revealed: A | B
|
||||
reveal_type(y) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
reveal_type(y) # revealed: A | B | C
|
||||
x = instance()
|
||||
y = instance()
|
||||
|
||||
if isinstance(x, A) or isinstance(y, A):
|
||||
# The predicate might be satisfied by the right side, so the type of `x` can’t be narrowed down here.
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
# The same for `y`
|
||||
reveal_type(y) # revealed: A | B | C
|
||||
else:
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A
|
||||
reveal_type(y) # revealed: B & ~A | C & ~A
|
||||
|
||||
if (isinstance(x, A) and isinstance(y, A)) or (isinstance(x, B) and isinstance(y, B)):
|
||||
# Here, types of `x` and `y` can be narrowd since all `or` arms constraint them.
|
||||
reveal_type(x) # revealed: A | B
|
||||
reveal_type(y) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B | C
|
||||
reveal_type(y) # revealed: A | B | C
|
||||
```
|
||||
|
||||
## mixing `and` and `not`
|
||||
@@ -150,12 +186,16 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, B) and not isinstance(x, C):
|
||||
reveal_type(x) # revealed: B & ~C
|
||||
else:
|
||||
# ~(B & ~C) -> ~B | C -> (A & ~B) | (C & ~B) | C -> (A & ~B) | C
|
||||
reveal_type(x) # revealed: A & ~B | C
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, B) and not isinstance(x, C):
|
||||
reveal_type(x) # revealed: B & ~C
|
||||
else:
|
||||
# ~(B & ~C) -> ~B | C -> (A & ~B) | (C & ~B) | C -> (A & ~B) | C
|
||||
reveal_type(x) # revealed: A & ~B | C
|
||||
```
|
||||
|
||||
## mixing `or` and `not`
|
||||
@@ -165,11 +205,15 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, B) or not isinstance(x, C):
|
||||
reveal_type(x) # revealed: B | A & ~C
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~B
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, B) or not isinstance(x, C):
|
||||
reveal_type(x) # revealed: B | A & ~C
|
||||
else:
|
||||
reveal_type(x) # revealed: C & ~B
|
||||
```
|
||||
|
||||
## `or` with nested `and`
|
||||
@@ -179,12 +223,16 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, A) or (isinstance(x, B) and not isinstance(x, C)):
|
||||
reveal_type(x) # revealed: A | B & ~C
|
||||
else:
|
||||
# ~(A | (B & ~C)) -> ~A & ~(B & ~C) -> ~A & (~B | C) -> (~A & C) | (~A ~ B)
|
||||
reveal_type(x) # revealed: C & ~A
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) or (isinstance(x, B) and not isinstance(x, C)):
|
||||
reveal_type(x) # revealed: A | B & ~C
|
||||
else:
|
||||
# ~(A | (B & ~C)) -> ~A & ~(B & ~C) -> ~A & (~B | C) -> (~A & C) | (~A ~ B)
|
||||
reveal_type(x) # revealed: C & ~A
|
||||
```
|
||||
|
||||
## `and` with nested `or`
|
||||
@@ -194,32 +242,41 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
def _(x: A | B | C):
|
||||
if isinstance(x, A) and (isinstance(x, B) or not isinstance(x, C)):
|
||||
# A & (B | ~C) -> (A & B) | (A & ~C)
|
||||
reveal_type(x) # revealed: A & B | A & ~C
|
||||
else:
|
||||
# ~((A & B) | (A & ~C)) ->
|
||||
# ~(A & B) & ~(A & ~C) ->
|
||||
# (~A | ~B) & (~A | C) ->
|
||||
# [(~A | ~B) & ~A] | [(~A | ~B) & C] ->
|
||||
# ~A | (~A & C) | (~B & C) ->
|
||||
# ~A | (C & ~B) ->
|
||||
# ~A | (C & ~B) The positive side of ~A is A | B | C ->
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A | C & ~B
|
||||
def instance() -> A | B | C:
|
||||
return A()
|
||||
|
||||
x = instance()
|
||||
|
||||
if isinstance(x, A) and (isinstance(x, B) or not isinstance(x, C)):
|
||||
# A & (B | ~C) -> (A & B) | (A & ~C)
|
||||
reveal_type(x) # revealed: A & B | A & ~C
|
||||
else:
|
||||
# ~((A & B) | (A & ~C)) ->
|
||||
# ~(A & B) & ~(A & ~C) ->
|
||||
# (~A | ~B) & (~A | C) ->
|
||||
# [(~A | ~B) & ~A] | [(~A | ~B) & C] ->
|
||||
# ~A | (~A & C) | (~B & C) ->
|
||||
# ~A | (C & ~B) ->
|
||||
# ~A | (C & ~B) The positive side of ~A is A | B | C ->
|
||||
reveal_type(x) # revealed: B & ~A | C & ~A | C & ~B
|
||||
```
|
||||
|
||||
## Boolean expression internal narrowing
|
||||
|
||||
```py
|
||||
def _(x: str | None, y: str | None):
|
||||
if x is None and y is not x:
|
||||
reveal_type(y) # revealed: str
|
||||
def optional_string() -> str | None:
|
||||
return None
|
||||
|
||||
# Neither of the conditions alone is sufficient for narrowing y's type:
|
||||
if x is None:
|
||||
reveal_type(y) # revealed: str | None
|
||||
x = optional_string()
|
||||
y = optional_string()
|
||||
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: str | None
|
||||
if x is None and y is not x:
|
||||
reveal_type(y) # revealed: str
|
||||
|
||||
# Neither of the conditions alone is sufficient for narrowing y's type:
|
||||
if x is None:
|
||||
reveal_type(y) # revealed: str | None
|
||||
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: str | None
|
||||
```
|
||||
|
||||
@@ -3,47 +3,55 @@
|
||||
## Positive contributions become negative in elif-else blocks
|
||||
|
||||
```py
|
||||
def _(x: int):
|
||||
if x == 1:
|
||||
# cannot narrow; could be a subclass of `int`
|
||||
reveal_type(x) # revealed: int
|
||||
elif x == 2:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
elif x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = int_instance()
|
||||
|
||||
if x == 1:
|
||||
# cannot narrow; could be a subclass of `int`
|
||||
reveal_type(x) # revealed: int
|
||||
elif x == 2:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
elif x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
```
|
||||
|
||||
## Positive contributions become negative in elif-else blocks, with simplification
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x == 1:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
elif x == 2:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x == 1:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
elif x == 2:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## Multiple negative contributions using elif, with simplification
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x != 2:
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
elif x == 3:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x != 2:
|
||||
# TODO should be `Literal[1]`
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
elif x == 3:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
@@ -3,64 +3,77 @@
|
||||
## `is None`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = None if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
flag = bool_instance()
|
||||
x = None if flag else 1
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `is` for other types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A: ...
|
||||
x = A()
|
||||
y = x if flag else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if y is x:
|
||||
reveal_type(y) # revealed: A
|
||||
else:
|
||||
reveal_type(y) # revealed: A | None
|
||||
flag = bool_instance()
|
||||
|
||||
class A: ...
|
||||
|
||||
x = A()
|
||||
y = x if flag else None
|
||||
|
||||
if y is x:
|
||||
reveal_type(y) # revealed: A
|
||||
else:
|
||||
reveal_type(y) # revealed: A | None
|
||||
|
||||
reveal_type(y) # revealed: A | None
|
||||
```
|
||||
|
||||
## `is` in chained comparisons
|
||||
|
||||
```py
|
||||
def _(x_flag: bool, y_flag: bool):
|
||||
x = True if x_flag else False
|
||||
y = True if y_flag else False
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x_flag, y_flag = bool_instance(), bool_instance()
|
||||
x = True if x_flag else False
|
||||
y = True if y_flag else False
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
|
||||
if y is x is False: # Interpreted as `(y is x) and (x is False)`
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is not x) or (x is not False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
|
||||
if y is x is False: # Interpreted as `(y is x) and (x is False)`
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is not x) or (x is not False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
## `is` in elif clause
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = None if flag1 else (1 if flag2 else True)
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1] | Literal[True]
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
elif x is True:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
x = None if bool_instance() else (1 if bool_instance() else True)
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1] | Literal[True]
|
||||
if x is None:
|
||||
reveal_type(x) # revealed: None
|
||||
elif x is True:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -5,28 +5,34 @@
|
||||
The type guard removes `None` from the union type:
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = None if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
flag = bool_instance()
|
||||
x = None if flag else 1
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
if x is not None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `is not` for other singleton types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = True if flag else False
|
||||
reveal_type(x) # revealed: bool
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x is not False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
flag = bool_instance()
|
||||
x = True if flag else False
|
||||
reveal_type(x) # revealed: bool
|
||||
|
||||
if x is not False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[False]
|
||||
```
|
||||
|
||||
## `is not` for non-singleton types
|
||||
@@ -47,17 +53,20 @@ else:
|
||||
## `is not` for other types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A: ...
|
||||
x = A()
|
||||
y = x if flag else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: A | None
|
||||
else:
|
||||
reveal_type(y) # revealed: A
|
||||
class A: ...
|
||||
|
||||
x = A()
|
||||
y = x if bool_instance() else None
|
||||
|
||||
if y is not x:
|
||||
reveal_type(y) # revealed: A | None
|
||||
else:
|
||||
reveal_type(y) # revealed: A
|
||||
|
||||
reveal_type(y) # revealed: A | None
|
||||
```
|
||||
|
||||
## `is not` in chained comparisons
|
||||
@@ -65,20 +74,23 @@ def _(flag: bool):
|
||||
The type guard removes `False` from the union type of the tested value only.
|
||||
|
||||
```py
|
||||
def _(x_flag: bool, y_flag: bool):
|
||||
x = True if x_flag else False
|
||||
y = True if y_flag else False
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x_flag, y_flag = bool_instance(), bool_instance()
|
||||
x = True if x_flag else False
|
||||
y = True if y_flag else False
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
|
||||
if y is not x is not False: # Interpreted as `(y is not x) and (x is not False)`
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is x) or (x is False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
|
||||
if y is not x is not False: # Interpreted as `(y is not x) and (x is not False)`
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
reveal_type(y) # revealed: bool
|
||||
else:
|
||||
# The negation of the clause above is (y is x) or (x is False)
|
||||
# So we can't narrow the type of x or y here, because each arm of the `or` could be true
|
||||
|
||||
reveal_type(x) # revealed: bool
|
||||
reveal_type(y) # revealed: bool
|
||||
```
|
||||
|
||||
@@ -3,45 +3,54 @@
|
||||
## Multiple negative contributions
|
||||
|
||||
```py
|
||||
def _(x: int):
|
||||
if x != 1:
|
||||
if x != 2:
|
||||
if x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
x = int_instance()
|
||||
|
||||
if x != 1:
|
||||
if x != 2:
|
||||
if x != 3:
|
||||
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
|
||||
```
|
||||
|
||||
## Multiple negative contributions with simplification
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x != 2:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
flag1, flag2 = bool_instance(), bool_instance()
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x != 2:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
```
|
||||
|
||||
## elif-else blocks
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2 if flag2 else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != 1:
|
||||
x = 1 if bool_instance() else 2 if bool_instance() else 3
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x == 2:
|
||||
# TODO should be `Literal[2]`
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
if x == 2:
|
||||
# TODO should be `Literal[2]`
|
||||
reveal_type(x) # revealed: Literal[2, 3]
|
||||
elif x == 3:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
elif x != 2:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
elif x == 3:
|
||||
reveal_type(x) # revealed: Literal[3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
elif x != 2:
|
||||
# TODO should be Literal[1]
|
||||
reveal_type(x) # revealed: Literal[1, 3]
|
||||
else:
|
||||
# TODO should be Never
|
||||
reveal_type(x) # revealed: Literal[1, 2, 3]
|
||||
```
|
||||
|
||||
@@ -5,25 +5,29 @@ The `not` operator negates a constraint.
|
||||
## `not is None`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = None if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if not x is None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
x = None if bool_instance() else 1
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
if not x is None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: None
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `not isinstance`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if not isinstance(x, (int)):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
x = 1 if bool_instance() else "a"
|
||||
|
||||
if not isinstance(x, (int)):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
@@ -3,66 +3,82 @@
|
||||
## `x != None`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = None if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
# TODO should be None
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
flag = bool_instance()
|
||||
x = None if flag else 1
|
||||
|
||||
if x != None:
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
# TODO should be None
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
```
|
||||
|
||||
## `!=` for other singleton types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = True if flag else False
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
# TODO should be Literal[False]
|
||||
reveal_type(x) # revealed: bool
|
||||
flag = bool_instance()
|
||||
x = True if flag else False
|
||||
|
||||
if x != False:
|
||||
reveal_type(x) # revealed: Literal[True]
|
||||
else:
|
||||
# TODO should be Literal[False]
|
||||
reveal_type(x) # revealed: bool
|
||||
```
|
||||
|
||||
## `x != y` where `y` is of literal type
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else 2
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else 2
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
```
|
||||
|
||||
## `x != y` where `y` is a single-valued type
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A: ...
|
||||
class B: ...
|
||||
C = A if flag else B
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if C != A:
|
||||
reveal_type(C) # revealed: Literal[B]
|
||||
else:
|
||||
# TODO should be Literal[A]
|
||||
reveal_type(C) # revealed: Literal[A, B]
|
||||
flag = bool_instance()
|
||||
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
C = A if flag else B
|
||||
|
||||
if C != A:
|
||||
reveal_type(C) # revealed: Literal[B]
|
||||
else:
|
||||
# TODO should be Literal[A]
|
||||
reveal_type(C) # revealed: Literal[A, B]
|
||||
```
|
||||
|
||||
## `x != y` where `y` has multiple single-valued options
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
x = 1 if flag1 else 2
|
||||
y = 2 if flag2 else 3
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
x = 1 if bool_instance() else 2
|
||||
y = 2 if bool_instance() else 3
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
# TODO should be Literal[2]
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
## `!=` for non-single-valued types
|
||||
@@ -70,22 +86,34 @@ def _(flag1: bool, flag2: bool):
|
||||
Only single-valued types should narrow the type:
|
||||
|
||||
```py
|
||||
def _(flag: bool, a: int, y: int):
|
||||
x = a if flag else None
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: int | None
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
flag = bool_instance()
|
||||
x = int_instance() if flag else None
|
||||
y = int_instance()
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Mix of single-valued and non-single-valued types
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool, a: int):
|
||||
x = 1 if flag1 else 2
|
||||
y = 2 if flag2 else a
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if bool_instance() else 2
|
||||
y = 2 if bool_instance() else int_instance()
|
||||
|
||||
if x != y:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[1, 2]
|
||||
```
|
||||
|
||||
@@ -5,19 +5,23 @@ Narrowing for `isinstance(object, classinfo)` expressions.
|
||||
## `classinfo` is a single type
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = bool_instance()
|
||||
|
||||
x = 1 if flag else "a"
|
||||
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
if isinstance(x, str):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if isinstance(x, str):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if isinstance(x, (int, object)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
if isinstance(x, (int, object)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
```
|
||||
|
||||
## `classinfo` is a tuple of types
|
||||
@@ -26,48 +30,56 @@ Note: `isinstance(x, (int, str))` should not be confused with `isinstance(x, tup
|
||||
The former is equivalent to `isinstance(x, int | str)`:
|
||||
|
||||
```py
|
||||
def _(flag: bool, flag1: bool, flag2: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if isinstance(x, (int, str)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
flag, flag1, flag2 = bool_instance(), bool_instance(), bool_instance()
|
||||
|
||||
if isinstance(x, (int, bytes)):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
x = 1 if flag else "a"
|
||||
|
||||
if isinstance(x, (bytes, str)):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
if isinstance(x, (int, str)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
# No narrowing should occur if a larger type is also
|
||||
# one of the possibilities:
|
||||
if isinstance(x, (int, object)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
if isinstance(x, (int, bytes)):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
|
||||
y = 1 if flag1 else "a" if flag2 else b"b"
|
||||
if isinstance(y, (int, str)):
|
||||
reveal_type(y) # revealed: Literal[1] | Literal["a"]
|
||||
if isinstance(x, (bytes, str)):
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
|
||||
if isinstance(y, (int, bytes)):
|
||||
reveal_type(y) # revealed: Literal[1] | Literal[b"b"]
|
||||
# No narrowing should occur if a larger type is also
|
||||
# one of the possibilities:
|
||||
if isinstance(x, (int, object)):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if isinstance(y, (str, bytes)):
|
||||
reveal_type(y) # revealed: Literal["a"] | Literal[b"b"]
|
||||
y = 1 if flag1 else "a" if flag2 else b"b"
|
||||
if isinstance(y, (int, str)):
|
||||
reveal_type(y) # revealed: Literal[1] | Literal["a"]
|
||||
|
||||
if isinstance(y, (int, bytes)):
|
||||
reveal_type(y) # revealed: Literal[1] | Literal[b"b"]
|
||||
|
||||
if isinstance(y, (str, bytes)):
|
||||
reveal_type(y) # revealed: Literal["a"] | Literal[b"b"]
|
||||
```
|
||||
|
||||
## `classinfo` is a nested tuple of types
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if isinstance(x, (bool, (bytes, int))):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
flag = bool_instance()
|
||||
|
||||
x = 1 if flag else "a"
|
||||
|
||||
if isinstance(x, (bool, (bytes, int))):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal["a"]
|
||||
```
|
||||
|
||||
## Class types
|
||||
@@ -77,7 +89,9 @@ class A: ...
|
||||
class B: ...
|
||||
class C: ...
|
||||
|
||||
x = object()
|
||||
def get_object() -> object: ...
|
||||
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, A):
|
||||
reveal_type(x) # revealed: A
|
||||
@@ -98,40 +112,50 @@ else:
|
||||
## No narrowing for instances of `builtins.type`
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
t = type("t", (), {})
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# This isn't testing what we want it to test if we infer anything more precise here:
|
||||
reveal_type(t) # revealed: type
|
||||
flag = bool_instance()
|
||||
|
||||
x = 1 if flag else "foo"
|
||||
t = type("t", (), {})
|
||||
|
||||
if isinstance(x, t):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["foo"]
|
||||
# This isn't testing what we want it to test if we infer anything more precise here:
|
||||
reveal_type(t) # revealed: type
|
||||
x = 1 if flag else "foo"
|
||||
|
||||
if isinstance(x, t):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["foo"]
|
||||
```
|
||||
|
||||
## Do not use custom `isinstance` for narrowing
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
def isinstance(x, t):
|
||||
return True
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
flag = bool_instance()
|
||||
|
||||
def isinstance(x, t):
|
||||
return True
|
||||
|
||||
x = 1 if flag else "a"
|
||||
if isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
```
|
||||
|
||||
## Do support narrowing if `isinstance` is aliased
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
isinstance_alias = isinstance
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x = 1 if flag else "a"
|
||||
flag = bool_instance()
|
||||
|
||||
if isinstance_alias(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
isinstance_alias = isinstance
|
||||
|
||||
x = 1 if flag else "a"
|
||||
if isinstance_alias(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Do support narrowing if `isinstance` is imported
|
||||
@@ -139,38 +163,46 @@ def _(flag: bool):
|
||||
```py
|
||||
from builtins import isinstance as imported_isinstance
|
||||
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if imported_isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else "a"
|
||||
if imported_isinstance(x, int):
|
||||
reveal_type(x) # revealed: Literal[1]
|
||||
```
|
||||
|
||||
## Do not narrow if second argument is not a type
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if isinstance(x, "a"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else "a"
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if isinstance(x, "int"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if isinstance(x, "a"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if isinstance(x, "int"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
```
|
||||
|
||||
## Do not narrow if there are keyword arguments
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = 1 if flag else "a"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic
|
||||
# (`isinstance` has no `foo` parameter)
|
||||
if isinstance(x, int, foo="bar"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
flag = bool_instance()
|
||||
x = 1 if flag else "a"
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic
|
||||
# (`isinstance` has no `foo` parameter)
|
||||
if isinstance(x, int, foo="bar"):
|
||||
reveal_type(x) # revealed: Literal[1] | Literal["a"]
|
||||
```
|
||||
|
||||
@@ -7,43 +7,45 @@ Narrowing for `issubclass(class, classinfo)` expressions.
|
||||
### Basic example
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
t = int if flag else str
|
||||
def flag() -> bool: ...
|
||||
|
||||
if issubclass(t, bytes):
|
||||
reveal_type(t) # revealed: Never
|
||||
t = int if flag() else str
|
||||
|
||||
if issubclass(t, object):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
if issubclass(t, bytes):
|
||||
reveal_type(t) # revealed: Never
|
||||
|
||||
if issubclass(t, object):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
|
||||
if issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
|
||||
if issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Never
|
||||
reveal_type(t) # revealed: Never
|
||||
```
|
||||
|
||||
### Proper narrowing in `elif` and `else` branches
|
||||
|
||||
```py
|
||||
def _(flag1: bool, flag2: bool):
|
||||
t = int if flag1 else str if flag2 else bytes
|
||||
def flag() -> bool: ...
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str, bytes]
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
elif issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[bytes]
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str, bytes]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
elif issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[bytes]
|
||||
```
|
||||
|
||||
### Multiple derived classes
|
||||
@@ -54,28 +56,29 @@ class Derived1(Base): ...
|
||||
class Derived2(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def _(flag1: bool, flag2: bool, flag3: bool):
|
||||
t1 = Derived1 if flag1 else Derived2
|
||||
def flag() -> bool: ...
|
||||
|
||||
if issubclass(t1, Base):
|
||||
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
|
||||
t1 = Derived1 if flag() else Derived2
|
||||
|
||||
if issubclass(t1, Derived1):
|
||||
reveal_type(t1) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t1) # revealed: Literal[Derived2]
|
||||
if issubclass(t1, Base):
|
||||
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
|
||||
|
||||
t2 = Derived1 if flag2 else Base
|
||||
if issubclass(t1, Derived1):
|
||||
reveal_type(t1) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t1) # revealed: Literal[Derived2]
|
||||
|
||||
if issubclass(t2, Base):
|
||||
reveal_type(t2) # revealed: Literal[Derived1, Base]
|
||||
t2 = Derived1 if flag() else Base
|
||||
|
||||
t3 = Derived1 if flag3 else Unrelated
|
||||
if issubclass(t2, Base):
|
||||
reveal_type(t2) # revealed: Literal[Derived1, Base]
|
||||
|
||||
if issubclass(t3, Base):
|
||||
reveal_type(t3) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t3) # revealed: Literal[Unrelated]
|
||||
t3 = Derived1 if flag() else Unrelated
|
||||
|
||||
if issubclass(t3, Base):
|
||||
reveal_type(t3) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t3) # revealed: Literal[Unrelated]
|
||||
```
|
||||
|
||||
### Narrowing for non-literals
|
||||
@@ -84,13 +87,16 @@ def _(flag1: bool, flag2: bool, flag3: bool):
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(t: type[object]):
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
if issubclass(t, B):
|
||||
reveal_type(t) # revealed: type[A] & type[B]
|
||||
else:
|
||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
||||
def get_class() -> type[object]: ...
|
||||
|
||||
t = get_class()
|
||||
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
if issubclass(t, B):
|
||||
reveal_type(t) # revealed: type[A] & type[B]
|
||||
else:
|
||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
||||
```
|
||||
|
||||
### Handling of `None`
|
||||
@@ -101,15 +107,16 @@ def _(t: type[object]):
|
||||
# error: [possibly-unbound-import] "Member `NoneType` of module `types` is possibly unbound"
|
||||
from types import NoneType
|
||||
|
||||
def _(flag: bool):
|
||||
t = int if flag else NoneType
|
||||
def flag() -> bool: ...
|
||||
|
||||
if issubclass(t, NoneType):
|
||||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
t = int if flag() else NoneType
|
||||
|
||||
if issubclass(t, type(None)):
|
||||
# TODO: this should be just `Literal[NoneType]`
|
||||
reveal_type(t) # revealed: Literal[int, NoneType]
|
||||
if issubclass(t, NoneType):
|
||||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
|
||||
if issubclass(t, type(None)):
|
||||
# TODO: this should be just `Literal[NoneType]`
|
||||
reveal_type(t) # revealed: Literal[int, NoneType]
|
||||
```
|
||||
|
||||
## `classinfo` contains multiple types
|
||||
@@ -119,13 +126,14 @@ def _(flag: bool):
|
||||
```py
|
||||
class Unrelated: ...
|
||||
|
||||
def _(flag1: bool, flag2: bool):
|
||||
t = int if flag1 else str if flag2 else bytes
|
||||
def flag() -> bool: ...
|
||||
|
||||
if issubclass(t, (int, (Unrelated, (bytes,)))):
|
||||
reveal_type(t) # revealed: Literal[int, bytes]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, (int, (Unrelated, (bytes,)))):
|
||||
reveal_type(t) # revealed: Literal[int, bytes]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
## Special cases
|
||||
@@ -140,7 +148,9 @@ to `issubclass`:
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
t = object()
|
||||
def get_object() -> object: ...
|
||||
|
||||
t = get_object()
|
||||
|
||||
# TODO: we should emit a diagnostic here
|
||||
if issubclass(t, A):
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
## Single `match` pattern
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
x = None if flag else 1
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
flag = bool_instance()
|
||||
|
||||
y = 0
|
||||
x = None if flag else 1
|
||||
reveal_type(x) # revealed: None | Literal[1]
|
||||
|
||||
match x:
|
||||
case None:
|
||||
y = x
|
||||
y = 0
|
||||
|
||||
reveal_type(y) # revealed: Literal[0] | None
|
||||
match x:
|
||||
case None:
|
||||
y = x
|
||||
|
||||
reveal_type(y) # revealed: Literal[0] | None
|
||||
```
|
||||
|
||||
@@ -3,49 +3,62 @@
|
||||
## After if-else statements, narrowing has no effect if the variable is not mutated in any branch
|
||||
|
||||
```py
|
||||
def _(x: int | None):
|
||||
if x is None:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
reveal_type(x) # revealed: int | None
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int | None
|
||||
```
|
||||
|
||||
## Narrowing can have a persistent effect if the variable is mutated in one branch
|
||||
|
||||
```py
|
||||
def _(x: int | None):
|
||||
if x is None:
|
||||
x = 10
|
||||
else:
|
||||
pass
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 10
|
||||
else:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
## An if statement without an explicit `else` branch is equivalent to one with a no-op `else` branch
|
||||
|
||||
```py
|
||||
def _(x: int | None, y: int | None):
|
||||
if x is None:
|
||||
x = 0
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
if y is None:
|
||||
pass
|
||||
x = optional_int()
|
||||
y = optional_int()
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(y) # revealed: int | None
|
||||
if x is None:
|
||||
x = 0
|
||||
|
||||
if y is None:
|
||||
pass
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
reveal_type(y) # revealed: int | None
|
||||
```
|
||||
|
||||
## An if-elif without an explicit else branch is equivalent to one with an empty else branch
|
||||
|
||||
```py
|
||||
def _(x: int | None):
|
||||
if x is None:
|
||||
x = 0
|
||||
elif x > 50:
|
||||
x = 50
|
||||
def optional_int() -> int | None: ...
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
x = optional_int()
|
||||
|
||||
if x is None:
|
||||
x = 0
|
||||
elif x > 50:
|
||||
x = 50
|
||||
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(x: A | B):
|
||||
if type(x) is A:
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
# It would be wrong to infer `B` here. The type
|
||||
# of `x` could be a subclass of `A`, so we need
|
||||
# to infer the full union type:
|
||||
reveal_type(x) # revealed: A | B
|
||||
def get_a_or_b() -> A | B:
|
||||
return A()
|
||||
|
||||
x = get_a_or_b()
|
||||
|
||||
if type(x) is A:
|
||||
reveal_type(x) # revealed: A
|
||||
else:
|
||||
# It would be wrong to infer `B` here. The type
|
||||
# of `x` could be a subclass of `A`, so we need
|
||||
# to infer the full union type:
|
||||
reveal_type(x) # revealed: A | B
|
||||
```
|
||||
|
||||
## `type(x) is not C`
|
||||
@@ -22,12 +26,16 @@ def _(x: A | B):
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def _(x: A | B):
|
||||
if type(x) is not A:
|
||||
# Same reasoning as above: no narrowing should occur here.
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A
|
||||
def get_a_or_b() -> A | B:
|
||||
return A()
|
||||
|
||||
x = get_a_or_b()
|
||||
|
||||
if type(x) is not A:
|
||||
# Same reasoning as above: no narrowing should occur here.
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A
|
||||
```
|
||||
|
||||
## `type(x) == C`, `type(x) != C`
|
||||
@@ -46,12 +54,16 @@ class IsEqualToEverything(type):
|
||||
class A(metaclass=IsEqualToEverything): ...
|
||||
class B(metaclass=IsEqualToEverything): ...
|
||||
|
||||
def _(x: A | B):
|
||||
if type(x) == A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
def get_a_or_b() -> A | B:
|
||||
return B()
|
||||
|
||||
if type(x) != A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
x = get_a_or_b()
|
||||
|
||||
if type(x) == A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
|
||||
if type(x) != A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
```
|
||||
|
||||
## No narrowing for custom `type` callable
|
||||
@@ -63,11 +75,15 @@ class B: ...
|
||||
def type(x):
|
||||
return int
|
||||
|
||||
def _(x: A | B):
|
||||
if type(x) is A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
def get_a_or_b() -> A | B:
|
||||
return A()
|
||||
|
||||
x = get_a_or_b()
|
||||
|
||||
if type(x) is A:
|
||||
reveal_type(x) # revealed: A | B
|
||||
else:
|
||||
reveal_type(x) # revealed: A | B
|
||||
```
|
||||
|
||||
## No narrowing for multiple arguments
|
||||
@@ -75,11 +91,15 @@ def _(x: A | B):
|
||||
No narrowing should occur if `type` is used to dynamically create a class:
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
if type(x, (), {}) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
else:
|
||||
reveal_type(x) # revealed: str | int
|
||||
def get_str_or_int() -> str | int:
|
||||
return "test"
|
||||
|
||||
x = get_str_or_int()
|
||||
|
||||
if type(x, (), {}) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
else:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
## No narrowing for keyword arguments
|
||||
@@ -87,10 +107,14 @@ def _(x: str | int):
|
||||
`type` can't be used with a keyword argument:
|
||||
|
||||
```py
|
||||
def _(x: str | int):
|
||||
# TODO: we could issue a diagnostic here
|
||||
if type(object=x) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
def get_str_or_int() -> str | int:
|
||||
return "test"
|
||||
|
||||
x = get_str_or_int()
|
||||
|
||||
# TODO: we could issue a diagnostic here
|
||||
if type(object=x) is str:
|
||||
reveal_type(x) # revealed: str | int
|
||||
```
|
||||
|
||||
## Narrowing if `type` is aliased
|
||||
@@ -101,9 +125,13 @@ class B: ...
|
||||
|
||||
alias_for_type = type
|
||||
|
||||
def _(x: A | B):
|
||||
if alias_for_type(x) is A:
|
||||
reveal_type(x) # revealed: A
|
||||
def get_a_or_b() -> A | B:
|
||||
return A()
|
||||
|
||||
x = get_a_or_b()
|
||||
|
||||
if alias_for_type(x) is A:
|
||||
reveal_type(x) # revealed: A
|
||||
```
|
||||
|
||||
## Limitations
|
||||
@@ -112,9 +140,13 @@ def _(x: A | B):
|
||||
class Base: ...
|
||||
class Derived(Base): ...
|
||||
|
||||
def _(x: Base):
|
||||
if type(x) is Base:
|
||||
# Ideally, this could be narrower, but there is now way to
|
||||
# express a constraint like `Base & ~ProperSubtypeOf[Base]`.
|
||||
reveal_type(x) # revealed: Base
|
||||
def get_base() -> Base:
|
||||
return Base()
|
||||
|
||||
x = get_base()
|
||||
|
||||
if type(x) is Base:
|
||||
# Ideally, this could be narrower, but there is now way to
|
||||
# express a constraint like `Base & ~ProperSubtypeOf[Base]`.
|
||||
reveal_type(x) # revealed: Base
|
||||
```
|
||||
|
||||
@@ -59,7 +59,7 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
|
||||
# These come from `builtins.object`, not `types.ModuleType`:
|
||||
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
|
||||
|
||||
reveal_type(typing.__class__) # revealed: Literal[ModuleType]
|
||||
reveal_type(typing.__class__) # revealed: Literal[type]
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties and generics;
|
||||
# should be `dict[str, Any]`
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
Name lookups within a class scope fall back to globals, but lookups of class attributes don't.
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
flag = coinflip()
|
||||
flag = bool_instance()
|
||||
x = 1
|
||||
|
||||
class C:
|
||||
@@ -24,14 +24,14 @@ reveal_type(C.y) # revealed: Literal[1]
|
||||
## Possibly unbound in class and global scope
|
||||
|
||||
```py
|
||||
def coinflip() -> bool:
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
if coinflip():
|
||||
if bool_instance():
|
||||
x = "abc"
|
||||
|
||||
class C:
|
||||
if coinflip():
|
||||
if bool_instance():
|
||||
x = 1
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
## Shadow after incompatible declarations is OK
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
x: bytes = b"foo"
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
x: str
|
||||
else:
|
||||
x: int
|
||||
x: bytes = b"foo"
|
||||
```
|
||||
|
||||
@@ -22,10 +22,12 @@ reveal_type(x) # revealed: Unknown
|
||||
y = b[-6] # error: [index-out-of-bounds] "Index -6 is out of bounds for bytes literal `Literal[b"\x00abc\xff"]` with length 5"
|
||||
reveal_type(y) # revealed: Unknown
|
||||
|
||||
def _(n: int):
|
||||
a = b"abcde"[n]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
def int_instance() -> int:
|
||||
return 42
|
||||
|
||||
a = b"abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
## Slices
|
||||
@@ -41,13 +43,15 @@ b[:4:0] # error: [zero-stepsize-in-slice]
|
||||
b[0::0] # error: [zero-stepsize-in-slice]
|
||||
b[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def _(m: int, n: int):
|
||||
byte_slice1 = b[m:n]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice1) # revealed: @Todo(return type)
|
||||
def int_instance() -> int: ...
|
||||
|
||||
def _(s: bytes) -> bytes:
|
||||
byte_slice2 = s[0:5]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice2) # revealed: @Todo(return type)
|
||||
byte_slice1 = b[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice1) # revealed: @Todo(return type)
|
||||
|
||||
def bytes_instance() -> bytes: ...
|
||||
|
||||
byte_slice2 = bytes_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `bytes`
|
||||
reveal_type(byte_slice2) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
@@ -21,66 +21,77 @@ reveal_type(Identity[0]) # revealed: str
|
||||
## Class getitem union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class UnionClassGetItem:
|
||||
if flag:
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
else:
|
||||
def __class_getitem__(cls, item: int) -> int:
|
||||
return item
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(UnionClassGetItem[0]) # revealed: str | int
|
||||
class UnionClassGetItem:
|
||||
if bool_instance():
|
||||
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
else:
|
||||
|
||||
def __class_getitem__(cls, item: int) -> int:
|
||||
return item
|
||||
|
||||
reveal_type(UnionClassGetItem[0]) # revealed: str | int
|
||||
```
|
||||
|
||||
## Class getitem with class union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class A:
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
class B:
|
||||
def __class_getitem__(cls, item: int) -> int:
|
||||
return item
|
||||
class A:
|
||||
def __class_getitem__(cls, item: int) -> str:
|
||||
return item
|
||||
|
||||
x = A if flag else B
|
||||
class B:
|
||||
def __class_getitem__(cls, item: int) -> int:
|
||||
return item
|
||||
|
||||
reveal_type(x) # revealed: Literal[A, B]
|
||||
reveal_type(x[0]) # revealed: str | int
|
||||
x = A if bool_instance() else B
|
||||
|
||||
reveal_type(x) # revealed: Literal[A, B]
|
||||
reveal_type(x[0]) # revealed: str | int
|
||||
```
|
||||
|
||||
## Class getitem with unbound method union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
class Spam:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
else:
|
||||
class Spam: ...
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound"
|
||||
# revealed: str
|
||||
reveal_type(Spam[42])
|
||||
if bool_instance():
|
||||
class Spam:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
|
||||
else:
|
||||
class Spam: ...
|
||||
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[Spam, Spam]` is possibly unbound"
|
||||
# revealed: str
|
||||
reveal_type(Spam[42])
|
||||
```
|
||||
|
||||
## TODO: Class getitem non-class union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
class Eggs:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
else:
|
||||
Eggs = 1
|
||||
if bool_instance():
|
||||
class Eggs:
|
||||
def __class_getitem__(self, x: int) -> str:
|
||||
return "foo"
|
||||
|
||||
a = Eggs[42] # error: "Cannot subscript object of type `Literal[Eggs] | Literal[1]` with no `__getitem__` method"
|
||||
else:
|
||||
Eggs = 1
|
||||
|
||||
# TODO: should _probably_ emit `str | Unknown`
|
||||
reveal_type(a) # revealed: Unknown
|
||||
a = Eggs[42] # error: "Cannot subscript object of type `Literal[Eggs] | Literal[1]` with no `__getitem__` method"
|
||||
|
||||
# TODO: should _probably_ emit `str | Unknown`
|
||||
reveal_type(a) # revealed: Unknown
|
||||
```
|
||||
|
||||
@@ -30,14 +30,18 @@ reveal_type(Identity()[0]) # revealed: int
|
||||
## Getitem union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Identity:
|
||||
if flag:
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return index
|
||||
else:
|
||||
def __getitem__(self, index: int) -> str:
|
||||
return str(index)
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(Identity()[0]) # revealed: int | str
|
||||
class Identity:
|
||||
if bool_instance():
|
||||
|
||||
def __getitem__(self, index: int) -> int:
|
||||
return index
|
||||
else:
|
||||
|
||||
def __getitem__(self, index: int) -> str:
|
||||
return str(index)
|
||||
|
||||
reveal_type(Identity()[0]) # revealed: int | str
|
||||
```
|
||||
|
||||
@@ -19,10 +19,11 @@ reveal_type(a) # revealed: Unknown
|
||||
b = s[-8] # error: [index-out-of-bounds] "Index -8 is out of bounds for string `Literal["abcde"]` with length 5"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
|
||||
def _(n: int):
|
||||
a = "abcde"[n]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
def int_instance() -> int: ...
|
||||
|
||||
a = "abcde"[int_instance()]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(a) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
## Slices
|
||||
@@ -73,14 +74,17 @@ s[:4:0] # error: [zero-stepsize-in-slice]
|
||||
s[0::0] # error: [zero-stepsize-in-slice]
|
||||
s[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def _(m: int, n: int, s2: str):
|
||||
substring1 = s[m:n]
|
||||
# TODO: Support overloads... Should be `LiteralString`
|
||||
reveal_type(substring1) # revealed: @Todo(return type)
|
||||
def int_instance() -> int: ...
|
||||
|
||||
substring2 = s2[0:5]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(substring2) # revealed: @Todo(return type)
|
||||
substring1 = s[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `LiteralString`
|
||||
reveal_type(substring1) # revealed: @Todo(return type)
|
||||
|
||||
def str_instance() -> str: ...
|
||||
|
||||
substring2 = str_instance()[0:5]
|
||||
# TODO: Support overloads... Should be `str`
|
||||
reveal_type(substring2) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
## Unsupported slice types
|
||||
|
||||
@@ -67,60 +67,9 @@ t[:4:0] # error: [zero-stepsize-in-slice]
|
||||
t[0::0] # error: [zero-stepsize-in-slice]
|
||||
t[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
def _(m: int, n: int):
|
||||
tuple_slice = t[m:n]
|
||||
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||
reveal_type(tuple_slice) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
target-version = "3.9"
|
||||
```
|
||||
|
||||
```py
|
||||
# TODO:
|
||||
# * `tuple.__class_getitem__` is always bound on 3.9 (`sys.version_info`)
|
||||
# * `tuple[int, str]` is a valid base (generics)
|
||||
# error: [call-possibly-unbound-method] "Method `__class_getitem__` of type `Literal[tuple]` is possibly unbound"
|
||||
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class A(tuple[int, str]): ...
|
||||
|
||||
# Runtime value: `(A, tuple, object)`
|
||||
# TODO: Generics
|
||||
reveal_type(A.__mro__) # revealed: tuple[Literal[A], Unknown, Literal[object]]
|
||||
```
|
||||
|
||||
## `typing.Tuple`
|
||||
|
||||
### Correspondence with `tuple`
|
||||
|
||||
`typing.Tuple` can be used interchangeably with `tuple`:
|
||||
|
||||
```py
|
||||
from typing import Tuple
|
||||
|
||||
class A: ...
|
||||
|
||||
def _(c: Tuple, d: Tuple[int, A], e: Tuple[Any, ...]):
|
||||
reveal_type(c) # revealed: tuple
|
||||
reveal_type(d) # revealed: tuple[int, A]
|
||||
reveal_type(e) # revealed: @Todo(full tuple[...] support)
|
||||
```
|
||||
|
||||
### Inheritance
|
||||
|
||||
Inheriting from `Tuple` results in a MRO with `builtins.tuple` and `typing.Generic`. `Tuple` itself
|
||||
is not a class.
|
||||
|
||||
```py
|
||||
from typing import Tuple
|
||||
|
||||
class C(Tuple): ...
|
||||
|
||||
# Runtime value: `(C, tuple, typing.Generic, object)`
|
||||
# TODO: Add `Generic` to the MRO
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[tuple], Unknown, Literal[object]]
|
||||
def int_instance() -> int: ...
|
||||
|
||||
tuple_slice = t[int_instance() : int_instance()]
|
||||
# TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]`
|
||||
reveal_type(tuple_slice) # revealed: @Todo(return type)
|
||||
```
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# type[Any]
|
||||
|
||||
## Simple
|
||||
|
||||
```py
|
||||
def f(x: type[Any]):
|
||||
reveal_type(x) # revealed: type[Any]
|
||||
# TODO: could be `<object.__repr__ type> & Any`
|
||||
reveal_type(x.__repr__) # revealed: Any
|
||||
|
||||
class A: ...
|
||||
|
||||
x: type[Any] = object
|
||||
x: type[Any] = type
|
||||
x: type[Any] = A
|
||||
x: type[Any] = A() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## Bare type
|
||||
|
||||
The interpretation of bare `type` is not clear: existing wording in the spec does not match the
|
||||
behavior of mypy or pyright. For now we interpret it as simply "an instance of `builtins.type`",
|
||||
which is equivalent to `type[object]`. This is similar to the current behavior of mypy, and pyright
|
||||
in strict mode.
|
||||
|
||||
```py
|
||||
def f(x: type):
|
||||
reveal_type(x) # revealed: type
|
||||
reveal_type(x.__repr__) # revealed: @Todo(instance attributes)
|
||||
|
||||
class A: ...
|
||||
|
||||
x: type = object
|
||||
x: type = type
|
||||
x: type = A
|
||||
x: type = A() # error: [invalid-assignment]
|
||||
```
|
||||
|
||||
## type[object] != type[Any]
|
||||
|
||||
```py
|
||||
def f(x: type[object]):
|
||||
reveal_type(x) # revealed: type[object]
|
||||
# TODO: bound method types
|
||||
reveal_type(x.__repr__) # revealed: Literal[__repr__]
|
||||
|
||||
class A: ...
|
||||
|
||||
x: type[object] = object
|
||||
x: type[object] = type
|
||||
x: type[object] = A
|
||||
x: type[object] = A() # error: [invalid-assignment]
|
||||
```
|
||||
@@ -5,8 +5,10 @@
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def _(c: type[A]):
|
||||
reveal_type(c) # revealed: type[A]
|
||||
def f() -> type[A]:
|
||||
return A
|
||||
|
||||
reveal_type(f()) # revealed: type[A]
|
||||
```
|
||||
|
||||
## Nested class literal
|
||||
@@ -15,8 +17,10 @@ def _(c: type[A]):
|
||||
class A:
|
||||
class B: ...
|
||||
|
||||
def f(c: type[A.B]):
|
||||
reveal_type(c) # revealed: type[B]
|
||||
def f() -> type[A.B]:
|
||||
return A.B
|
||||
|
||||
reveal_type(f()) # revealed: type[B]
|
||||
```
|
||||
|
||||
## Deeply nested class literal
|
||||
@@ -26,8 +30,10 @@ class A:
|
||||
class B:
|
||||
class C: ...
|
||||
|
||||
def f(c: type[A.B.C]):
|
||||
reveal_type(c) # revealed: type[C]
|
||||
def f() -> type[A.B.C]:
|
||||
return A.B.C
|
||||
|
||||
reveal_type(f()) # revealed: type[C]
|
||||
```
|
||||
|
||||
## Class literal from another module
|
||||
@@ -35,8 +41,10 @@ def f(c: type[A.B.C]):
|
||||
```py
|
||||
from a import A
|
||||
|
||||
def f(c: type[A]):
|
||||
reveal_type(c) # revealed: type[A]
|
||||
def f() -> type[A]:
|
||||
return A
|
||||
|
||||
reveal_type(f()) # revealed: type[A]
|
||||
```
|
||||
|
||||
```py path=a.py
|
||||
@@ -48,8 +56,10 @@ class A: ...
|
||||
```py
|
||||
import a
|
||||
|
||||
def f(c: type[a.B]):
|
||||
reveal_type(c) # revealed: type[B]
|
||||
def f() -> type[a.B]:
|
||||
return a.B
|
||||
|
||||
reveal_type(f()) # revealed: type[B]
|
||||
```
|
||||
|
||||
```py path=a.py
|
||||
@@ -63,8 +73,12 @@ import a.b
|
||||
|
||||
# TODO: no diagnostic
|
||||
# error: [unresolved-attribute]
|
||||
def f(c: type[a.b.C]):
|
||||
reveal_type(c) # revealed: @Todo(unsupported type[X] special form)
|
||||
def f() -> type[a.b.C]:
|
||||
# TODO: no diagnostic
|
||||
# error: [unresolved-attribute]
|
||||
return a.b.C
|
||||
|
||||
reveal_type(f()) # revealed: @Todo(unsupported type[X] special form)
|
||||
```
|
||||
|
||||
```py path=a/__init__.py
|
||||
@@ -74,7 +88,7 @@ def f(c: type[a.b.C]):
|
||||
class C: ...
|
||||
```
|
||||
|
||||
## New-style union of classes
|
||||
## Union of classes
|
||||
|
||||
```py
|
||||
class BasicUser: ...
|
||||
@@ -84,44 +98,11 @@ class A:
|
||||
class B:
|
||||
class C: ...
|
||||
|
||||
def _(u: type[BasicUser | ProUser | A.B.C]):
|
||||
# revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(u)
|
||||
```
|
||||
def get_user() -> type[BasicUser | ProUser | A.B.C]:
|
||||
return BasicUser
|
||||
|
||||
## Old-style union of classes
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
class BasicUser: ...
|
||||
class ProUser: ...
|
||||
|
||||
class A:
|
||||
class B:
|
||||
class C: ...
|
||||
|
||||
def f(a: type[Union[BasicUser, ProUser, A.B.C]], b: type[Union[str]], c: type[Union[BasicUser, Union[ProUser, A.B.C]]]):
|
||||
reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(b) # revealed: type[str]
|
||||
reveal_type(c) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
```
|
||||
|
||||
## New-style and old-style unions in combination
|
||||
|
||||
```py
|
||||
from typing import Union
|
||||
|
||||
class BasicUser: ...
|
||||
class ProUser: ...
|
||||
|
||||
class A:
|
||||
class B:
|
||||
class C: ...
|
||||
|
||||
def f(a: type[BasicUser | Union[ProUser, A.B.C]], b: type[Union[BasicUser | Union[ProUser, A.B.C | str]]]):
|
||||
reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(b) # revealed: type[BasicUser] | type[ProUser] | type[C] | type[str]
|
||||
# revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(get_user())
|
||||
```
|
||||
|
||||
## Illegal parameters
|
||||
@@ -131,16 +112,6 @@ class A: ...
|
||||
class B: ...
|
||||
|
||||
# error: [invalid-type-form]
|
||||
_: type[A, B]
|
||||
```
|
||||
|
||||
## As a base class
|
||||
|
||||
```py
|
||||
# TODO: this is a false positive
|
||||
# error: [invalid-base] "Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)"
|
||||
class Foo(type[int]): ...
|
||||
|
||||
# TODO: should be `tuple[Literal[Foo], Literal[type], Literal[object]]
|
||||
reveal_type(Foo.__mro__) # revealed: tuple[Literal[Foo], Unknown, Literal[object]]
|
||||
def get_user() -> type[A, B]:
|
||||
return A
|
||||
```
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# `typing.Type`
|
||||
|
||||
## Annotation
|
||||
|
||||
`typing.Type` can be used interchangeably with `type`:
|
||||
|
||||
```py
|
||||
from typing import Type
|
||||
|
||||
class A: ...
|
||||
|
||||
def _(c: Type, d: Type[A], e: Type[A]):
|
||||
reveal_type(c) # revealed: type
|
||||
reveal_type(d) # revealed: type[A]
|
||||
c = d # fine
|
||||
d = c # fine
|
||||
```
|
||||
|
||||
## Inheritance
|
||||
|
||||
Inheriting from `Type` results in a MRO with `builtins.type` and `typing.Generic`. `Type` itself is
|
||||
not a class.
|
||||
|
||||
```py
|
||||
from typing import Type
|
||||
|
||||
class C(Type): ...
|
||||
|
||||
# Runtime value: `(C, type, typing.Generic, object)`
|
||||
# TODO: Add `Generic` to the MRO
|
||||
reveal_type(C.__mro__) # revealed: tuple[Literal[C], Literal[type], Literal[object]]
|
||||
```
|
||||
@@ -35,25 +35,29 @@ y = 1
|
||||
## Union
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
if flag:
|
||||
p = 1
|
||||
q = 3.3
|
||||
r = "hello"
|
||||
s = "world"
|
||||
t = 0
|
||||
else:
|
||||
p = "hello"
|
||||
q = 4
|
||||
r = ""
|
||||
s = 0
|
||||
t = ""
|
||||
def bool_instance() -> bool:
|
||||
return True
|
||||
|
||||
reveal_type(not p) # revealed: Literal[False]
|
||||
reveal_type(not q) # revealed: bool
|
||||
reveal_type(not r) # revealed: bool
|
||||
reveal_type(not s) # revealed: bool
|
||||
reveal_type(not t) # revealed: Literal[True]
|
||||
flag = bool_instance()
|
||||
|
||||
if flag:
|
||||
p = 1
|
||||
q = 3.3
|
||||
r = "hello"
|
||||
s = "world"
|
||||
t = 0
|
||||
else:
|
||||
p = "hello"
|
||||
q = 4
|
||||
r = ""
|
||||
s = 0
|
||||
t = ""
|
||||
|
||||
reveal_type(not p) # revealed: Literal[False]
|
||||
reveal_type(not q) # revealed: bool
|
||||
reveal_type(not r) # revealed: bool
|
||||
reveal_type(not s) # revealed: bool
|
||||
reveal_type(not t) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Integer literal
|
||||
|
||||
@@ -21,23 +21,25 @@ with Manager() as f:
|
||||
## Union context manager
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
class Manager2:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
class Manager2:
|
||||
def __enter__(self) -> int:
|
||||
return 42
|
||||
|
||||
context_expr = Manager1() if flag else Manager2()
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str | int
|
||||
context_expr = Manager1() if coinflip() else Manager2()
|
||||
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str | int
|
||||
```
|
||||
|
||||
## Context manager without an `__enter__` or `__exit__` method
|
||||
@@ -101,34 +103,39 @@ with Manager():
|
||||
## Context expression with possibly-unbound union variants
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
class Manager1:
|
||||
def __enter__(self) -> str:
|
||||
return "foo"
|
||||
|
||||
class NotAContextManager: ...
|
||||
context_expr = Manager1() if flag else NotAContextManager()
|
||||
def __exit__(self, exc_type, exc_value, traceback): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__exit__` is possibly unbound"
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str
|
||||
class NotAContextManager: ...
|
||||
|
||||
context_expr = Manager1() if coinflip() else NotAContextManager()
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
# error: [invalid-context-manager] "Object of type `Manager1 | NotAContextManager` cannot be used with `with` because the method `__exit__` is possibly unbound"
|
||||
with context_expr as f:
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
## Context expression with "sometimes" callable `__enter__` method
|
||||
|
||||
```py
|
||||
def _(flag: bool):
|
||||
class Manager:
|
||||
if flag:
|
||||
def __enter__(self) -> str:
|
||||
return "abcd"
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
def __exit__(self, *args): ...
|
||||
class Manager:
|
||||
if coinflip():
|
||||
def __enter__(self) -> str:
|
||||
return "abcd"
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
with Manager() as f:
|
||||
reveal_type(f) # revealed: str
|
||||
def __exit__(self, *args): ...
|
||||
|
||||
# error: [invalid-context-manager] "Object of type `Manager` cannot be used with `with` because the method `__enter__` is possibly unbound"
|
||||
with Manager() as f:
|
||||
reveal_type(f) # revealed: str
|
||||
```
|
||||
|
||||
@@ -29,14 +29,12 @@ mod util;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Creates a new registry with all known semantic lints.
|
||||
pub fn default_lint_registry() -> LintRegistry {
|
||||
let mut registry = LintRegistryBuilder::default();
|
||||
register_lints(&mut registry);
|
||||
register_semantic_lints(&mut registry);
|
||||
registry.build()
|
||||
}
|
||||
|
||||
/// Register all known semantic lints.
|
||||
pub fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
types::register_lints(registry);
|
||||
pub fn register_semantic_lints(registry: &mut LintRegistryBuilder) {
|
||||
types::register_type_lints(registry);
|
||||
}
|
||||
|
||||
@@ -23,11 +23,8 @@ pub struct LintMetadata {
|
||||
|
||||
pub status: LintStatus,
|
||||
|
||||
/// The source file in which the lint is declared.
|
||||
pub file: &'static str,
|
||||
|
||||
/// The 1-based line number in the source `file` where the lint is declared.
|
||||
pub line: u32,
|
||||
/// Location where this lint is declared: `file_name:line`
|
||||
pub source: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
@@ -77,7 +74,7 @@ impl LintMetadata {
|
||||
self.summary
|
||||
}
|
||||
|
||||
/// Returns the documentation line by line with one leading space and all trailing whitespace removed.
|
||||
/// Returns the documentation line by line with leading and trailing whitespace removed.
|
||||
pub fn documentation_lines(&self) -> impl Iterator<Item = &str> {
|
||||
self.raw_documentation
|
||||
.lines()
|
||||
@@ -97,12 +94,8 @@ impl LintMetadata {
|
||||
&self.status
|
||||
}
|
||||
|
||||
pub fn file(&self) -> &str {
|
||||
self.file
|
||||
}
|
||||
|
||||
pub fn line(&self) -> u32 {
|
||||
self.line
|
||||
pub fn source(&self) -> &str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,43 +107,30 @@ pub const fn lint_metadata_defaults() -> LintMetadata {
|
||||
raw_documentation: "",
|
||||
default_level: Level::Error,
|
||||
status: LintStatus::preview("0.0.0"),
|
||||
file: "",
|
||||
line: 1,
|
||||
source: "",
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum LintStatus {
|
||||
/// The lint has been added to the linter, but is not yet stable.
|
||||
/// The rule has been added to the linter, but is not yet stable.
|
||||
Preview {
|
||||
/// The version in which the lint was added.
|
||||
/// When the rule was added to preview
|
||||
since: &'static str,
|
||||
},
|
||||
|
||||
/// The lint is stable.
|
||||
Stable {
|
||||
/// The version in which the lint was stabilized.
|
||||
since: &'static str,
|
||||
},
|
||||
/// Stable rule that was added in the version defined by `since`.
|
||||
Stable { since: &'static str },
|
||||
|
||||
/// The lint is deprecated and no longer recommended for use.
|
||||
/// The rule has been deprecated [`since`] (version) and will be removed in the future.
|
||||
Deprecated {
|
||||
/// The version in which the lint was deprecated.
|
||||
since: &'static str,
|
||||
|
||||
/// The reason why the lint has been deprecated.
|
||||
///
|
||||
/// This should explain why the lint has been deprecated and if there's a replacement lint that users
|
||||
/// can use instead.
|
||||
reason: &'static str,
|
||||
},
|
||||
|
||||
/// The lint has been removed and can no longer be used.
|
||||
/// The rule has been removed [`since`] (version) and using it will result in an error.
|
||||
Removed {
|
||||
/// The version in which the lint was removed.
|
||||
since: &'static str,
|
||||
|
||||
/// The reason why the lint has been removed.
|
||||
reason: &'static str,
|
||||
},
|
||||
}
|
||||
@@ -177,31 +157,6 @@ impl LintStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Declares a lint rule with the given metadata.
|
||||
///
|
||||
/// ```rust
|
||||
/// use red_knot_python_semantic::declare_lint;
|
||||
/// use red_knot_python_semantic::lint::{LintStatus, Level};
|
||||
///
|
||||
/// declare_lint! {
|
||||
/// /// ## What it does
|
||||
/// /// Checks for references to names that are not defined.
|
||||
/// ///
|
||||
/// /// ## Why is this bad?
|
||||
/// /// Using an undefined variable will raise a `NameError` at runtime.
|
||||
/// ///
|
||||
/// /// ## Example
|
||||
/// ///
|
||||
/// /// ```python
|
||||
/// /// print(x) # NameError: name 'x' is not defined
|
||||
/// /// ```
|
||||
/// pub(crate) static UNRESOLVED_REFERENCE = {
|
||||
/// summary: "detects references to names that are not defined",
|
||||
/// status: LintStatus::preview("1.0.0"),
|
||||
/// default_level: Level::Warn,
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! declare_lint {
|
||||
(
|
||||
@@ -220,8 +175,7 @@ macro_rules! declare_lint {
|
||||
summary: $summary,
|
||||
raw_documentation: concat!($($doc,)+ "\n"),
|
||||
status: $status,
|
||||
file: file!(),
|
||||
line: line!(),
|
||||
source: concat!(file!(), ":", line!()),
|
||||
$( $key: $value, )*
|
||||
..$crate::lint::lint_metadata_defaults()
|
||||
};
|
||||
@@ -352,7 +306,7 @@ impl LintRegistry {
|
||||
/// Returns an iterator over all known aliases and to their target lints.
|
||||
///
|
||||
/// This iterator includes aliases that point to removed lints.
|
||||
pub fn aliases(&self) -> impl Iterator<Item = (LintName, LintId)> + '_ {
|
||||
pub fn aliases(&self) -> impl Iterator<Item = (LintName, LintId)> + use<'_> {
|
||||
self.by_name.iter().filter_map(|(key, value)| {
|
||||
if let LintEntry::Alias(alias) = value {
|
||||
Some((LintName::of(key), *alias))
|
||||
@@ -363,7 +317,7 @@ impl LintRegistry {
|
||||
}
|
||||
|
||||
/// Iterates over all removed lints.
|
||||
pub fn removed(&self) -> impl Iterator<Item = LintId> + '_ {
|
||||
pub fn removed(&self) -> impl Iterator<Item = LintId> + use<'_> {
|
||||
self.by_name.iter().filter_map(|(_, value)| {
|
||||
if let LintEntry::Removed(metadata) = value {
|
||||
Some(*metadata)
|
||||
@@ -430,12 +384,12 @@ impl RuleSelection {
|
||||
}
|
||||
|
||||
/// Returns an iterator over all enabled lints.
|
||||
pub fn enabled(&self) -> impl Iterator<Item = LintId> + '_ {
|
||||
pub fn enabled(&self) -> impl Iterator<Item = LintId> + use<'_> {
|
||||
self.lints.keys().copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all enabled lints and their severity.
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LintId, Severity)> + '_ {
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LintId, Severity)> + use<'_> {
|
||||
self.lints.iter().map(|(&lint, &severity)| (lint, severity))
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
//! The **declared type** represents the code author's declaration (usually through a type
|
||||
//! annotation) that a given variable should not be assigned any type outside the declared type. In
|
||||
//! our model, declared types are also control-flow-sensitive; we allow the code author to
|
||||
//! explicitly redeclare the same variable with a different type. So for a given binding of a
|
||||
//! explicitly re-declare the same variable with a different type. So for a given binding of a
|
||||
//! variable, we will want to ask which declarations of that variable can reach that binding, in
|
||||
//! order to determine whether the binding is permitted, or should be a type error. For example:
|
||||
//!
|
||||
@@ -62,7 +62,7 @@
|
||||
//! assignable to `str`. This is the purpose of declared types: they prevent accidental assignment
|
||||
//! of the wrong type to a variable.
|
||||
//!
|
||||
//! But in some cases it is useful to "shadow" or "redeclare" a variable with a new type, and we
|
||||
//! But in some cases it is useful to "shadow" or "re-declare" a variable with a new type, and we
|
||||
//! permit this, as long as it is done with an explicit re-annotation. So `path: Path =
|
||||
//! Path(path)`, with the explicit `: Path` annotation, is permitted.
|
||||
//!
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::hash::Hash;
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use ruff_db::diagnostic::Severity;
|
||||
use ruff_db::diagnostic::{DiagnosticId, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::diagnostic::register_lints;
|
||||
pub(crate) use self::diagnostic::register_type_lints;
|
||||
pub use self::diagnostic::{TypeCheckDiagnostic, TypeCheckDiagnostics};
|
||||
pub(crate) use self::display::TypeArrayDisplay;
|
||||
pub(crate) use self::infer::{
|
||||
@@ -26,14 +26,12 @@ use crate::stdlib::{
|
||||
builtins_symbol, core_module_symbol, typing_extensions_symbol, CoreStdlibModule,
|
||||
};
|
||||
use crate::symbol::{Boundness, Symbol};
|
||||
use crate::types::call::{CallDunderResult, CallOutcome};
|
||||
use crate::types::diagnostic::TypeCheckDiagnosticsBuilder;
|
||||
use crate::types::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
|
||||
use crate::types::mro::{ClassBase, Mro, MroError, MroIterator};
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
use crate::{Db, FxOrderSet, Module, Program, PythonVersion};
|
||||
|
||||
mod builder;
|
||||
mod call;
|
||||
mod diagnostic;
|
||||
mod display;
|
||||
mod infer;
|
||||
@@ -228,6 +226,18 @@ fn definition_expression_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the type of an expression from an arbitrary scope.
|
||||
///
|
||||
/// Can cause query cycles if used carelessly; caller must be sure that type inference isn't
|
||||
/// currently in progress for the expression's scope.
|
||||
fn expression_ty<'db>(db: &'db dyn Db, file: File, expression: &ast::Expr) -> Type<'db> {
|
||||
let index = semantic_index(db, file);
|
||||
let file_scope = index.expression_scope_id(expression);
|
||||
let scope = file_scope.to_scope_id(db, file);
|
||||
let expr_id = expression.scoped_expression_id(db, scope);
|
||||
infer_scope_types(db, scope).expression_ty(expr_id)
|
||||
}
|
||||
|
||||
/// Infer the combined type of an iterator of bindings.
|
||||
///
|
||||
/// Will return a union if there is more than one binding.
|
||||
@@ -563,11 +573,7 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
|
||||
pub const fn subclass_of(class: Class<'db>) -> Self {
|
||||
Self::subclass_of_base(ClassBase::Class(class))
|
||||
}
|
||||
|
||||
pub const fn subclass_of_base(base: ClassBase<'db>) -> Self {
|
||||
Self::SubclassOf(SubclassOfType { base })
|
||||
Self::SubclassOf(SubclassOfType { class })
|
||||
}
|
||||
|
||||
pub fn string_literal(db: &'db dyn Db, string: &str) -> Self {
|
||||
@@ -612,6 +618,8 @@ impl<'db> Type<'db> {
|
||||
return false;
|
||||
}
|
||||
match (self, target) {
|
||||
(Type::Unknown | Type::Any | Type::Todo(_), _) => false,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo(_)) => false,
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
(_, Type::Instance(InstanceType { class }))
|
||||
@@ -654,31 +662,19 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
)
|
||||
}
|
||||
(Type::ClassLiteral(..), Type::Instance(InstanceType { class }))
|
||||
if class.is_known(db, KnownClass::Type) =>
|
||||
{
|
||||
true
|
||||
}
|
||||
(Type::ClassLiteral(self_class), Type::SubclassOf(target_class)) => {
|
||||
self_class.class.is_subclass_of_base(db, target_class.base)
|
||||
self_class.class.is_subclass_of(db, target_class.class)
|
||||
}
|
||||
(Type::SubclassOf(self_class), Type::SubclassOf(target_class)) => {
|
||||
self_class.class.is_subclass_of(db, target_class.class)
|
||||
}
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::SubclassOf(target_class),
|
||||
) if self_class.is_known(db, KnownClass::Type) => {
|
||||
self_class.is_subclass_of_base(db, target_class.base)
|
||||
}
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(target_class),
|
||||
}),
|
||||
) => self_class.is_subclass_of(db, target_class),
|
||||
// C ⊆ type
|
||||
// type[C] ⊆ type
|
||||
// Though note that this works regardless of which metaclass C has, not just for type.
|
||||
(
|
||||
Type::ClassLiteral(ClassLiteralType { class: self_class })
|
||||
| Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(self_class),
|
||||
}),
|
||||
Type::SubclassOf(SubclassOfType { class: self_class }),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
}),
|
||||
@@ -775,30 +771,6 @@ impl<'db> Type<'db> {
|
||||
},
|
||||
)
|
||||
}
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any,
|
||||
}),
|
||||
Type::SubclassOf(_),
|
||||
) => true,
|
||||
(
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any,
|
||||
}),
|
||||
Type::Instance(target),
|
||||
) if target.class.is_known(db, KnownClass::Type) => true,
|
||||
(
|
||||
Type::Instance(class),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any,
|
||||
}),
|
||||
) if class.class.is_known(db, KnownClass::Type) => true,
|
||||
(
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Any,
|
||||
}),
|
||||
) => true,
|
||||
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
|
||||
_ => self.is_subtype_of(db, target),
|
||||
}
|
||||
@@ -814,56 +786,27 @@ impl<'db> Type<'db> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
if let (
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType {
|
||||
class: target_class,
|
||||
}),
|
||||
) = (self, other)
|
||||
{
|
||||
let self_known = self_class.known(db);
|
||||
if matches!(
|
||||
self_known,
|
||||
Some(KnownClass::NoneType | KnownClass::NoDefaultType)
|
||||
) && self_known == target_class.known(db)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// type[object] ≡ type
|
||||
if let (
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(object_class),
|
||||
}),
|
||||
Type::Instance(InstanceType { class: type_class }),
|
||||
)
|
||||
| (
|
||||
Type::Instance(InstanceType { class: type_class }),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(object_class),
|
||||
}),
|
||||
) = (self, other)
|
||||
{
|
||||
// This is the only case where "instance of a class" is equivalent to "subclass of a
|
||||
// class", so we don't need to fall through if we're not looking at instance[type] and
|
||||
// type[object] specifically.
|
||||
return object_class.is_known(db, KnownClass::Object)
|
||||
&& type_class.is_known(db, KnownClass::Type);
|
||||
}
|
||||
|
||||
// TODO equivalent but not identical structural types, differently-ordered unions and
|
||||
// intersections, other cases?
|
||||
|
||||
// TODO: Once we have support for final classes, we can establish that
|
||||
// `Type::SubclassOf('FinalClass')` is equivalent to `Type::ClassLiteral('FinalClass')`.
|
||||
|
||||
// For all other cases, types are equivalent iff they have the same internal
|
||||
// representation.
|
||||
// TODO: The following is a workaround that is required to unify the two different versions
|
||||
// of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once
|
||||
// we understand `sys.version_info` branches.
|
||||
self == other
|
||||
|| matches!((self, other),
|
||||
(
|
||||
Type::Instance(InstanceType { class: self_class }),
|
||||
Type::Instance(InstanceType { class: target_class })
|
||||
)
|
||||
if {
|
||||
let self_known = self_class.known(db);
|
||||
matches!(self_known, Some(KnownClass::NoneType | KnownClass::NoDefaultType))
|
||||
&& self_known == target_class.known(db)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if both `self` and `other` are the same gradual form
|
||||
@@ -930,7 +873,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
(Type::SubclassOf(type_class), Type::ClassLiteral(class_literal))
|
||||
| (Type::ClassLiteral(class_literal), Type::SubclassOf(type_class)) => {
|
||||
!class_literal.class.is_subclass_of_base(db, type_class.base)
|
||||
!class_literal.class.is_subclass_of(db, type_class.class)
|
||||
}
|
||||
(Type::SubclassOf(_), Type::SubclassOf(_)) => false,
|
||||
(Type::SubclassOf(_), Type::Instance(_)) | (Type::Instance(_), Type::SubclassOf(_)) => {
|
||||
@@ -1097,8 +1040,7 @@ impl<'db> Type<'db> {
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::KnownInstance(_) => true,
|
||||
Type::SubclassOf(SubclassOfType { base }) => matches!(base, ClassBase::Class(_)),
|
||||
Type::ClassLiteral(_) | Type::Instance(_) => {
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_) | Type::Instance(_) => {
|
||||
// TODO: Ideally, we would iterate over the MRO of the class, check if all
|
||||
// bases are fully static, and only return `true` if that is the case.
|
||||
//
|
||||
@@ -1244,8 +1186,6 @@ impl<'db> Type<'db> {
|
||||
| KnownClass::Set
|
||||
| KnownClass::Dict
|
||||
| KnownClass::Slice
|
||||
| KnownClass::BaseException
|
||||
| KnownClass::BaseExceptionGroup
|
||||
| KnownClass::GenericAlias
|
||||
| KnownClass::ModuleType
|
||||
| KnownClass::FunctionType
|
||||
@@ -1272,10 +1212,6 @@ impl<'db> Type<'db> {
|
||||
/// as accessed from instances of the `Bar` class.
|
||||
#[must_use]
|
||||
pub(crate) fn member(&self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
||||
if name == "__class__" {
|
||||
return self.to_meta_type(db).into();
|
||||
}
|
||||
|
||||
match self {
|
||||
Type::Any => Type::Any.into(),
|
||||
Type::Never => {
|
||||
@@ -1664,6 +1600,12 @@ impl<'db> Type<'db> {
|
||||
};
|
||||
}
|
||||
|
||||
if matches!(self, Type::Unknown | Type::Any | Type::Todo(_)) {
|
||||
// Explicit handling of `Unknown` and `Any` necessary until `type[Unknown]` and
|
||||
// `type[Any]` are not defined as `Todo` anymore.
|
||||
return IterationOutcome::Iterable { element_ty: self };
|
||||
}
|
||||
|
||||
let dunder_iter_result = self.call_dunder(db, "__iter__", &[self]);
|
||||
match dunder_iter_result {
|
||||
CallDunderResult::CallOutcome(ref call_outcome)
|
||||
@@ -1721,10 +1663,7 @@ impl<'db> Type<'db> {
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Never => Type::Never,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class),
|
||||
}) => Type::instance(*class),
|
||||
Type::SubclassOf(_) => Type::Any,
|
||||
Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class),
|
||||
Type::Union(union) => union.map(db, |element| element.to_instance(db)),
|
||||
// TODO: we can probably do better here: --Alex
|
||||
Type::Intersection(_) => todo_type!(),
|
||||
@@ -1753,12 +1692,7 @@ impl<'db> Type<'db> {
|
||||
#[must_use]
|
||||
pub fn in_type_expression(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
|
||||
// equivalent to `type[object]`.
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_) => self.to_instance(db),
|
||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||
Type::KnownInstance(KnownInstanceType::Type) => KnownClass::Type.to_instance(db),
|
||||
Type::KnownInstance(KnownInstanceType::Tuple) => KnownClass::Tuple.to_instance(db),
|
||||
Type::Union(union) => union.map(db, |element| element.in_type_expression(db)),
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO map this to a new `Type::TypeVar` variant
|
||||
@@ -1817,7 +1751,9 @@ impl<'db> Type<'db> {
|
||||
pub fn to_meta_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
match self {
|
||||
Type::Never => Type::Never,
|
||||
Type::Instance(InstanceType { class }) => Type::subclass_of(*class),
|
||||
Type::Instance(InstanceType { class }) => {
|
||||
Type::SubclassOf(SubclassOfType { class: *class })
|
||||
}
|
||||
Type::KnownInstance(known_instance) => known_instance.class().to_class_literal(db),
|
||||
Type::Union(union) => union.map(db, |ty| ty.to_meta_type(db)),
|
||||
Type::BooleanLiteral(_) => KnownClass::Bool.to_class_literal(db),
|
||||
@@ -1828,9 +1764,7 @@ impl<'db> Type<'db> {
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => class.metaclass(db),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class),
|
||||
}) => Type::subclass_of(
|
||||
Type::SubclassOf(SubclassOfType { class }) => Type::subclass_of(
|
||||
class
|
||||
.try_metaclass(db)
|
||||
.ok()
|
||||
@@ -1838,9 +1772,10 @@ impl<'db> Type<'db> {
|
||||
.unwrap_or_else(|| KnownClass::Type.to_class_literal(db).expect_class_literal())
|
||||
.class,
|
||||
),
|
||||
Type::SubclassOf(_) => Type::Any,
|
||||
Type::StringLiteral(_) | Type::LiteralString => KnownClass::Str.to_class_literal(db),
|
||||
// TODO: `type[Any]`?
|
||||
Type::Any => Type::Any,
|
||||
// TODO: `type[Unknown]`?
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO intersections
|
||||
Type::Intersection(_) => todo_type!(),
|
||||
@@ -1923,8 +1858,6 @@ pub enum KnownClass {
|
||||
Set,
|
||||
Dict,
|
||||
Slice,
|
||||
BaseException,
|
||||
BaseExceptionGroup,
|
||||
// Types
|
||||
GenericAlias,
|
||||
ModuleType,
|
||||
@@ -1955,8 +1888,6 @@ impl<'db> KnownClass {
|
||||
Self::List => "list",
|
||||
Self::Type => "type",
|
||||
Self::Slice => "slice",
|
||||
Self::BaseException => "BaseException",
|
||||
Self::BaseExceptionGroup => "BaseExceptionGroup",
|
||||
Self::GenericAlias => "GenericAlias",
|
||||
Self::ModuleType => "ModuleType",
|
||||
Self::FunctionType => "FunctionType",
|
||||
@@ -1984,12 +1915,6 @@ impl<'db> KnownClass {
|
||||
.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
pub fn to_subclass_of(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_literal()
|
||||
.map(|ClassLiteralType { class }| Type::subclass_of(class))
|
||||
}
|
||||
|
||||
/// Return the module in which we should look up the definition for this class
|
||||
pub(crate) fn canonical_module(self, db: &'db dyn Db) -> CoreStdlibModule {
|
||||
match self {
|
||||
@@ -2004,8 +1929,6 @@ impl<'db> KnownClass {
|
||||
| Self::Tuple
|
||||
| Self::Set
|
||||
| Self::Dict
|
||||
| Self::BaseException
|
||||
| Self::BaseExceptionGroup
|
||||
| Self::Slice => CoreStdlibModule::Builtins,
|
||||
Self::VersionInfo => CoreStdlibModule::Sys,
|
||||
Self::GenericAlias | Self::ModuleType | Self::FunctionType => CoreStdlibModule::Types,
|
||||
@@ -2049,8 +1972,6 @@ impl<'db> KnownClass {
|
||||
| Self::ModuleType
|
||||
| Self::FunctionType
|
||||
| Self::SpecialForm
|
||||
| Self::BaseException
|
||||
| Self::BaseExceptionGroup
|
||||
| Self::TypeVar => false,
|
||||
}
|
||||
}
|
||||
@@ -2072,8 +1993,6 @@ impl<'db> KnownClass {
|
||||
"dict" => Self::Dict,
|
||||
"list" => Self::List,
|
||||
"slice" => Self::Slice,
|
||||
"BaseException" => Self::BaseException,
|
||||
"BaseExceptionGroup" => Self::BaseExceptionGroup,
|
||||
"GenericAlias" => Self::GenericAlias,
|
||||
"NoneType" => Self::NoneType,
|
||||
"ModuleType" => Self::ModuleType,
|
||||
@@ -2110,8 +2029,6 @@ impl<'db> KnownClass {
|
||||
| Self::GenericAlias
|
||||
| Self::ModuleType
|
||||
| Self::VersionInfo
|
||||
| Self::BaseException
|
||||
| Self::BaseExceptionGroup
|
||||
| Self::FunctionType => module.name() == self.canonical_module(db).as_str(),
|
||||
Self::NoneType => matches!(module.name().as_str(), "_typeshed" | "types"),
|
||||
Self::SpecialForm | Self::TypeVar | Self::TypeAliasType | Self::NoDefaultType => {
|
||||
@@ -2138,10 +2055,6 @@ pub enum KnownInstanceType<'db> {
|
||||
Never,
|
||||
/// The symbol `typing.Any` (which can also be found as `typing_extensions.Any`)
|
||||
Any,
|
||||
/// The symbol `typing.Tuple` (which can also be found as `typing_extensions.Tuple`)
|
||||
Tuple,
|
||||
/// The symbol `typing.Type` (which can also be found as `typing_extensions.Type`)
|
||||
Type,
|
||||
/// A single instance of `typing.TypeVar`
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
/// A single instance of `typing.TypeAliasType` (PEP 695 type alias)
|
||||
@@ -2160,8 +2073,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
Self::NoReturn => "NoReturn",
|
||||
Self::Never => "Never",
|
||||
Self::Any => "Any",
|
||||
Self::Tuple => "Tuple",
|
||||
Self::Type => "Type",
|
||||
Self::TypeAliasType(_) => "TypeAliasType",
|
||||
}
|
||||
}
|
||||
@@ -2177,8 +2088,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
| Self::NoReturn
|
||||
| Self::Never
|
||||
| Self::Any
|
||||
| Self::Tuple
|
||||
| Self::Type
|
||||
| Self::TypeAliasType(_) => Truthiness::AlwaysTrue,
|
||||
}
|
||||
}
|
||||
@@ -2193,8 +2102,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
Self::NoReturn => "typing.NoReturn",
|
||||
Self::Never => "typing.Never",
|
||||
Self::Any => "typing.Any",
|
||||
Self::Tuple => "typing.Tuple",
|
||||
Self::Type => "typing.Type",
|
||||
Self::TypeVar(typevar) => typevar.name(db),
|
||||
Self::TypeAliasType(_) => "typing.TypeAliasType",
|
||||
}
|
||||
@@ -2210,8 +2117,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
Self::NoReturn => KnownClass::SpecialForm,
|
||||
Self::Never => KnownClass::SpecialForm,
|
||||
Self::Any => KnownClass::Object,
|
||||
Self::Tuple => KnownClass::SpecialForm,
|
||||
Self::Type => KnownClass::SpecialForm,
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
}
|
||||
@@ -2238,8 +2143,6 @@ impl<'db> KnownInstanceType<'db> {
|
||||
("typing" | "typing_extensions", "Union") => Some(Self::Union),
|
||||
("typing" | "typing_extensions", "NoReturn") => Some(Self::NoReturn),
|
||||
("typing" | "typing_extensions", "Never") => Some(Self::Never),
|
||||
("typing" | "typing_extensions", "Tuple") => Some(Self::Tuple),
|
||||
("typing" | "typing_extensions", "Type") => Some(Self::Type),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -2304,6 +2207,313 @@ pub enum TypeVarBoundOrConstraints<'db> {
|
||||
Constraints(TupleType<'db>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum CallOutcome<'db> {
|
||||
Callable {
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
RevealType {
|
||||
return_ty: Type<'db>,
|
||||
revealed_ty: Type<'db>,
|
||||
},
|
||||
NotCallable {
|
||||
not_callable_ty: Type<'db>,
|
||||
},
|
||||
Union {
|
||||
called_ty: Type<'db>,
|
||||
outcomes: Box<[CallOutcome<'db>]>,
|
||||
},
|
||||
PossiblyUnboundDunderCall {
|
||||
called_ty: Type<'db>,
|
||||
call_outcome: Box<CallOutcome<'db>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'db> CallOutcome<'db> {
|
||||
/// Create a new `CallOutcome::Callable` with given return type.
|
||||
fn callable(return_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::Callable { return_ty }
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::NotCallable` with given not-callable type.
|
||||
fn not_callable(not_callable_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::NotCallable { not_callable_ty }
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::RevealType` with given revealed and return types.
|
||||
fn revealed(return_ty: Type<'db>, revealed_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::RevealType {
|
||||
return_ty,
|
||||
revealed_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::Union` with given wrapped outcomes.
|
||||
fn union(
|
||||
called_ty: Type<'db>,
|
||||
outcomes: impl IntoIterator<Item = CallOutcome<'db>>,
|
||||
) -> CallOutcome<'db> {
|
||||
CallOutcome::Union {
|
||||
called_ty,
|
||||
outcomes: outcomes.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call, or `None` if not callable.
|
||||
fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::Callable { return_ty } => Some(*return_ty),
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty: _,
|
||||
} => Some(*return_ty),
|
||||
Self::NotCallable { not_callable_ty: _ } => None,
|
||||
Self::Union {
|
||||
outcomes,
|
||||
called_ty: _,
|
||||
} => outcomes
|
||||
.iter()
|
||||
// If all outcomes are NotCallable, we return None; if some outcomes are callable
|
||||
// and some are not, we return a union including Unknown.
|
||||
.fold(None, |acc, outcome| {
|
||||
let ty = outcome.return_ty(db);
|
||||
match (acc, ty) {
|
||||
(None, None) => None,
|
||||
(None, Some(ty)) => Some(UnionBuilder::new(db).add(ty)),
|
||||
(Some(builder), ty) => Some(builder.add(ty.unwrap_or(Type::Unknown))),
|
||||
}
|
||||
})
|
||||
.map(UnionBuilder::build),
|
||||
Self::PossiblyUnboundDunderCall { call_outcome, .. } => call_outcome.return_ty(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call, emitting default diagnostics if needed.
|
||||
fn unwrap_with_diagnostic<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
) -> Type<'db> {
|
||||
match self.return_ty_result(db, node, diagnostics) {
|
||||
Ok(return_ty) => return_ty,
|
||||
Err(NotCallableError::Type {
|
||||
not_callable_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
not_callable_ty.display(db)
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::UnionElement {
|
||||
not_callable_ty,
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union element `{}`)",
|
||||
called_ty.display(db),
|
||||
not_callable_ty.display(db),
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::UnionElements {
|
||||
not_callable_tys,
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union elements {})",
|
||||
called_ty.display(db),
|
||||
not_callable_tys.display(db),
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||
callable_ty: called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||
called_ty.display(db)
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call as a result.
|
||||
fn return_ty_result<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
) -> Result<Type<'db>, NotCallableError<'db>> {
|
||||
match self {
|
||||
Self::Callable { return_ty } => Ok(*return_ty),
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty,
|
||||
} => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
DiagnosticId::RevealedType,
|
||||
Severity::Info,
|
||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||
);
|
||||
Ok(*return_ty)
|
||||
}
|
||||
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
|
||||
not_callable_ty: *not_callable_ty,
|
||||
return_ty: Type::Unknown,
|
||||
}),
|
||||
Self::PossiblyUnboundDunderCall {
|
||||
called_ty,
|
||||
call_outcome,
|
||||
} => Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||
callable_ty: *called_ty,
|
||||
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
|
||||
}),
|
||||
Self::Union {
|
||||
outcomes,
|
||||
called_ty,
|
||||
} => {
|
||||
let mut not_callable = vec![];
|
||||
let mut union_builder = UnionBuilder::new(db);
|
||||
let mut revealed = false;
|
||||
for outcome in outcomes {
|
||||
let return_ty = match outcome {
|
||||
Self::NotCallable { not_callable_ty } => {
|
||||
not_callable.push(*not_callable_ty);
|
||||
Type::Unknown
|
||||
}
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty: _,
|
||||
} => {
|
||||
if revealed {
|
||||
*return_ty
|
||||
} else {
|
||||
revealed = true;
|
||||
outcome.unwrap_with_diagnostic(db, node, diagnostics)
|
||||
}
|
||||
}
|
||||
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
|
||||
};
|
||||
union_builder = union_builder.add(return_ty);
|
||||
}
|
||||
let return_ty = union_builder.build();
|
||||
match not_callable[..] {
|
||||
[] => Ok(return_ty),
|
||||
[elem] => Err(NotCallableError::UnionElement {
|
||||
not_callable_ty: elem,
|
||||
called_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
_ if not_callable.len() == outcomes.len() => Err(NotCallableError::Type {
|
||||
not_callable_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
_ => Err(NotCallableError::UnionElements {
|
||||
not_callable_tys: not_callable.into_boxed_slice(),
|
||||
called_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CallDunderResult<'db> {
|
||||
CallOutcome(CallOutcome<'db>),
|
||||
PossiblyUnbound(CallOutcome<'db>),
|
||||
MethodNotAvailable,
|
||||
}
|
||||
|
||||
impl<'db> CallDunderResult<'db> {
|
||||
fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::CallOutcome(outcome) => outcome.return_ty(db),
|
||||
Self::PossiblyUnbound { .. } => None,
|
||||
Self::MethodNotAvailable => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum NotCallableError<'db> {
|
||||
/// The type is not callable.
|
||||
Type {
|
||||
not_callable_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
/// A single union element is not callable.
|
||||
UnionElement {
|
||||
not_callable_ty: Type<'db>,
|
||||
called_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
/// Multiple (but not all) union elements are not callable.
|
||||
UnionElements {
|
||||
not_callable_tys: Box<[Type<'db>]>,
|
||||
called_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
PossiblyUnboundDunderCall {
|
||||
callable_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'db> NotCallableError<'db> {
|
||||
/// The return type that should be used when a call is not callable.
|
||||
fn return_ty(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Type { return_ty, .. } => *return_ty,
|
||||
Self::UnionElement { return_ty, .. } => *return_ty,
|
||||
Self::UnionElements { return_ty, .. } => *return_ty,
|
||||
Self::PossiblyUnboundDunderCall { return_ty, .. } => *return_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved type that was not callable.
|
||||
///
|
||||
/// For unions, returns the union type itself, which may contain a mix of callable and
|
||||
/// non-callable types.
|
||||
fn called_ty(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Type {
|
||||
not_callable_ty, ..
|
||||
} => *not_callable_ty,
|
||||
Self::UnionElement { called_ty, .. } => *called_ty,
|
||||
Self::UnionElements { called_ty, .. } => *called_ty,
|
||||
Self::PossiblyUnboundDunderCall {
|
||||
callable_ty: called_ty,
|
||||
..
|
||||
} => *called_ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum IterationOutcome<'db> {
|
||||
Iterable {
|
||||
@@ -2709,11 +2919,15 @@ impl<'db> Class<'db> {
|
||||
return Type::tuple(db, &tuple_elements).into();
|
||||
}
|
||||
|
||||
if name == "__class__" {
|
||||
return self.metaclass(db).into();
|
||||
}
|
||||
|
||||
for superclass in self.iter_mro(db) {
|
||||
match superclass {
|
||||
// TODO we may instead want to record the fact that we encountered dynamic, and intersect it with
|
||||
// the type found on the next "real" class.
|
||||
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo(_) => {
|
||||
ClassBase::Any | ClassBase::Unknown | ClassBase::Todo => {
|
||||
return Type::from(superclass).member(db, name)
|
||||
}
|
||||
ClassBase::Class(class) => {
|
||||
@@ -2821,12 +3035,12 @@ impl<'db> From<ClassLiteralType<'db>> for Type<'db> {
|
||||
/// A type that represents `type[C]`, i.e. the class literal `C` and class literals that are subclasses of `C`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
|
||||
pub struct SubclassOfType<'db> {
|
||||
base: ClassBase<'db>,
|
||||
class: Class<'db>,
|
||||
}
|
||||
|
||||
impl<'db> SubclassOfType<'db> {
|
||||
fn member(self, db: &'db dyn Db, name: &str) -> Symbol<'db> {
|
||||
Type::from(self.base).member(db, name)
|
||||
self.class.class_member(db, name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3045,8 +3259,6 @@ pub(crate) mod tests {
|
||||
Union(Vec<Ty>),
|
||||
Intersection { pos: Vec<Ty>, neg: Vec<Ty> },
|
||||
Tuple(Vec<Ty>),
|
||||
SubclassOfAny,
|
||||
SubclassOfBuiltinClass(&'static str),
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
@@ -3084,13 +3296,6 @@ pub(crate) mod tests {
|
||||
let elements = tys.into_iter().map(|ty| ty.into_type(db));
|
||||
Type::tuple(db, elements)
|
||||
}
|
||||
Ty::SubclassOfAny => Type::subclass_of_base(ClassBase::Any),
|
||||
Ty::SubclassOfBuiltinClass(s) => Type::subclass_of(
|
||||
builtins_symbol(db, s)
|
||||
.expect_type()
|
||||
.expect_class_literal()
|
||||
.class,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3128,26 +3333,6 @@ pub(crate) mod tests {
|
||||
)]
|
||||
#[test_case(Ty::Tuple(vec![Ty::Todo]), Ty::Tuple(vec![Ty::IntLiteral(2)]))]
|
||||
#[test_case(Ty::Tuple(vec![Ty::IntLiteral(2)]), Ty::Tuple(vec![Ty::Todo]))]
|
||||
#[test_case(Ty::SubclassOfAny, Ty::SubclassOfAny)]
|
||||
#[test_case(Ty::SubclassOfAny, Ty::SubclassOfBuiltinClass("object"))]
|
||||
#[test_case(Ty::SubclassOfAny, Ty::SubclassOfBuiltinClass("str"))]
|
||||
#[test_case(Ty::SubclassOfAny, Ty::BuiltinInstance("type"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::SubclassOfAny)]
|
||||
#[test_case(
|
||||
Ty::SubclassOfBuiltinClass("object"),
|
||||
Ty::SubclassOfBuiltinClass("object")
|
||||
)]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::BuiltinInstance("type"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::SubclassOfAny)]
|
||||
#[test_case(
|
||||
Ty::SubclassOfBuiltinClass("str"),
|
||||
Ty::SubclassOfBuiltinClass("object")
|
||||
)]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::SubclassOfBuiltinClass("str"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"), Ty::BuiltinInstance("type"))]
|
||||
#[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfAny)]
|
||||
#[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfBuiltinClass("object"))]
|
||||
#[test_case(Ty::BuiltinInstance("type"), Ty::BuiltinInstance("type"))]
|
||||
fn is_assignable_to(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
|
||||
@@ -3165,11 +3350,6 @@ pub(crate) mod tests {
|
||||
Ty::Union(vec![Ty::IntLiteral(1), Ty::None]),
|
||||
Ty::Union(vec![Ty::BuiltinInstance("str"), Ty::None])
|
||||
)]
|
||||
#[test_case(
|
||||
Ty::SubclassOfBuiltinClass("object"),
|
||||
Ty::SubclassOfBuiltinClass("str")
|
||||
)]
|
||||
#[test_case(Ty::BuiltinInstance("type"), Ty::SubclassOfBuiltinClass("str"))]
|
||||
fn is_not_assignable_to(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(!from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
|
||||
@@ -3324,13 +3504,10 @@ pub(crate) mod tests {
|
||||
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]),
|
||||
Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)])
|
||||
)]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("object"), Ty::BuiltinInstance("type"))]
|
||||
fn is_equivalent_to(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
let from = from.into_type(&db);
|
||||
let to = to.into_type(&db);
|
||||
assert!(from.is_equivalent_to(&db, to));
|
||||
assert!(to.is_equivalent_to(&db, from));
|
||||
|
||||
assert!(from.into_type(&db).is_equivalent_to(&db, to.into_type(&db)));
|
||||
}
|
||||
|
||||
#[test_case(Ty::Any, Ty::Any)]
|
||||
@@ -3341,10 +3518,8 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2)]), Ty::Union(vec![Ty::IntLiteral(1), Ty::IntLiteral(2), Ty::IntLiteral(3)]))]
|
||||
fn is_not_equivalent_to(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
let from = from.into_type(&db);
|
||||
let to = to.into_type(&db);
|
||||
assert!(!from.is_equivalent_to(&db, to));
|
||||
assert!(!to.is_equivalent_to(&db, from));
|
||||
|
||||
assert!(!from.into_type(&db).is_equivalent_to(&db, to.into_type(&db)));
|
||||
}
|
||||
|
||||
#[test_case(Ty::Never, Ty::Never)]
|
||||
@@ -3606,9 +3781,6 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::Intersection{pos: vec![Ty::KnownClassInstance(KnownClass::Str)], neg: vec![Ty::LiteralString]})]
|
||||
#[test_case(Ty::Tuple(vec![]))]
|
||||
#[test_case(Ty::Tuple(vec![Ty::KnownClassInstance(KnownClass::Int), Ty::KnownClassInstance(KnownClass::Object)]))]
|
||||
#[test_case(Ty::BuiltinInstance("type"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("object"))]
|
||||
#[test_case(Ty::SubclassOfBuiltinClass("str"))]
|
||||
fn is_fully_static(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
@@ -3622,7 +3794,6 @@ pub(crate) mod tests {
|
||||
#[test_case(Ty::Union(vec![Ty::KnownClassInstance(KnownClass::Str), Ty::Unknown]))]
|
||||
#[test_case(Ty::Intersection{pos: vec![Ty::Any], neg: vec![Ty::LiteralString]})]
|
||||
#[test_case(Ty::Tuple(vec![Ty::KnownClassInstance(KnownClass::Int), Ty::Any]))]
|
||||
#[test_case(Ty::SubclassOfAny)]
|
||||
fn is_not_fully_static(from: Ty) {
|
||||
let db = setup_db();
|
||||
|
||||
|
||||
@@ -1,312 +0,0 @@
|
||||
use super::diagnostic::{TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE};
|
||||
use super::{Severity, Type, TypeArrayDisplay, UnionBuilder};
|
||||
use crate::Db;
|
||||
use ruff_db::diagnostic::DiagnosticId;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum CallOutcome<'db> {
|
||||
Callable {
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
RevealType {
|
||||
return_ty: Type<'db>,
|
||||
revealed_ty: Type<'db>,
|
||||
},
|
||||
NotCallable {
|
||||
not_callable_ty: Type<'db>,
|
||||
},
|
||||
Union {
|
||||
called_ty: Type<'db>,
|
||||
outcomes: Box<[CallOutcome<'db>]>,
|
||||
},
|
||||
PossiblyUnboundDunderCall {
|
||||
called_ty: Type<'db>,
|
||||
call_outcome: Box<CallOutcome<'db>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'db> CallOutcome<'db> {
|
||||
/// Create a new `CallOutcome::Callable` with given return type.
|
||||
pub(super) fn callable(return_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::Callable { return_ty }
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::NotCallable` with given not-callable type.
|
||||
pub(super) fn not_callable(not_callable_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::NotCallable { not_callable_ty }
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::RevealType` with given revealed and return types.
|
||||
pub(super) fn revealed(return_ty: Type<'db>, revealed_ty: Type<'db>) -> CallOutcome<'db> {
|
||||
CallOutcome::RevealType {
|
||||
return_ty,
|
||||
revealed_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `CallOutcome::Union` with given wrapped outcomes.
|
||||
pub(super) fn union(
|
||||
called_ty: Type<'db>,
|
||||
outcomes: impl IntoIterator<Item = CallOutcome<'db>>,
|
||||
) -> CallOutcome<'db> {
|
||||
CallOutcome::Union {
|
||||
called_ty,
|
||||
outcomes: outcomes.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call, or `None` if not callable.
|
||||
pub(super) fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::Callable { return_ty } => Some(*return_ty),
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty: _,
|
||||
} => Some(*return_ty),
|
||||
Self::NotCallable { not_callable_ty: _ } => None,
|
||||
Self::Union {
|
||||
outcomes,
|
||||
called_ty: _,
|
||||
} => outcomes
|
||||
.iter()
|
||||
// If all outcomes are NotCallable, we return None; if some outcomes are callable
|
||||
// and some are not, we return a union including Unknown.
|
||||
.fold(None, |acc, outcome| {
|
||||
let ty = outcome.return_ty(db);
|
||||
match (acc, ty) {
|
||||
(None, None) => None,
|
||||
(None, Some(ty)) => Some(UnionBuilder::new(db).add(ty)),
|
||||
(Some(builder), ty) => Some(builder.add(ty.unwrap_or(Type::Unknown))),
|
||||
}
|
||||
})
|
||||
.map(UnionBuilder::build),
|
||||
Self::PossiblyUnboundDunderCall { call_outcome, .. } => call_outcome.return_ty(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call, emitting default diagnostics if needed.
|
||||
pub(super) fn unwrap_with_diagnostic<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
) -> Type<'db> {
|
||||
match self.return_ty_result(db, node, diagnostics) {
|
||||
Ok(return_ty) => return_ty,
|
||||
Err(NotCallableError::Type {
|
||||
not_callable_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable",
|
||||
not_callable_ty.display(db)
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::UnionElement {
|
||||
not_callable_ty,
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union element `{}`)",
|
||||
called_ty.display(db),
|
||||
not_callable_ty.display(db),
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::UnionElements {
|
||||
not_callable_tys,
|
||||
called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (due to union elements {})",
|
||||
called_ty.display(db),
|
||||
not_callable_tys.display(db),
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||
callable_ty: called_ty,
|
||||
return_ty,
|
||||
}) => {
|
||||
diagnostics.add_lint(
|
||||
&CALL_NON_CALLABLE,
|
||||
node,
|
||||
format_args!(
|
||||
"Object of type `{}` is not callable (possibly unbound `__call__` method)",
|
||||
called_ty.display(db)
|
||||
),
|
||||
);
|
||||
return_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the return type of the call as a result.
|
||||
pub(super) fn return_ty_result<'a>(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
node: ast::AnyNodeRef,
|
||||
diagnostics: &'a mut TypeCheckDiagnosticsBuilder<'db>,
|
||||
) -> Result<Type<'db>, NotCallableError<'db>> {
|
||||
match self {
|
||||
Self::Callable { return_ty } => Ok(*return_ty),
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty,
|
||||
} => {
|
||||
diagnostics.add(
|
||||
node,
|
||||
DiagnosticId::RevealedType,
|
||||
Severity::Info,
|
||||
format_args!("Revealed type is `{}`", revealed_ty.display(db)),
|
||||
);
|
||||
Ok(*return_ty)
|
||||
}
|
||||
Self::NotCallable { not_callable_ty } => Err(NotCallableError::Type {
|
||||
not_callable_ty: *not_callable_ty,
|
||||
return_ty: Type::Unknown,
|
||||
}),
|
||||
Self::PossiblyUnboundDunderCall {
|
||||
called_ty,
|
||||
call_outcome,
|
||||
} => Err(NotCallableError::PossiblyUnboundDunderCall {
|
||||
callable_ty: *called_ty,
|
||||
return_ty: call_outcome.return_ty(db).unwrap_or(Type::Unknown),
|
||||
}),
|
||||
Self::Union {
|
||||
outcomes,
|
||||
called_ty,
|
||||
} => {
|
||||
let mut not_callable = vec![];
|
||||
let mut union_builder = UnionBuilder::new(db);
|
||||
let mut revealed = false;
|
||||
for outcome in outcomes {
|
||||
let return_ty = match outcome {
|
||||
Self::NotCallable { not_callable_ty } => {
|
||||
not_callable.push(*not_callable_ty);
|
||||
Type::Unknown
|
||||
}
|
||||
Self::RevealType {
|
||||
return_ty,
|
||||
revealed_ty: _,
|
||||
} => {
|
||||
if revealed {
|
||||
*return_ty
|
||||
} else {
|
||||
revealed = true;
|
||||
outcome.unwrap_with_diagnostic(db, node, diagnostics)
|
||||
}
|
||||
}
|
||||
_ => outcome.unwrap_with_diagnostic(db, node, diagnostics),
|
||||
};
|
||||
union_builder = union_builder.add(return_ty);
|
||||
}
|
||||
let return_ty = union_builder.build();
|
||||
match not_callable[..] {
|
||||
[] => Ok(return_ty),
|
||||
[elem] => Err(NotCallableError::UnionElement {
|
||||
not_callable_ty: elem,
|
||||
called_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
_ if not_callable.len() == outcomes.len() => Err(NotCallableError::Type {
|
||||
not_callable_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
_ => Err(NotCallableError::UnionElements {
|
||||
not_callable_tys: not_callable.into_boxed_slice(),
|
||||
called_ty: *called_ty,
|
||||
return_ty,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum CallDunderResult<'db> {
|
||||
CallOutcome(CallOutcome<'db>),
|
||||
PossiblyUnbound(CallOutcome<'db>),
|
||||
MethodNotAvailable,
|
||||
}
|
||||
|
||||
impl<'db> CallDunderResult<'db> {
|
||||
pub(super) fn return_ty(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Self::CallOutcome(outcome) => outcome.return_ty(db),
|
||||
Self::PossiblyUnbound { .. } => None,
|
||||
Self::MethodNotAvailable => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum NotCallableError<'db> {
|
||||
/// The type is not callable.
|
||||
Type {
|
||||
not_callable_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
/// A single union element is not callable.
|
||||
UnionElement {
|
||||
not_callable_ty: Type<'db>,
|
||||
called_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
/// Multiple (but not all) union elements are not callable.
|
||||
UnionElements {
|
||||
not_callable_tys: Box<[Type<'db>]>,
|
||||
called_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
PossiblyUnboundDunderCall {
|
||||
callable_ty: Type<'db>,
|
||||
return_ty: Type<'db>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'db> NotCallableError<'db> {
|
||||
/// The return type that should be used when a call is not callable.
|
||||
pub(super) fn return_ty(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Type { return_ty, .. } => *return_ty,
|
||||
Self::UnionElement { return_ty, .. } => *return_ty,
|
||||
Self::UnionElements { return_ty, .. } => *return_ty,
|
||||
Self::PossiblyUnboundDunderCall { return_ty, .. } => *return_ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// The resolved type that was not callable.
|
||||
///
|
||||
/// For unions, returns the union type itself, which may contain a mix of callable and
|
||||
/// non-callable types.
|
||||
pub(super) fn called_ty(&self) -> Type<'db> {
|
||||
match self {
|
||||
Self::Type {
|
||||
not_callable_ty, ..
|
||||
} => *not_callable_ty,
|
||||
Self::UnionElement { called_ty, .. } => *called_ty,
|
||||
Self::UnionElements { called_ty, .. } => *called_ty,
|
||||
Self::PossiblyUnboundDunderCall {
|
||||
callable_ty: called_ty,
|
||||
..
|
||||
} => *called_ty,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Registers all known type check lints.
|
||||
pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
pub(crate) fn register_type_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&UNRESOLVED_REFERENCE);
|
||||
registry.register_lint(&POSSIBLY_UNRESOLVED_REFERENCE);
|
||||
registry.register_lint(&NOT_ITERABLE);
|
||||
@@ -46,7 +46,6 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
|
||||
registry.register_lint(&UNDEFINED_REVEAL);
|
||||
registry.register_lint(&INVALID_PARAMETER_DEFAULT);
|
||||
registry.register_lint(&INVALID_TYPE_FORM);
|
||||
registry.register_lint(&INVALID_EXCEPTION_CAUGHT);
|
||||
|
||||
// String annotations
|
||||
registry.register_lint(&FSTRING_TYPE_ANNOTATION);
|
||||
@@ -78,7 +77,7 @@ declare_lint! {
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for references to names that are possibly not defined.
|
||||
/// Checks for references to names that are possibly not defined..
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using an undefined variable will raise a `NameError` at runtime.
|
||||
@@ -92,7 +91,7 @@ declare_lint! {
|
||||
/// print(x) # NameError: name 'x' is not defined
|
||||
/// ```
|
||||
pub(crate) static POSSIBLY_UNRESOLVED_REFERENCE = {
|
||||
summary: "detects references to possibly undefined names",
|
||||
summary: "detects references to possibly unresolved references",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Warn,
|
||||
}
|
||||
@@ -112,7 +111,7 @@ declare_lint! {
|
||||
/// pass
|
||||
/// ```
|
||||
pub(crate) static NOT_ITERABLE = {
|
||||
summary: "detects iteration over an object that is not iterable",
|
||||
summary: "detects objects that are not iterable",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
@@ -120,7 +119,7 @@ declare_lint! {
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INDEX_OUT_OF_BOUNDS = {
|
||||
summary: "detects index out of bounds errors",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -130,7 +129,7 @@ declare_lint! {
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for subscripting objects that do not support subscripting.
|
||||
/// Checks for objects that do not support subscripting but are used in a context that requires them to be.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Subscripting an object that does not support it will raise a `TypeError` at runtime.
|
||||
@@ -140,7 +139,7 @@ declare_lint! {
|
||||
/// 4[1] # TypeError: 'int' object is not subscriptable
|
||||
/// ```
|
||||
pub(crate) static NON_SUBSCRIPTABLE = {
|
||||
summary: "detects subscripting objects that do not support subscripting",
|
||||
summary: "detects objects that do not support subscripting",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
@@ -160,7 +159,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static POSSIBLY_UNBOUND_IMPORT = {
|
||||
summary: "detects possibly unbound imports",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -187,7 +186,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_ASSIGNMENT = {
|
||||
summary: "detects invalid assignments",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -196,7 +195,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_DECLARATION = {
|
||||
summary: "detects invalid declarations",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -205,7 +204,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static CONFLICTING_DECLARATIONS = {
|
||||
summary: "detects conflicting declarations",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -251,7 +250,7 @@ declare_lint! {
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_TYPE_PARAMETER = {
|
||||
summary: "detects invalid type parameters",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -260,7 +259,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_TYPE_VARIABLE_CONSTRAINTS = {
|
||||
summary: "detects invalid type variable constraints",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -273,7 +272,7 @@ declare_lint! {
|
||||
/// Checks for class definitions with a cyclic inheritance chain.
|
||||
///
|
||||
/// ## Why is it bad?
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static CYCLIC_CLASS_DEFINITION = {
|
||||
summary: "detects cyclic class definitions",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -282,7 +281,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static DUPLICATE_BASE = {
|
||||
summary: "detects class definitions with duplicate bases",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -291,7 +290,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_BASE = {
|
||||
summary: "detects class definitions with an invalid base",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -300,7 +299,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INCONSISTENT_MRO = {
|
||||
summary: "detects class definitions with an inconsistent MRO",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -311,8 +310,6 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for invalid parameters to `typing.Literal`.
|
||||
///
|
||||
/// TODO #14889
|
||||
pub(crate) static INVALID_LITERAL_PARAMETER = {
|
||||
summary: "detects invalid literal parameters",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -323,8 +320,6 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for calls to possibly unbound methods.
|
||||
///
|
||||
/// TODO #14889
|
||||
pub(crate) static CALL_POSSIBLY_UNBOUND_METHOD = {
|
||||
summary: "detects calls to possibly unbound methods",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -335,8 +330,6 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for possibly unbound attributes.
|
||||
///
|
||||
/// TODO #14889
|
||||
pub(crate) static POSSIBLY_UNBOUND_ATTRIBUTE = {
|
||||
summary: "detects references to possibly unbound attributes",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -347,8 +340,6 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for unresolved attributes.
|
||||
///
|
||||
/// TODO #14889
|
||||
pub(crate) static UNRESOLVED_ATTRIBUTE = {
|
||||
summary: "detects references to unresolved attributes",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -357,7 +348,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static CONFLICTING_METACLASS = {
|
||||
summary: "detects conflicting metaclasses",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -368,17 +359,15 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for binary expressions, comparisons, and unary expressions where the operands don't support the operator.
|
||||
///
|
||||
/// TODO #14889
|
||||
pub(crate) static UNSUPPORTED_OPERATOR = {
|
||||
summary: "detects binary, unary, or comparison expressions where the operands don't support the operator",
|
||||
summary: "detects binary expressions where the operands don't support the operator",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_CONTEXT_MANAGER = {
|
||||
summary: "detects expressions used in with statements that don't implement the context manager protocol",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -392,9 +381,6 @@ declare_lint! {
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Using `reveal_type` without importing it will raise a `NameError` at runtime.
|
||||
///
|
||||
/// ## Examples
|
||||
/// TODO #14889
|
||||
pub(crate) static UNDEFINED_REVEAL = {
|
||||
summary: "detects usages of `reveal_type` without importing it",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -405,9 +391,6 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for default values that can't be assigned to the parameter's annotated type.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// TODO #14889
|
||||
pub(crate) static INVALID_PARAMETER_DEFAULT = {
|
||||
summary: "detects default values that can't be assigned to the parameter's annotated type",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -417,10 +400,7 @@ declare_lint! {
|
||||
|
||||
declare_lint! {
|
||||
/// ## What it does
|
||||
/// Checks for invalid type expressions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// TODO #14889
|
||||
/// Checks for `type[]` usages that have too many or too few type arguments.
|
||||
pub(crate) static INVALID_TYPE_FORM = {
|
||||
summary: "detects invalid type forms",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -428,41 +408,6 @@ declare_lint! {
|
||||
}
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// Checks for exception handlers that catch non-exception classes.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Catching classes that do not inherit from `BaseException` will raise a TypeError at runtime.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except 1:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except ZeroDivisionError:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: except clause](https://docs.python.org/3/reference/compound_stmts.html#except-clause)
|
||||
/// - [Python documentation: Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions)
|
||||
///
|
||||
/// ## Ruff rule
|
||||
/// This rule corresponds to Ruff's [`except-with-non-exception-classes` (`B030`)](https://docs.astral.sh/ruff/rules/except-with-non-exception-classes)
|
||||
pub(crate) static INVALID_EXCEPTION_CAUGHT = {
|
||||
summary: "detects exception handlers that catch classes that do not inherit from `BaseException`",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
default_level: Level::Error,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
pub(super) id: DiagnosticId,
|
||||
@@ -743,18 +688,6 @@ impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_invalid_exception_caught(&mut self, db: &dyn Db, node: &ast::Expr, ty: Type) {
|
||||
self.add_lint(
|
||||
&INVALID_EXCEPTION_CAUGHT,
|
||||
node.into(),
|
||||
format_args!(
|
||||
"Cannot catch object of type `{}` in an exception handler \
|
||||
(must be a `BaseException` subclass or a tuple of `BaseException` subclasses)",
|
||||
ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_lint(
|
||||
&mut self,
|
||||
lint: &'static LintMetadata,
|
||||
|
||||
@@ -6,7 +6,6 @@ use ruff_db::display::FormatterJoinExtension;
|
||||
use ruff_python_ast::str::Quote;
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
|
||||
use crate::types::mro::ClassBase;
|
||||
use crate::types::{
|
||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, StringLiteralType,
|
||||
SubclassOfType, Type, UnionType,
|
||||
@@ -84,16 +83,9 @@ impl Display for DisplayRepresentation<'_> {
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::SubclassOf(SubclassOfType {
|
||||
base: ClassBase::Class(class),
|
||||
}) => {
|
||||
// Only show the bare class name here; ClassBase::display would render this as
|
||||
// type[<class 'Foo'>] instead of type[Foo].
|
||||
Type::SubclassOf(SubclassOfType { class }) => {
|
||||
write!(f, "type[{}]", class.name(self.db))
|
||||
}
|
||||
Type::SubclassOf(SubclassOfType { base }) => {
|
||||
write!(f, "type[{}]", base.display(self.db))
|
||||
}
|
||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.repr(self.db)),
|
||||
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
|
||||
@@ -57,7 +57,7 @@ use crate::types::diagnostic::{
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
|
||||
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
|
||||
};
|
||||
use crate::types::mro::{ClassBase, MroErrorKind};
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type,
|
||||
@@ -71,6 +71,7 @@ use crate::unpack::Unpack;
|
||||
use crate::util::subscript::{PyIndex, PySlice};
|
||||
use crate::Db;
|
||||
|
||||
use super::expression_ty;
|
||||
use super::string_annotation::{
|
||||
parse_string_annotation, BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION,
|
||||
};
|
||||
@@ -416,37 +417,13 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
|
||||
/// Get the already-inferred type of an expression node.
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the expression is not within this region, or if no type has yet been inferred for
|
||||
/// this node.
|
||||
/// PANIC if no type has been inferred for this node.
|
||||
#[track_caller]
|
||||
fn expression_ty(&self, expr: &ast::Expr) -> Type<'db> {
|
||||
self.types
|
||||
.expression_ty(expr.scoped_expression_id(self.db, self.scope()))
|
||||
}
|
||||
|
||||
/// Get the type of an expression from any scope in the same file.
|
||||
///
|
||||
/// If the expression is in the current scope, and we are inferring the entire scope, just look
|
||||
/// up the expression in our own results, otherwise call [`infer_scope_types()`] for the scope
|
||||
/// of the expression.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// If the expression is in the current scope but we haven't yet inferred a type for it.
|
||||
///
|
||||
/// Can cause query cycles if the expression is from a different scope and type inference is
|
||||
/// already in progress for that scope (further up the stack).
|
||||
fn file_expression_ty(&self, expression: &ast::Expr) -> Type<'db> {
|
||||
let file_scope = self.index.expression_scope_id(expression);
|
||||
let expr_scope = file_scope.to_scope_id(self.db, self.file);
|
||||
let expr_id = expression.scoped_expression_id(self.db, expr_scope);
|
||||
match self.region {
|
||||
InferenceRegion::Scope(scope) if scope == expr_scope => self.expression_ty(expression),
|
||||
_ => infer_scope_types(self.db, expr_scope).expression_ty(expr_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Infers types in the given [`InferenceRegion`].
|
||||
fn infer_region(&mut self) {
|
||||
match self.region {
|
||||
@@ -1114,9 +1091,9 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
} = parameter_with_default;
|
||||
let default_ty = default
|
||||
.as_ref()
|
||||
.map(|default| self.file_expression_ty(default));
|
||||
.map(|default| expression_ty(self.db, self.file, default));
|
||||
if let Some(annotation) = parameter.annotation.as_ref() {
|
||||
let declared_ty = self.file_expression_ty(annotation);
|
||||
let declared_ty = expression_ty(self.db, self.file, annotation);
|
||||
let inferred_ty = if let Some(default_ty) = default_ty {
|
||||
if default_ty.is_assignable_to(self.db, declared_ty) {
|
||||
UnionType::from_elements(self.db, [declared_ty, default_ty])
|
||||
@@ -1160,7 +1137,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
if let Some(annotation) = parameter.annotation.as_ref() {
|
||||
let _annotated_ty = self.file_expression_ty(annotation);
|
||||
let _annotated_ty = expression_ty(self.db, self.file, annotation);
|
||||
// TODO `tuple[annotated_ty, ...]`
|
||||
let ty = KnownClass::Tuple.to_instance(self.db);
|
||||
self.add_declaration_with_binding(parameter.into(), definition, ty, ty);
|
||||
@@ -1185,7 +1162,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
if let Some(annotation) = parameter.annotation.as_ref() {
|
||||
let _annotated_ty = self.file_expression_ty(annotation);
|
||||
let _annotated_ty = expression_ty(self.db, self.file, annotation);
|
||||
// TODO `dict[str, annotated_ty]`
|
||||
let ty = KnownClass::Dict.to_instance(self.db);
|
||||
self.add_declaration_with_binding(parameter.into(), definition, ty, ty);
|
||||
@@ -1545,56 +1522,40 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
except_handler_definition: &ExceptHandlerDefinitionKind,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
let node = except_handler_definition.handled_exceptions();
|
||||
|
||||
// If there is no handled exception, it's invalid syntax;
|
||||
// a diagnostic will have already been emitted
|
||||
let node_ty = node.map_or(Type::Unknown, |ty| self.infer_expression(ty));
|
||||
|
||||
// If it's an `except*` handler, this won't actually be the type of the bound symbol;
|
||||
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
||||
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
||||
let type_base_exception = KnownClass::BaseException
|
||||
.to_subclass_of(self.db)
|
||||
.unwrap_or(Type::Unknown);
|
||||
let mut builder = UnionBuilder::new(self.db);
|
||||
for element in tuple.elements(self.db).iter().copied() {
|
||||
builder = builder.add(if element.is_assignable_to(self.db, type_base_exception) {
|
||||
element.to_instance(self.db)
|
||||
} else {
|
||||
if let Some(node) = node {
|
||||
self.diagnostics
|
||||
.add_invalid_exception_caught(self.db, node, element);
|
||||
}
|
||||
Type::Unknown
|
||||
});
|
||||
}
|
||||
builder.build()
|
||||
} else if node_ty.is_subtype_of(self.db, KnownClass::Tuple.to_instance(self.db)) {
|
||||
todo_type!("Homogeneous tuple in exception handler")
|
||||
} else {
|
||||
let type_base_exception = KnownClass::BaseException
|
||||
.to_subclass_of(self.db)
|
||||
.unwrap_or(Type::Unknown);
|
||||
if node_ty.is_assignable_to(self.db, type_base_exception) {
|
||||
node_ty.to_instance(self.db)
|
||||
} else {
|
||||
if let Some(node) = node {
|
||||
self.diagnostics
|
||||
.add_invalid_exception_caught(self.db, node, node_ty);
|
||||
}
|
||||
Type::Unknown
|
||||
}
|
||||
};
|
||||
let node_ty = except_handler_definition
|
||||
.handled_exceptions()
|
||||
.map(|ty| self.infer_expression(ty))
|
||||
// If there is no handled exception, it's invalid syntax;
|
||||
// a diagnostic will have already been emitted
|
||||
.unwrap_or(Type::Unknown);
|
||||
|
||||
let symbol_ty = if except_handler_definition.is_star() {
|
||||
// TODO: we should infer `ExceptionGroup` if `node_ty` is a subtype of `tuple[type[Exception], ...]`
|
||||
// (needs support for homogeneous tuples).
|
||||
// TODO should be generic --Alex
|
||||
//
|
||||
// TODO: should be generic with `symbol_ty` as the generic parameter
|
||||
KnownClass::BaseExceptionGroup.to_instance(self.db)
|
||||
// TODO should infer `ExceptionGroup` if all caught exceptions
|
||||
// are subclasses of `Exception` --Alex
|
||||
builtins_symbol(self.db, "BaseExceptionGroup")
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::Unknown)
|
||||
.to_instance(self.db)
|
||||
} else {
|
||||
symbol_ty
|
||||
// TODO: anything that's a consistent subtype of
|
||||
// `type[BaseException] | tuple[type[BaseException], ...]` should be valid;
|
||||
// anything else is invalid and should lead to a diagnostic being reported --Alex
|
||||
match node_ty {
|
||||
Type::Any | Type::Unknown => node_ty,
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(class),
|
||||
Type::Tuple(tuple) => UnionType::from_elements(
|
||||
self.db,
|
||||
tuple.elements(self.db).iter().map(|ty| {
|
||||
ty.into_class_literal().map_or(
|
||||
todo_type!("exception type"),
|
||||
|ClassLiteralType { class }| Type::instance(class),
|
||||
)
|
||||
}),
|
||||
),
|
||||
_ => todo_type!("exception type"),
|
||||
}
|
||||
};
|
||||
|
||||
self.add_binding(
|
||||
@@ -4706,12 +4667,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
match slice {
|
||||
ast::Expr::Name(_) | ast::Expr::Attribute(_) => {
|
||||
let name_ty = self.infer_expression(slice);
|
||||
match name_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Type::subclass_of(class),
|
||||
Type::KnownInstance(KnownInstanceType::Any) => {
|
||||
Type::subclass_of_base(ClassBase::Any)
|
||||
}
|
||||
_ => todo_type!("unsupported type[X] special form"),
|
||||
if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() {
|
||||
Type::subclass_of(class)
|
||||
} else {
|
||||
todo_type!("unsupported type[X] special form")
|
||||
}
|
||||
}
|
||||
ast::Expr::BinOp(binary) if binary.op == ast::Operator::BitOr => {
|
||||
@@ -4735,33 +4694,6 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
ast::Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice: parameters,
|
||||
..
|
||||
}) => {
|
||||
let parameters_ty = match self.infer_expression(value) {
|
||||
Type::KnownInstance(KnownInstanceType::Union) => match &**parameters {
|
||||
ast::Expr::Tuple(tuple) => {
|
||||
let ty = UnionType::from_elements(
|
||||
self.db,
|
||||
tuple
|
||||
.iter()
|
||||
.map(|element| self.infer_subclass_of_type_expression(element)),
|
||||
);
|
||||
self.store_expression_type(parameters, ty);
|
||||
ty
|
||||
}
|
||||
_ => self.infer_subclass_of_type_expression(parameters),
|
||||
},
|
||||
_ => {
|
||||
self.infer_type_expression(parameters);
|
||||
todo_type!("unsupported nested subscript in type[X]")
|
||||
}
|
||||
};
|
||||
self.store_expression_type(slice, parameters_ty);
|
||||
parameters_ty
|
||||
}
|
||||
// TODO: subscripts, etc.
|
||||
_ => {
|
||||
self.infer_type_expression(slice);
|
||||
@@ -4861,19 +4793,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
KnownInstanceType::Type => self.infer_subclass_of_type_expression(parameters),
|
||||
KnownInstanceType::Tuple => self.infer_tuple_type_expression(parameters),
|
||||
KnownInstanceType::Any => {
|
||||
self.diagnostics.add_lint(
|
||||
&INVALID_TYPE_PARAMETER,
|
||||
subscript.into(),
|
||||
format_args!(
|
||||
"Type `{}` expected no type parameter",
|
||||
known_instance.repr(self.db)
|
||||
),
|
||||
);
|
||||
Type::Unknown
|
||||
}
|
||||
KnownInstanceType::Any => Type::Any,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use std::ops::Deref;
|
||||
use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, TodoType, Type};
|
||||
use crate::Db;
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
|
||||
use crate::{types::todo_type, Db};
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
///
|
||||
@@ -76,14 +76,21 @@ impl<'db> Mro<'db> {
|
||||
// This *could* theoretically be handled by the final branch below,
|
||||
// but it's a common case (i.e., worth optimizing for),
|
||||
// and the `c3_merge` function requires lots of allocations.
|
||||
[single_base] => ClassBase::try_from_ty(db, *single_base).map_or_else(
|
||||
|| Err(MroErrorKind::InvalidBases(Box::from([(0, *single_base)]))),
|
||||
|single_base| {
|
||||
Ok(std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db))
|
||||
.collect())
|
||||
},
|
||||
),
|
||||
[single_base] => {
|
||||
let single_base = ClassBase::try_from_ty(*single_base).ok_or(*single_base);
|
||||
single_base.map_or_else(
|
||||
|invalid_base_ty| {
|
||||
let bases_info = Box::from([(0, invalid_base_ty)]);
|
||||
Err(MroErrorKind::InvalidBases(bases_info))
|
||||
},
|
||||
|single_base| {
|
||||
let mro = std::iter::once(ClassBase::Class(class))
|
||||
.chain(single_base.mro(db))
|
||||
.collect();
|
||||
Ok(mro)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// The class has multiple explicit bases.
|
||||
//
|
||||
@@ -95,9 +102,9 @@ impl<'db> Mro<'db> {
|
||||
let mut invalid_bases = vec![];
|
||||
|
||||
for (i, base) in multiple_bases.iter().enumerate() {
|
||||
match ClassBase::try_from_ty(db, *base) {
|
||||
Some(valid_base) => valid_bases.push(valid_base),
|
||||
None => invalid_bases.push((i, *base)),
|
||||
match ClassBase::try_from_ty(*base).ok_or(*base) {
|
||||
Ok(valid_base) => valid_bases.push(valid_base),
|
||||
Err(invalid_base) => invalid_bases.push((i, invalid_base)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,16 +299,16 @@ pub(super) enum MroErrorKind<'db> {
|
||||
/// This is much more limited than the [`Type`] enum:
|
||||
/// all types that would be invalid to have as a class base are
|
||||
/// transformed into [`ClassBase::Unknown`]
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, salsa::Update)]
|
||||
pub enum ClassBase<'db> {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub(super) enum ClassBase<'db> {
|
||||
Any,
|
||||
Unknown,
|
||||
Todo(TodoType),
|
||||
Todo,
|
||||
Class(Class<'db>),
|
||||
}
|
||||
|
||||
impl<'db> ClassBase<'db> {
|
||||
pub fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||
pub(super) fn display(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db {
|
||||
struct Display<'db> {
|
||||
base: ClassBase<'db>,
|
||||
db: &'db dyn Db,
|
||||
@@ -311,7 +318,7 @@ impl<'db> ClassBase<'db> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.base {
|
||||
ClassBase::Any => f.write_str("Any"),
|
||||
ClassBase::Todo(todo) => todo.fmt(f),
|
||||
ClassBase::Todo => f.write_str("Todo"),
|
||||
ClassBase::Unknown => f.write_str("Unknown"),
|
||||
ClassBase::Class(class) => write!(f, "<class '{}'>", class.name(self.db)),
|
||||
}
|
||||
@@ -334,11 +341,11 @@ impl<'db> ClassBase<'db> {
|
||||
/// Attempt to resolve `ty` into a `ClassBase`.
|
||||
///
|
||||
/// Return `None` if `ty` is not an acceptable type for a class base.
|
||||
fn try_from_ty(db: &'db dyn Db, ty: Type<'db>) -> Option<Self> {
|
||||
fn try_from_ty(ty: Type<'db>) -> Option<Self> {
|
||||
match ty {
|
||||
Type::Any => Some(Self::Any),
|
||||
Type::Unknown => Some(Self::Unknown),
|
||||
Type::Todo(todo) => Some(Self::Todo(todo)),
|
||||
Type::Todo(_) => Some(Self::Todo),
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)),
|
||||
Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs?
|
||||
Type::Intersection(_) => None, // TODO -- probably incorrect?
|
||||
@@ -364,13 +371,6 @@ impl<'db> ClassBase<'db> {
|
||||
| KnownInstanceType::Never
|
||||
| KnownInstanceType::Optional => None,
|
||||
KnownInstanceType::Any => Some(Self::Any),
|
||||
// TODO: Classes inheriting from `typing.Type` et al. also have `Generic` in their MRO
|
||||
KnownInstanceType::Type => {
|
||||
ClassBase::try_from_ty(db, KnownClass::Type.to_class_literal(db))
|
||||
}
|
||||
KnownInstanceType::Tuple => {
|
||||
ClassBase::try_from_ty(db, KnownClass::Tuple.to_class_literal(db))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -392,9 +392,7 @@ impl<'db> ClassBase<'db> {
|
||||
ClassBase::Unknown => {
|
||||
Either::Left([ClassBase::Unknown, ClassBase::object(db)].into_iter())
|
||||
}
|
||||
ClassBase::Todo(todo) => {
|
||||
Either::Left([ClassBase::Todo(todo), ClassBase::object(db)].into_iter())
|
||||
}
|
||||
ClassBase::Todo => Either::Left([ClassBase::Todo, ClassBase::object(db)].into_iter()),
|
||||
ClassBase::Class(class) => Either::Right(class.iter_mro(db)),
|
||||
}
|
||||
}
|
||||
@@ -410,7 +408,7 @@ impl<'db> From<ClassBase<'db>> for Type<'db> {
|
||||
fn from(value: ClassBase<'db>) -> Self {
|
||||
match value {
|
||||
ClassBase::Any => Type::Any,
|
||||
ClassBase::Todo(todo) => Type::Todo(todo),
|
||||
ClassBase::Todo => todo_type!(),
|
||||
ClassBase::Unknown => Type::Unknown,
|
||||
ClassBase::Class(class) => Type::class_literal(class),
|
||||
}
|
||||
|
||||
@@ -64,10 +64,6 @@ fn arbitrary_core_type(g: &mut Gen) -> Ty {
|
||||
Ty::BuiltinClassLiteral("int"),
|
||||
Ty::BuiltinClassLiteral("bool"),
|
||||
Ty::BuiltinClassLiteral("object"),
|
||||
Ty::BuiltinInstance("type"),
|
||||
Ty::SubclassOfAny,
|
||||
Ty::SubclassOfBuiltinClass("object"),
|
||||
Ty::SubclassOfBuiltinClass("str"),
|
||||
])
|
||||
.unwrap()
|
||||
.clone()
|
||||
@@ -167,13 +163,13 @@ mod stable {
|
||||
// `T` is equivalent to itself.
|
||||
type_property_test!(
|
||||
equivalent_to_is_reflexive, db,
|
||||
forall types t. t.is_fully_static(db) => t.is_equivalent_to(db, t)
|
||||
forall types t. t.is_equivalent_to(db, t)
|
||||
);
|
||||
|
||||
// `T` is a subtype of itself.
|
||||
type_property_test!(
|
||||
subtype_of_is_reflexive, db,
|
||||
forall types t. t.is_fully_static(db) => t.is_subtype_of(db, t)
|
||||
forall types t. t.is_subtype_of(db, t)
|
||||
);
|
||||
|
||||
// `S <: T` and `T <: U` implies that `S <: U`.
|
||||
@@ -218,12 +214,6 @@ mod stable {
|
||||
forall types t. t.is_singleton(db) => t.is_single_valued(db)
|
||||
);
|
||||
|
||||
// If `T` contains a gradual form, it should not participate in equivalence
|
||||
type_property_test!(
|
||||
non_fully_static_types_do_not_participate_in_equivalence, db,
|
||||
forall types s, t. !s.is_fully_static(db) => !s.is_equivalent_to(db, t) && !t.is_equivalent_to(db, s)
|
||||
);
|
||||
|
||||
// If `T` contains a gradual form, it should not participate in subtyping
|
||||
type_property_test!(
|
||||
non_fully_static_types_do_not_participate_in_subtyping, db,
|
||||
|
||||
@@ -110,7 +110,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static INVALID_SYNTAX_IN_FORWARD_ANNOTATION = {
|
||||
summary: "detects invalid syntax in forward annotations",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
@@ -119,7 +119,7 @@ declare_lint! {
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
/// TODO #14889
|
||||
/// TODO
|
||||
pub(crate) static ESCAPE_CHARACTER_IN_FORWARD_ANNOTATION = {
|
||||
summary: "detects forward type annotations with escape characters",
|
||||
status: LintStatus::preview("1.0.0"),
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::assertion::{Assertion, ErrorAssertion, InlineFileAssertions};
|
||||
use crate::db::Db;
|
||||
use crate::diagnostic::SortedDiagnostics;
|
||||
use colored::Colorize;
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticAsStrError, DiagnosticId};
|
||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticId};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::source::{line_index, source_text, SourceText};
|
||||
use ruff_source_file::{LineIndex, OneIndexed};
|
||||
@@ -161,14 +161,9 @@ where
|
||||
T: Diagnostic,
|
||||
{
|
||||
fn unmatched(&self) -> String {
|
||||
let id = self.id();
|
||||
let id = id.as_str().unwrap_or_else(|error| match error {
|
||||
DiagnosticAsStrError::Category { name, .. } => name,
|
||||
});
|
||||
|
||||
maybe_add_undefined_reveal_clarification(
|
||||
self,
|
||||
format_args!(r#"[{id}] "{message}""#, message = self.message()),
|
||||
format_args!(r#"[{}] "{}""#, self.id(), self.message()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -178,14 +173,9 @@ where
|
||||
T: Diagnostic,
|
||||
{
|
||||
fn unmatched_with_column(&self, column: OneIndexed) -> String {
|
||||
let id = self.id();
|
||||
let id = id.as_str().unwrap_or_else(|error| match error {
|
||||
DiagnosticAsStrError::Category { name, .. } => name,
|
||||
});
|
||||
|
||||
maybe_add_undefined_reveal_clarification(
|
||||
self,
|
||||
format_args!(r#"{column} [{id}] "{message}""#, message = self.message()),
|
||||
format_args!(r#"{column} [{}] "{}""#, self.id(), self.message()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -474,7 +464,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"unmatched assertion: revealed: Foo",
|
||||
r#"unexpected error: 1 [not-revealed-type] "Revealed type is `Foo`""#,
|
||||
r#"unexpected error: 1 [lint/not-revealed-type] "Revealed type is `Foo`""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -591,7 +581,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
||||
original diagnostic: [undefined-reveal] \"undefined reveal message\")",
|
||||
original diagnostic: [lint/undefined-reveal] \"undefined reveal message\")",
|
||||
r#"unexpected error: [revealed-type] "Revealed type is `Literal[1]`""#,
|
||||
],
|
||||
)],
|
||||
@@ -623,7 +613,7 @@ mod tests {
|
||||
&[
|
||||
"unmatched assertion: error: [something-else]",
|
||||
"used built-in `reveal_type`: add a `# revealed` assertion on this line (\
|
||||
original diagnostic: 1 [undefined-reveal] \"undefined reveal message\")",
|
||||
original diagnostic: 1 [lint/undefined-reveal] \"undefined reveal message\")",
|
||||
r#"unexpected error: 13 [revealed-type] "Revealed type is `Literal[1]`""#,
|
||||
],
|
||||
)],
|
||||
@@ -668,7 +658,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"unmatched assertion: error: [some-rule]",
|
||||
r#"unexpected error: 1 [anything] "Any message""#,
|
||||
r#"unexpected error: 1 [lint/anything] "Any message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -705,7 +695,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
r#"unmatched assertion: error: "contains this""#,
|
||||
r#"unexpected error: 1 [anything] "Any message""#,
|
||||
r#"unexpected error: 1 [lint/anything] "Any message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -742,7 +732,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"unmatched assertion: error: 2 [rule]",
|
||||
r#"unexpected error: 1 [rule] "Any message""#,
|
||||
r#"unexpected error: 1 [lint/rule] "Any message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -807,7 +797,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
r#"unmatched assertion: error: 2 [some-rule] "contains this""#,
|
||||
r#"unexpected error: 1 [some-rule] "message contains this""#,
|
||||
r#"unexpected error: 1 [lint/some-rule] "message contains this""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -830,7 +820,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
||||
r#"unexpected error: 1 [other-rule] "message contains this""#,
|
||||
r#"unexpected error: 1 [lint/other-rule] "message contains this""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -853,7 +843,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
r#"unmatched assertion: error: 1 [some-rule] "contains this""#,
|
||||
r#"unexpected error: 1 [some-rule] "Any message""#,
|
||||
r#"unexpected error: 1 [lint/some-rule] "Any message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -887,9 +877,9 @@ mod tests {
|
||||
result,
|
||||
&[
|
||||
(1, &["unmatched assertion: error: [line-one]"]),
|
||||
(2, &[r#"unexpected error: [line-two] "msg""#]),
|
||||
(2, &[r#"unexpected error: [lint/line-two] "msg""#]),
|
||||
(4, &["unmatched assertion: error: [line-four]"]),
|
||||
(5, &[r#"unexpected error: [line-five] "msg""#]),
|
||||
(5, &[r#"unexpected error: [lint/line-five] "msg""#]),
|
||||
(6, &["unmatched assertion: error: [line-six]"]),
|
||||
],
|
||||
);
|
||||
@@ -913,7 +903,10 @@ mod tests {
|
||||
],
|
||||
);
|
||||
|
||||
assert_fail(result, &[(2, &[r#"unexpected error: [line-two] "msg""#])]);
|
||||
assert_fail(
|
||||
result,
|
||||
&[(2, &[r#"unexpected error: [lint/line-two] "msg""#])],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -979,7 +972,7 @@ mod tests {
|
||||
|
||||
assert_fail(
|
||||
result,
|
||||
&[(3, &[r#"unexpected error: 1 [third-rule] "msg""#])],
|
||||
&[(3, &[r#"unexpected error: 1 [lint/third-rule] "msg""#])],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1028,7 +1021,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"invalid assertion: no rule or message text",
|
||||
r#"unexpected error: 1 [some-rule] "some message""#,
|
||||
r#"unexpected error: 1 [lint/some-rule] "some message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
@@ -1053,7 +1046,7 @@ mod tests {
|
||||
0,
|
||||
&[
|
||||
"invalid assertion: no rule or message text",
|
||||
r#"unexpected error: 1 [some-rule] "some message""#,
|
||||
r#"unexpected error: 1 [lint/some-rule] "some message""#,
|
||||
],
|
||||
)],
|
||||
);
|
||||
|
||||
@@ -19,6 +19,6 @@ fn check() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
vec!["error[lint:unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
|
||||
vec!["error[unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use red_knot_python_semantic::lint::{LintRegistry, LintRegistryBuilder};
|
||||
use red_knot_python_semantic::register_lints;
|
||||
use red_knot_python_semantic::register_semantic_lints;
|
||||
|
||||
pub mod db;
|
||||
pub mod watch;
|
||||
@@ -10,6 +10,6 @@ pub static DEFAULT_LINT_REGISTRY: std::sync::LazyLock<LintRegistry> =
|
||||
|
||||
pub fn default_lints_registry() -> LintRegistry {
|
||||
let mut builder = LintRegistryBuilder::default();
|
||||
register_lints(&mut builder);
|
||||
register_semantic_lints(&mut builder);
|
||||
builder.build()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
use std::{fs, path::Path, process::Command};
|
||||
|
||||
fn main() {
|
||||
// The workspace root directory is not available without walking up the tree
|
||||
@@ -25,22 +21,27 @@ fn commit_info(workspace_root: &Path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(git_head_path) = git_head(&git_dir) {
|
||||
println!("cargo:rerun-if-changed={}", git_head_path.display());
|
||||
let git_head_path = git_dir.join("HEAD");
|
||||
println!(
|
||||
"cargo::rerun-if-changed={}",
|
||||
git_head_path.as_path().display()
|
||||
);
|
||||
|
||||
let git_head_contents = fs::read_to_string(git_head_path);
|
||||
if let Ok(git_head_contents) = git_head_contents {
|
||||
// The contents are either a commit or a reference in the following formats
|
||||
// - "<commit>" when the head is detached
|
||||
// - "ref <ref>" when working on a branch
|
||||
// If a commit, checking if the HEAD file has changed is sufficient
|
||||
// If a ref, we need to add the head file for that ref to rebuild on commit
|
||||
let mut git_ref_parts = git_head_contents.split_whitespace();
|
||||
git_ref_parts.next();
|
||||
if let Some(git_ref) = git_ref_parts.next() {
|
||||
let git_ref_path = git_dir.join(git_ref);
|
||||
println!("cargo:rerun-if-changed={}", git_ref_path.display());
|
||||
}
|
||||
let git_head_contents = fs::read_to_string(git_head_path);
|
||||
if let Ok(git_head_contents) = git_head_contents {
|
||||
// The contents are either a commit or a reference in the following formats
|
||||
// - "<commit>" when the head is detached
|
||||
// - "ref <ref>" when working on a branch
|
||||
// If a commit, checking if the HEAD file has changed is sufficient
|
||||
// If a ref, we need to add the head file for that ref to rebuild on commit
|
||||
let mut git_ref_parts = git_head_contents.split_whitespace();
|
||||
git_ref_parts.next();
|
||||
if let Some(git_ref) = git_ref_parts.next() {
|
||||
let git_ref_path = git_dir.join(git_ref);
|
||||
println!(
|
||||
"cargo::rerun-if-changed={}",
|
||||
git_ref_path.as_path().display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,30 +78,3 @@ fn commit_info(workspace_root: &Path) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn git_head(git_dir: &Path) -> Option<PathBuf> {
|
||||
// The typical case is a standard git repository.
|
||||
let git_head_path = git_dir.join("HEAD");
|
||||
if git_head_path.exists() {
|
||||
return Some(git_head_path);
|
||||
}
|
||||
if !git_dir.is_file() {
|
||||
return None;
|
||||
}
|
||||
// If `.git/HEAD` doesn't exist and `.git` is actually a file,
|
||||
// then let's try to attempt to read it as a worktree. If it's
|
||||
// a worktree, then its contents will look like this, e.g.:
|
||||
//
|
||||
// gitdir: /home/andrew/astral/uv/main/.git/worktrees/pr2
|
||||
//
|
||||
// And the HEAD file we want to watch will be at:
|
||||
//
|
||||
// /home/andrew/astral/uv/main/.git/worktrees/pr2/HEAD
|
||||
let contents = fs::read_to_string(git_dir).ok()?;
|
||||
let (label, worktree_path) = contents.split_once(':')?;
|
||||
if label != "gitdir" {
|
||||
return None;
|
||||
}
|
||||
let worktree_path = worktree_path.trim();
|
||||
Some(PathBuf::from(worktree_path))
|
||||
}
|
||||
|
||||
@@ -25,30 +25,30 @@ const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/
|
||||
|
||||
static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
// We don't support `*` imports yet:
|
||||
"error[lint:unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
|
||||
"error[unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
|
||||
// We don't support terminal statements in control flow yet:
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:566:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
||||
"error[lint:conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:566:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
|
||||
"error[conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
|
||||
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
|
||||
];
|
||||
|
||||
fn get_test_file(name: &str) -> TestFile {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -68,9 +66,12 @@ pub enum DiagnosticId {
|
||||
|
||||
/// A lint violation.
|
||||
///
|
||||
/// Lints can be suppressed and some lints can be enabled or disabled in the configuration.
|
||||
/// Lint's can be suppressed and some lints can be enabled or disabled in the configuration.
|
||||
Lint(LintName),
|
||||
|
||||
/// Some code is incorrectly formatted.
|
||||
Format,
|
||||
|
||||
/// A revealed type: Created by `reveal_type(expression)`.
|
||||
RevealedType,
|
||||
}
|
||||
@@ -91,58 +92,28 @@ impl DiagnosticId {
|
||||
matches!(self, DiagnosticId::Lint(self_name) if self_name == name)
|
||||
}
|
||||
|
||||
/// Returns `true` if this `DiagnosticId` matches the given name.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// use ruff_db::diagnostic::DiagnosticId;
|
||||
///
|
||||
/// assert!(DiagnosticId::Io.matches("io"));
|
||||
/// assert!(DiagnosticId::lint("test").matches("lint:test"));
|
||||
/// assert!(!DiagnosticId::lint("test").matches("test"));
|
||||
/// ```
|
||||
pub fn matches(&self, expected_name: &str) -> bool {
|
||||
match self.as_str() {
|
||||
Ok(id) => id == expected_name,
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => expected_name
|
||||
.strip_prefix(category)
|
||||
.and_then(|prefix| prefix.strip_prefix(":"))
|
||||
.is_some_and(|rest| rest == name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Result<&str, DiagnosticAsStrError> {
|
||||
pub fn matches(&self, name: &str) -> bool {
|
||||
match self {
|
||||
DiagnosticId::Io => Ok("io"),
|
||||
DiagnosticId::InvalidSyntax => Ok("invalid-syntax"),
|
||||
DiagnosticId::Lint(name) => Err(DiagnosticAsStrError::Category {
|
||||
category: "lint",
|
||||
name: name.as_str(),
|
||||
}),
|
||||
DiagnosticId::RevealedType => Ok("revealed-type"),
|
||||
DiagnosticId::Lint(self_name) => name
|
||||
.strip_prefix("lint/")
|
||||
.is_some_and(|rest| rest == &**self_name),
|
||||
|
||||
DiagnosticId::Io => name == "io",
|
||||
DiagnosticId::InvalidSyntax => name == "invalid-syntax",
|
||||
DiagnosticId::Format => name == "format",
|
||||
DiagnosticId::RevealedType => name == "revealed-type",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Error)]
|
||||
pub enum DiagnosticAsStrError {
|
||||
/// The id can't be converted to a string because it belongs to a sub-category.
|
||||
#[error("id from a sub-category: {category}:{name}")]
|
||||
Category {
|
||||
/// The id's category.
|
||||
category: &'static str,
|
||||
/// The diagnostic id in this category.
|
||||
name: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DiagnosticId {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self.as_str() {
|
||||
Ok(name) => f.write_str(name),
|
||||
Err(DiagnosticAsStrError::Category { category, name }) => {
|
||||
write!(f, "{category}:{name}")
|
||||
}
|
||||
match self {
|
||||
DiagnosticId::InvalidSyntax => f.write_str("invalid-syntax"),
|
||||
DiagnosticId::Io => f.write_str("io"),
|
||||
DiagnosticId::Lint(name) => write!(f, "lint/{name}"),
|
||||
DiagnosticId::Format => f.write_str("format"),
|
||||
DiagnosticId::RevealedType => f.write_str("revealed-type"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,7 +121,7 @@ impl std::fmt::Display for DiagnosticId {
|
||||
pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
||||
fn id(&self) -> DiagnosticId;
|
||||
|
||||
fn message(&self) -> Cow<str>;
|
||||
fn message(&self) -> std::borrow::Cow<str>;
|
||||
|
||||
fn file(&self) -> File;
|
||||
|
||||
|
||||
@@ -656,7 +656,7 @@ where
|
||||
let elements = buffer.elements();
|
||||
|
||||
let recorded = if self.start > elements.len() {
|
||||
// May happen if buffer was rewound.
|
||||
// May happen if buffer was rewinded.
|
||||
&[]
|
||||
} else {
|
||||
&elements[self.start..]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.8.3"
|
||||
version = "0.8.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -5,23 +5,13 @@ Should emit:
|
||||
B028 - on lines 8 and 9
|
||||
"""
|
||||
|
||||
warnings.warn("test", DeprecationWarning)
|
||||
warnings.warn("test", DeprecationWarning, source=None)
|
||||
warnings.warn("test", DeprecationWarning, source=None, stacklevel=2)
|
||||
warnings.warn("test", DeprecationWarning, stacklevel=1)
|
||||
warnings.warn("test", DeprecationWarning, 1)
|
||||
warnings.warn("test", category=DeprecationWarning, stacklevel=1)
|
||||
args = ("test", DeprecationWarning, 1)
|
||||
warnings.warn(*args)
|
||||
kwargs = {"message": "test", "category": DeprecationWarning, "stacklevel": 1}
|
||||
warnings.warn(**kwargs)
|
||||
args = ("test", DeprecationWarning)
|
||||
kwargs = {"stacklevel": 1}
|
||||
warnings.warn(*args, **kwargs)
|
||||
warnings.warn(DeprecationWarning("test"))
|
||||
warnings.warn(DeprecationWarning("test"), source=None)
|
||||
warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2)
|
||||
warnings.warn(DeprecationWarning("test"), stacklevel=1)
|
||||
|
||||
warnings.warn(
|
||||
"test",
|
||||
DeprecationWarning,
|
||||
DeprecationWarning("test"),
|
||||
# some comments here
|
||||
source = None # no trailing comma
|
||||
)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
from itertools import batched, count, cycle, repeat
|
||||
|
||||
|
||||
# Errors
|
||||
batched(range(3), 1)
|
||||
batched("abc", 2)
|
||||
batched([i for i in range(42)], some_n)
|
||||
batched((foo for foo in cycle()))
|
||||
batched(itertools.batched([1, 2, 3], strict=True))
|
||||
|
||||
# Errors (limited iterators).
|
||||
batched(repeat(1, 1))
|
||||
batched(repeat(1, times=4))
|
||||
|
||||
# No fix
|
||||
batched([], **kwargs)
|
||||
|
||||
# No errors
|
||||
batched()
|
||||
batched(range(3), 0, strict=True)
|
||||
batched(["a", "b"], count, strict=False)
|
||||
batched(("a", "b", "c"), zip(repeat()), strict=True)
|
||||
|
||||
# No errors (infinite iterators)
|
||||
batched(cycle("ABCDEF"), 3)
|
||||
batched(count(), qux + lorem)
|
||||
batched(repeat(1), ipsum // 19 @ 0x1)
|
||||
batched(repeat(1, None))
|
||||
batched(repeat(1, times=None))
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
# Errors
|
||||
itertools.batched(range(3), 1)
|
||||
itertools.batched("abc", 2)
|
||||
itertools.batched([i for i in range(42)], some_n)
|
||||
itertools.batched((foo for foo in cycle()))
|
||||
itertools.batched(itertools.batched([1, 2, 3], strict=True))
|
||||
|
||||
# Errors (limited iterators).
|
||||
itertools.batched(repeat(1, 1))
|
||||
itertools.batched(repeat(1, times=4))
|
||||
|
||||
# No fix
|
||||
itertools.batched([], **kwargs)
|
||||
|
||||
# No errors
|
||||
itertools.batched()
|
||||
itertools.batched(range(3), 0, strict=True)
|
||||
itertools.batched(["a", "b"], count, strict=False)
|
||||
itertools.batched(("a", "b", "c"), zip(repeat()), strict=True)
|
||||
|
||||
# No errors (infinite iterators)
|
||||
itertools.batched(cycle("ABCDEF"), 3)
|
||||
itertools.batched(count(), qux + lorem)
|
||||
itertools.batched(repeat(1), ipsum // 19 @ 0x1)
|
||||
itertools.batched(repeat(1, None))
|
||||
itertools.batched(repeat(1, times=None))
|
||||
@@ -8,7 +8,7 @@ import typing
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from enum import EnumMeta
|
||||
from typing import Any, Generic, ParamSpec, Type, TypeVar, TypeVarTuple, overload
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
from _typeshed import Self
|
||||
@@ -321,41 +321,3 @@ def __imul__(self, other: Any) -> list[str]:
|
||||
class UsesStringizedAnnotations:
|
||||
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self":
|
||||
return self
|
||||
|
||||
|
||||
class NonGeneric1(tuple):
|
||||
def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ...
|
||||
def __enter__(self: NonGeneric1) -> NonGeneric1: ...
|
||||
|
||||
class NonGeneric2(tuple):
|
||||
def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ...
|
||||
|
||||
class Generic1[T](list):
|
||||
def __new__(cls: type[Generic1]) -> Generic1: ...
|
||||
def __enter__(self: Generic1) -> Generic1: ...
|
||||
|
||||
|
||||
### Correctness of typevar-likes are not verified.
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec()
|
||||
Ts = TypeVarTuple('foo')
|
||||
|
||||
class Generic2(Generic[T]):
|
||||
def __new__(cls: type[Generic2]) -> Generic2: ...
|
||||
def __enter__(self: Generic2) -> Generic2: ...
|
||||
|
||||
class Generic3(tuple[*Ts]):
|
||||
def __new__(cls: type[Generic3]) -> Generic3: ...
|
||||
def __enter__(self: Generic3) -> Generic3: ...
|
||||
|
||||
class Generic4(collections.abc.Callable[P, ...]):
|
||||
def __new__(cls: type[Generic4]) -> Generic4: ...
|
||||
def __enter__(self: Generic4) -> Generic4: ...
|
||||
|
||||
from some_module import PotentialTypeVar
|
||||
|
||||
class Generic5(list[PotentialTypeVar]):
|
||||
def __new__(cls: type[Generic5]) -> Generic5: ...
|
||||
def __enter__(self: Generic5) -> Generic5: ...
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import typing
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator
|
||||
from enum import EnumMeta
|
||||
from typing import Any, Generic, ParamSpec, Type, TypeVar, TypeVarTuple, overload
|
||||
from typing import Any, overload
|
||||
|
||||
import typing_extensions
|
||||
from _typeshed import Self
|
||||
@@ -215,40 +215,3 @@ def __imul__(self, other: Any) -> list[str]: ...
|
||||
|
||||
class UsesStringizedAnnotations:
|
||||
def __iadd__(self, other: "UsesStringizedAnnotations") -> "typing.Self": ...
|
||||
|
||||
|
||||
class NonGeneric1(tuple):
|
||||
def __new__(cls: type[NonGeneric1], *args, **kwargs) -> NonGeneric1: ...
|
||||
def __enter__(self: NonGeneric1) -> NonGeneric1: ...
|
||||
|
||||
class NonGeneric2(tuple):
|
||||
def __new__(cls: Type[NonGeneric2]) -> NonGeneric2: ...
|
||||
|
||||
class Generic1[T](list):
|
||||
def __new__(cls: type[Generic1]) -> Generic1: ...
|
||||
def __enter__(self: Generic1) -> Generic1: ...
|
||||
|
||||
|
||||
### Correctness of typevar-likes are not verified.
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec()
|
||||
Ts = TypeVarTuple('foo')
|
||||
|
||||
class Generic2(Generic[T]):
|
||||
def __new__(cls: type[Generic2]) -> Generic2: ...
|
||||
def __enter__(self: Generic2) -> Generic2: ...
|
||||
|
||||
class Generic3(tuple[*Ts]):
|
||||
def __new__(cls: type[Generic3]) -> Generic3: ...
|
||||
def __enter__(self: Generic3) -> Generic3: ...
|
||||
|
||||
class Generic4(collections.abc.Callable[P, ...]):
|
||||
def __new__(cls: type[Generic4]) -> Generic4: ...
|
||||
def __enter__(self: Generic4) -> Generic4: ...
|
||||
|
||||
from some_module import PotentialTypeVar
|
||||
|
||||
class Generic5(list[PotentialTypeVar]):
|
||||
def __new__(cls: type[Generic5]) -> Generic5: ...
|
||||
def __enter__(self: Generic5) -> Generic5: ...
|
||||
|
||||
@@ -74,7 +74,7 @@ def f():
|
||||
result.append(i) # Ok
|
||||
|
||||
|
||||
async def f():
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
async for i in items:
|
||||
@@ -82,24 +82,17 @@ async def f():
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
async def f():
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
async for i in items:
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
async def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = [1, 2]
|
||||
async for i in items:
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result, _ = [1, 2, 3, 4], ...
|
||||
result, _ = [1,2,3,4], ...
|
||||
for i in range(10):
|
||||
result.append(i * 2) # PERF401
|
||||
result.append(i*2) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
@@ -107,24 +100,23 @@ def f():
|
||||
if True:
|
||||
for i in range(10): # single-line comment 1 should be protected
|
||||
# single-line comment 2 should be protected
|
||||
if i % 2: # single-line comment 3 should be protected
|
||||
result.append(i) # PERF401
|
||||
if i % 2: # single-line comment 3 should be protected
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = [] # comment after assignment should be protected
|
||||
result = [] # comment after assignment should be protected
|
||||
for i in range(10): # single-line comment 1 should be protected
|
||||
# single-line comment 2 should be protected
|
||||
if i % 2: # single-line comment 3 should be protected
|
||||
result.append(i) # PERF401
|
||||
if i % 2: # single-line comment 3 should be protected
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
for i in range(10):
|
||||
"""block comment stops the fix"""
|
||||
result.append(i * 2) # Ok
|
||||
|
||||
result.append(i*2) # Ok
|
||||
|
||||
def f(param):
|
||||
# PERF401
|
||||
@@ -133,107 +125,3 @@ def f(param):
|
||||
new_layers = []
|
||||
for value in param:
|
||||
new_layers.append(value * 3)
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
var = 1
|
||||
for _ in range(10):
|
||||
result.append(var + 1) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
# make sure that `tmp` is not deleted
|
||||
tmp = 1; result = [] # commment should be protected
|
||||
for i in range(10):
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
# make sure that `tmp` is not deleted
|
||||
result = []; tmp = 1 # commment should be protected
|
||||
for i in range(10):
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = [] # comment should be protected
|
||||
for i in range(10):
|
||||
result.append(i * 2) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
result.append(1)
|
||||
for i in range(10):
|
||||
result.append(i * 2) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
result += [1]
|
||||
for i in range(10):
|
||||
result.append(i * 2) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
for val in range(5):
|
||||
result.append(val * 2) # Ok
|
||||
print(val)
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
for val in range(5):
|
||||
result.append(val * 2) # PERF401
|
||||
val = 1
|
||||
print(val)
|
||||
|
||||
|
||||
def f():
|
||||
i = [1, 2, 3]
|
||||
result = []
|
||||
for i in i:
|
||||
result.append(i + 1) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result = []
|
||||
for i in range( # Comment 1 should not be duplicated
|
||||
(
|
||||
2 # Comment 2
|
||||
+ 1
|
||||
)
|
||||
): # Comment 3
|
||||
if i % 2: # Comment 4
|
||||
result.append(
|
||||
(
|
||||
i + 1,
|
||||
# Comment 5
|
||||
2,
|
||||
)
|
||||
) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
result: list[int] = []
|
||||
for i in range(10):
|
||||
result.append(i * 2) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
a, b = [1, 2, 3], [4, 5, 6]
|
||||
result = []
|
||||
for i in a, b:
|
||||
result.append(i[0] + i[1]) # PERF401
|
||||
return result
|
||||
|
||||
|
||||
def f():
|
||||
values = [1, 2, 3]
|
||||
result = []
|
||||
for a in values:
|
||||
print(a)
|
||||
for a in values:
|
||||
result.append(a + 1) # PERF401
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
print('No coding coments here')
|
||||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: ascii -*-
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: ascii -*-
|
||||
# -*- coding: latin -*-
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: ascii -*-
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf8 -*-
|
||||
print("the following is not a coding comment")
|
||||
# -*- coding: ascii -*-
|
||||
@@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# -*- coding: ascii -*-
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user